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) < 8 { 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 blobLength < 0 { return []byte{}, bytes, errors.New("OSC blob arg size not valid: size cannot be negative") } 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 } if int(blobEnd) > len(bytes) { return []byte{}, bytes, errors.New("OSC blob arg size not valid: size specified is larger than remaining bytes when accounting for padding") } 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") } }