mirror of
https://github.com/jwetzell/osc-go.git
synced 2026-07-26 10:28:42 +00:00
413 lines
11 KiB
Go
413 lines
11 KiB
Go
package osc
|
|
|
|
// TODO(jwetzell): split things up
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
func stringToOSCBytes(rawString string) []byte {
|
|
var sb strings.Builder
|
|
|
|
sb.WriteString(rawString)
|
|
sb.WriteString("\u0000")
|
|
|
|
padLength := 4 - (len(sb.String()) % 4)
|
|
if padLength < 4 {
|
|
for range padLength {
|
|
sb.WriteString("\u0000")
|
|
}
|
|
}
|
|
|
|
return []byte(sb.String())
|
|
}
|
|
|
|
func int32ToOSCBytes(number int32) []byte {
|
|
bytes := make([]byte, 4)
|
|
bytes[0] = byte((number >> 24) & 0xFF)
|
|
bytes[1] = byte((number >> 16) & 0xFF)
|
|
bytes[2] = byte((number >> 8) & 0xFF)
|
|
bytes[3] = byte(number & 0xFF)
|
|
return bytes
|
|
}
|
|
|
|
func int64ToOSCBytes(number int64) []byte {
|
|
bytes := make([]byte, 8)
|
|
bytes[0] = byte((number >> 56) & 0xFF)
|
|
bytes[1] = byte((number >> 48) & 0xFF)
|
|
bytes[2] = byte((number >> 40) & 0xFF)
|
|
bytes[3] = byte((number >> 32) & 0xFF)
|
|
bytes[4] = byte((number >> 24) & 0xFF)
|
|
bytes[5] = byte((number >> 16) & 0xFF)
|
|
bytes[6] = byte((number >> 8) & 0xFF)
|
|
bytes[7] = byte(number & 0xFF)
|
|
return bytes
|
|
}
|
|
|
|
func float32ToOSCBytes(number float32) []byte {
|
|
bytes := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(bytes, math.Float32bits(number))
|
|
return bytes
|
|
}
|
|
|
|
func float64ToOSCBytes(number float64) []byte {
|
|
bytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(bytes, math.Float64bits(number))
|
|
return bytes
|
|
}
|
|
|
|
func byteArrayToOSCBytes(bytes []byte) []byte {
|
|
oscBytes := []byte{}
|
|
|
|
bytesSize := len(bytes)
|
|
bytesSizeBytes := int32ToOSCBytes(int32(bytesSize))
|
|
oscBytes = append(oscBytes, bytesSizeBytes...)
|
|
oscBytes = append(oscBytes, bytes...)
|
|
|
|
padLength := 4 - (bytesSize % 4)
|
|
if padLength < 4 {
|
|
for range padLength {
|
|
oscBytes = append(oscBytes, 0)
|
|
}
|
|
}
|
|
|
|
return oscBytes
|
|
}
|
|
|
|
func timeTagToOSCBytes(timeTag OSCTimeTag) []byte {
|
|
timeTagBytes := int32ToOSCBytes(timeTag.seconds)
|
|
fractionalSecondsBytes := int32ToOSCBytes(timeTag.fractionalSeconds)
|
|
timeTagBytes = append(timeTagBytes, fractionalSecondsBytes...)
|
|
|
|
return timeTagBytes
|
|
}
|
|
|
|
func argsToBuffer(args []OSCArg) ([]byte, error) {
|
|
//TODO(jwetzell): add error handling
|
|
var argBuffers = []byte{}
|
|
|
|
for _, arg := range args {
|
|
switch arg.Type {
|
|
case "s":
|
|
if value, ok := arg.Value.(string); ok {
|
|
argBuffers = append(argBuffers, stringToOSCBytes(value)...)
|
|
} else {
|
|
return nil, errors.New("OSC arg had string type but non-string value")
|
|
}
|
|
case "i":
|
|
if value, ok := arg.Value.(int); ok {
|
|
valueBytes := int32ToOSCBytes(int32(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int32); ok {
|
|
valueBytes := int32ToOSCBytes(int32(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else {
|
|
return nil, errors.New("OSC arg had int32 type but non-number value")
|
|
}
|
|
case "f":
|
|
if value, ok := arg.Value.(float32); ok {
|
|
valueBytes := float32ToOSCBytes(value)
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(float64); ok {
|
|
valueBytes := float32ToOSCBytes(float32(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int); ok {
|
|
valueBytes := float32ToOSCBytes(float32(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int32); ok {
|
|
valueBytes := float32ToOSCBytes(float32(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int64); ok {
|
|
valueBytes := float32ToOSCBytes(float32(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else {
|
|
return nil, errors.New("OSC arg had float32 type but non-number value")
|
|
}
|
|
case "b":
|
|
if value, ok := arg.Value.([]byte); ok {
|
|
valueBytes := byteArrayToOSCBytes(value)
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else {
|
|
return nil, errors.New("OSC arg had blob type but non-blob value")
|
|
}
|
|
case "T":
|
|
argBuffers = append(argBuffers, make([]byte, 0)...)
|
|
case "F":
|
|
argBuffers = append(argBuffers, make([]byte, 0)...)
|
|
case "N":
|
|
argBuffers = append(argBuffers, make([]byte, 0)...)
|
|
case "I":
|
|
argBuffers = append(argBuffers, make([]byte, 0)...)
|
|
case "r":
|
|
color, ok := arg.Value.(OSCColor)
|
|
if !ok {
|
|
return nil, errors.New("OSC arg had color type but non-color value")
|
|
}
|
|
if ok {
|
|
colorBytes := []byte{color.r, color.g, color.b, color.a}
|
|
argBuffers = append(argBuffers, colorBytes...)
|
|
}
|
|
case "h":
|
|
if value, ok := arg.Value.(int); ok {
|
|
valueBytes := int64ToOSCBytes(int64(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int32); ok {
|
|
valueBytes := int64ToOSCBytes(int64(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int64); ok {
|
|
valueBytes := int64ToOSCBytes(value)
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else {
|
|
return nil, errors.New("OSC arg had int64 type but non-number value")
|
|
}
|
|
case "d":
|
|
if value, ok := arg.Value.(float32); ok {
|
|
valueBytes := float64ToOSCBytes(float64(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(float64); ok {
|
|
valueBytes := float64ToOSCBytes(value)
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int); ok {
|
|
valueBytes := float64ToOSCBytes(float64(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int32); ok {
|
|
valueBytes := float64ToOSCBytes(float64(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else if value, ok := arg.Value.(int64); ok {
|
|
valueBytes := float64ToOSCBytes(float64(value))
|
|
argBuffers = append(argBuffers, valueBytes...)
|
|
} else {
|
|
return nil, errors.New("OSC arg had float64 type but non-number value")
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported OSC argument type: %s", arg.Type)
|
|
}
|
|
}
|
|
return argBuffers, nil
|
|
}
|
|
|
|
func readOSCString(bytes []byte) (string, []byte, error) {
|
|
oscString := ""
|
|
stringEndIndex := 0
|
|
|
|
nullByteFound := false
|
|
for index, byteIn := range bytes {
|
|
if byteIn == 0 {
|
|
nullByteFound = true
|
|
oscString = string(bytes[0:index])
|
|
stringEndIndex = index + 1
|
|
break
|
|
}
|
|
}
|
|
|
|
if !nullByteFound {
|
|
return "", bytes, errors.New("OSC string must be null-terminated")
|
|
}
|
|
|
|
stringPadding := 4 - (stringEndIndex % 4)
|
|
|
|
if stringPadding < 4 {
|
|
stringEndIndex = stringEndIndex + stringPadding
|
|
}
|
|
|
|
if stringEndIndex > len(bytes) {
|
|
return "", bytes, errors.New("OSC string is not properly padded")
|
|
}
|
|
|
|
remainingBytes := bytes[stringEndIndex:]
|
|
|
|
return oscString, remainingBytes, nil
|
|
}
|
|
|
|
func readOSCInt32(bytes []byte) (int32, []byte, error) {
|
|
if len(bytes) < 4 {
|
|
return 0, bytes, errors.New("OSC int32 arg is not 4 bytes")
|
|
}
|
|
bits := binary.BigEndian.Uint32(bytes[0:4])
|
|
return int32(bits), bytes[4:], nil
|
|
}
|
|
|
|
func readOSCInt64(bytes []byte) (int64, []byte, error) {
|
|
if len(bytes) < 8 {
|
|
return 0, bytes, errors.New("OSC int64 arg is not 8 bytes")
|
|
}
|
|
bits := binary.BigEndian.Uint64(bytes[0:8])
|
|
return int64(bits), bytes[8:], nil
|
|
}
|
|
|
|
func readOSCFloat32(bytes []byte) (float32, []byte, error) {
|
|
if len(bytes) < 4 {
|
|
return 0, bytes, errors.New("OSC float32 arg is not 4 bytes")
|
|
}
|
|
bits := binary.BigEndian.Uint32(bytes[0:4])
|
|
return math.Float32frombits(bits), bytes[4:], nil
|
|
}
|
|
|
|
func readOSCFloat64(bytes []byte) (float64, []byte, error) {
|
|
if len(bytes) < 4 {
|
|
return 0, bytes, errors.New("OSC float64 arg is not 8 bytes")
|
|
}
|
|
bits := binary.BigEndian.Uint64(bytes[0:8])
|
|
return math.Float64frombits(bits), bytes[8:], nil
|
|
}
|
|
|
|
func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
|
|
blobLength, remainingBytes, err := readOSCInt32(bytes)
|
|
|
|
if err != nil {
|
|
return []byte{}, bytes, errors.New("OSC blob arg size not valid: " + err.Error())
|
|
}
|
|
|
|
if len(remainingBytes) < int(blobLength) {
|
|
return []byte{}, bytes, errors.New("OSC blob arg size not valid: size specified is larger than remaining bytes")
|
|
}
|
|
|
|
blobLengthPadding := 4 - (blobLength % 4)
|
|
blobEnd := 4 + blobLength
|
|
|
|
if blobLengthPadding < 4 {
|
|
blobEnd = blobEnd + blobLengthPadding
|
|
}
|
|
return bytes[4 : 4+blobLength], bytes[blobEnd:], nil
|
|
}
|
|
|
|
func readOSCColor(bytes []byte) (OSCColor, []byte, error) {
|
|
if len(bytes) < 4 {
|
|
return OSCColor{0, 0, 0, 0}, bytes, errors.New("OSC color arg is not 4 bytes")
|
|
}
|
|
oscColor := OSCColor{
|
|
r: bytes[0],
|
|
g: bytes[1],
|
|
b: bytes[2],
|
|
a: bytes[3],
|
|
}
|
|
return oscColor, bytes[4:], nil
|
|
}
|
|
|
|
func readOSCTimeTag(bytes []byte) (OSCTimeTag, []byte, error) {
|
|
seconds, bytesAfterSeconds, err := readOSCInt32(bytes)
|
|
if err != nil {
|
|
return OSCTimeTag{}, bytes, fmt.Errorf("OSC time tag seconds are not valid: %s", err)
|
|
}
|
|
fractionalSeconds, remainingBytes, err := readOSCInt32(bytesAfterSeconds)
|
|
if err != nil {
|
|
return OSCTimeTag{}, bytes, fmt.Errorf("OSC time tag fractional seconds are not valid: %s", err)
|
|
}
|
|
|
|
return OSCTimeTag{
|
|
seconds: seconds,
|
|
fractionalSeconds: fractionalSeconds,
|
|
},
|
|
remainingBytes,
|
|
nil
|
|
}
|
|
|
|
func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) {
|
|
var readArgError error
|
|
|
|
oscArg := OSCArg{}
|
|
oscArg.Type = oscType
|
|
|
|
var remainingBytes []byte
|
|
//TODO(jwetzell): add error handling
|
|
switch oscType {
|
|
case "s":
|
|
argString, bytesLeft, err := readOSCString(bytes)
|
|
if err != nil {
|
|
return OSCArg{}, bytes, err
|
|
}
|
|
oscArg.Value = argString
|
|
remainingBytes = bytesLeft
|
|
case "i":
|
|
argInt, bytesLeft, err := readOSCInt32(bytes)
|
|
if err != nil {
|
|
readArgError = err
|
|
}
|
|
oscArg.Value = argInt
|
|
remainingBytes = bytesLeft
|
|
case "f":
|
|
argFloat, bytesLeft, err := readOSCFloat32(bytes)
|
|
if err != nil {
|
|
readArgError = err
|
|
}
|
|
oscArg.Value = argFloat
|
|
remainingBytes = bytesLeft
|
|
case "b":
|
|
argBytes, bytesLeft, err := readOSCBlob(bytes)
|
|
if err != nil {
|
|
readArgError = err
|
|
}
|
|
oscArg.Value = argBytes
|
|
remainingBytes = bytesLeft
|
|
case "T":
|
|
oscArg.Value = true
|
|
remainingBytes = bytes
|
|
case "F":
|
|
oscArg.Value = false
|
|
remainingBytes = bytes
|
|
case "N":
|
|
oscArg.Value = nil
|
|
remainingBytes = bytes
|
|
case "I":
|
|
oscArg.Value = math.MaxInt32
|
|
remainingBytes = bytes
|
|
case "r":
|
|
argColor, bytesLeft, err := readOSCColor(bytes)
|
|
if err != nil {
|
|
readArgError = err
|
|
}
|
|
oscArg.Value = argColor
|
|
remainingBytes = bytesLeft
|
|
case "h":
|
|
argInt, bytesLeft, err := readOSCInt64(bytes)
|
|
if err != nil {
|
|
readArgError = err
|
|
}
|
|
oscArg.Value = argInt
|
|
remainingBytes = bytesLeft
|
|
case "d":
|
|
argFloat, bytesLeft, err := readOSCFloat64(bytes)
|
|
if err != nil {
|
|
readArgError = err
|
|
}
|
|
oscArg.Value = argFloat
|
|
remainingBytes = bytesLeft
|
|
case "t":
|
|
argTimeTag, bytesLeft, err := readOSCTimeTag(bytes)
|
|
if err != nil {
|
|
readArgError = err
|
|
}
|
|
oscArg.Value = argTimeTag
|
|
remainingBytes = bytesLeft
|
|
default:
|
|
return OSCArg{}, bytes, fmt.Errorf("unsupported OSC argument type: %s", oscType)
|
|
}
|
|
return oscArg, remainingBytes, readArgError
|
|
}
|
|
|
|
func PacketFromBytes(bytes []byte) (OSCPacket, []byte, error) {
|
|
if len(bytes) == 0 {
|
|
return nil, bytes, errors.New("cannot create OSC Packet from empty byte array")
|
|
}
|
|
|
|
switch bytes[0] {
|
|
case '#':
|
|
bundle, remainingBytes, err := BundleFromBytes(bytes)
|
|
if err != nil {
|
|
return nil, bytes, err
|
|
}
|
|
return bundle, remainingBytes, nil
|
|
case '/':
|
|
message, err := MessageFromBytes(bytes)
|
|
if err != nil {
|
|
return nil, bytes, err
|
|
}
|
|
return message, []byte{}, nil
|
|
default:
|
|
return nil, bytes, errors.New("OSC Packet must start with # for bundle or / for message")
|
|
}
|
|
}
|