Compare commits

..

38 Commits

Author SHA1 Message Date
jwetzell bdc1b45a9b add release workflow for receiveosc 2025-05-18 20:08:37 -05:00
jwetzell 518a083d04 add TCP to receiveosc 2025-05-18 20:06:19 -05:00
jwetzell 48ec7908dd switch to using the actual osc library here 2025-05-18 19:31:56 -05:00
jwetzell 8b07ce0a44 Create dependabot.yml 2025-05-18 19:10:13 -05:00
jwetzell 5965c10942 fieldalignment tool 2024-12-26 14:57:40 -06:00
jwetzell 0928352a57 move package to root level 2024-12-26 14:57:19 -06:00
jwetzell 46ea24d25e Merge pull request #2 from jwetzell/feat/osc-lib
basic osc lib
2024-12-22 14:18:34 -06:00
jwetzell 676644a457 add osc bundle encoding with proper timetag 2024-12-22 14:17:14 -06:00
jwetzell b71ee95364 add osc timetag 2024-12-22 14:16:57 -06:00
jwetzell fea1d47085 cleanup comments 2024-12-22 13:38:48 -06:00
jwetzell fa3c29d0a0 bundle encoding 2024-12-22 13:38:32 -06:00
jwetzell 67920c5921 prep for bundle work 2024-12-22 13:37:56 -06:00
jwetzell 757eb8f4c1 split message functions out to separate file 2024-12-15 23:27:45 -06:00
jwetzell 3e1449f374 add decoding for all types except array 2024-12-15 22:52:06 -06:00
jwetzell a742219efa add encoding for all types except array 2024-12-15 22:51:56 -06:00
jwetzell bbdb193d65 split types out 2024-12-15 22:50:32 -06:00
jwetzell 94251d47d7 bring back the osc lib for go learning reasons 2024-12-10 17:56:03 +00:00
jwetzell 952223d0b6 add release script for makeosc 2024-10-21 15:22:34 -05:00
jwetzell 4ef030848f add more osc types to makeosc 2024-10-21 15:22:27 -05:00
jwetzell 8ecda737e4 one more try 2024-10-21 15:19:22 -05:00
jwetzell 24dabc86dd try something else 2024-10-21 15:15:40 -05:00
jwetzell 3790e83b9c fix binary name and workflow dependency 2024-10-21 15:00:07 -05:00
jwetzell d60515863e rework sendosc release workflow 2024-10-21 14:48:43 -05:00
jwetzell defe4e1a3d Create LICENSE 2024-10-21 14:37:10 -05:00
jwetzell 9990c08f70 Merge pull request #1 from jwetzell/add-more-osc-types
Add more osc types
2024-10-21 14:35:32 -05:00
jwetzell 5f4ab5bbb8 add OSC nil type 2024-10-21 14:31:13 -05:00
jwetzell 1de7be013e add osc true and false types 2024-10-21 14:30:44 -05:00
jwetzell 5dc08cd907 add osc double type 2024-10-21 14:29:27 -05:00
jwetzell e8f0fb7d14 add osc int64 type 2024-10-21 14:28:48 -05:00
jwetzell 95a076717f try upstream osc lib as well 2024-09-30 18:33:06 -05:00
jwetzell 1fa6f5fbcc Create README.md 2024-09-30 12:41:48 -05:00
jwetzell 32212ce256 switch to an OSC library 2024-09-30 12:34:18 -05:00
jwetzell d19f0cd1ff add script to send osc bytes to stdout 2024-09-29 23:15:02 -05:00
jwetzell 2bba8da489 todo comments 2024-09-29 18:27:32 -05:00
jwetzell 9b17a6c662 add basic script for receiving osc 2024-09-29 18:27:24 -05:00
jwetzell 46fcdfd944 add parsing of OSC Messages only 2024-09-29 18:20:23 -05:00
jwetzell 21b61db4c9 rename ToBuffer to ToBytes 2024-09-29 15:56:51 -05:00
jwetzell a2a211dfe3 restructure to more generic go module 2024-09-29 15:50:25 -05:00
16 changed files with 1442 additions and 142 deletions
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: "weekly"
+49
View File
@@ -0,0 +1,49 @@
name: "release go binaries for multiple os/arch"
on:
push:
tags:
- 'makeosc/*'
permissions:
contents: write
packages: write
jobs:
create-release:
name: Create makeosc release
runs-on: ubuntu-latest
steps:
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
release-multi:
name: create binaries and upload
needs: create-release
runs-on: ubuntu-latest
strategy:
matrix:
goos: [linux, windows, darwin]
goarch: ["386", amd64, arm64]
exclude:
- goarch: "386"
goos: darwin
steps:
- uses: actions/checkout@v4
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "1.23.1"
project_path: "./cmd/makeosc"
binary_name: "makeosc"
asset_name: makeosc-${{ matrix.goos }}-${{ matrix.goarch }}
release_name: ${{github.ref_name}}
+49
View File
@@ -0,0 +1,49 @@
name: "release go binaries for multiple os/arch"
on:
push:
tags:
- 'receiveosc/*'
permissions:
contents: write
packages: write
jobs:
create-release:
name: Create receiveosc release
runs-on: ubuntu-latest
steps:
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
release-multi:
name: create binaries and upload
needs: create-release
runs-on: ubuntu-latest
strategy:
matrix:
goos: [linux, windows, darwin]
goarch: ["386", amd64, arm64]
exclude:
- goarch: "386"
goos: darwin
steps:
- uses: actions/checkout@v4
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "1.23.1"
project_path: "./cmd/receiveosc"
binary_name: "receiveosc"
asset_name: receiveosc-${{ matrix.goos }}-${{ matrix.goarch }}
release_name: ${{github.ref_name}}
@@ -1,16 +1,32 @@
name: "release go binaries for multiple os/arch"
on:
release:
types: [created]
push:
tags:
- 'sendosc/*'
permissions:
contents: write
packages: write
jobs:
create-release:
name: Create sendosc release
runs-on: ubuntu-latest
steps:
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
release-multi:
name: Release Go Binary
name: create binaries and upload
needs: create-release
runs-on: ubuntu-latest
strategy:
matrix:
@@ -29,3 +45,5 @@ jobs:
goversion: "1.23.1"
project_path: "./cmd/sendosc"
binary_name: "sendosc"
asset_name: sendosc-${{ matrix.goos }}-${{ matrix.goarch }}
release_name: ${{github.ref_name}}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Joel Wetzell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+5
View File
@@ -0,0 +1,5 @@
A collection of command line OSC utilities written in Go. Mainly an exercise in learning Go.
# `sendosc`
# `makeosc`
# `receiveosc`
+95
View File
@@ -0,0 +1,95 @@
package osc
import (
"errors"
)
func (b *OSCBundle) ToBytes() []byte {
bytes := stringToOSCBytes("#bundle")
bytes = append(bytes, timeTagToOSCBytes(b.TimeTag)...)
for _, packet := range b.Contents {
packetBytes := packet.ToBytes()
packetLength := len(packet.ToBytes())
bytes = append(bytes, int32ToOSCBytes(int32(packetLength))...)
bytes = append(bytes, packetBytes...)
}
return bytes
}
func BundleFromBytes(bytes []byte) (OSCBundle, []byte, error) {
if len(bytes) < 20 {
return OSCBundle{}, bytes, errors.New("bundle has to be at least 20 bytes")
}
if bytes[0] != 35 {
return OSCBundle{}, bytes, errors.New("bundle must start with a #")
}
bundleHeader, bytesAfterBundleHeader := readOSCString(bytes)
if bundleHeader != "#bundle" {
return OSCBundle{}, bytesAfterBundleHeader, errors.New("bundle must start with #bundle string")
}
timeTag, bytesAfterTimeTag, err := readOSCTimeTag(bytesAfterBundleHeader)
if err != nil {
return OSCBundle{}, bytesAfterBundleHeader, err
}
bundleContents := []OSCPacket{}
endOfBundle := false
remainingBytes := bytesAfterTimeTag
for !endOfBundle {
contentSize, bytesAfterContentSize, err := readOSCInt32(remainingBytes)
if err != nil {
return OSCBundle{}, remainingBytes, err
}
remainingBytes = bytesAfterContentSize
if len(remainingBytes) < int(contentSize) {
return OSCBundle{}, remainingBytes, errors.New("bundle doesn't have enough bytes for the content size it specifies")
}
bundleContentBytes := remainingBytes[0:contentSize]
if bundleContentBytes[0] == 35 {
content, _, err := BundleFromBytes(bundleContentBytes)
if err != nil {
return OSCBundle{}, remainingBytes, err
}
bundleContents = append(bundleContents, &content)
} else if bundleContentBytes[0] == 47 {
content, err := MessageFromBytes(bundleContentBytes)
if err != nil {
return OSCBundle{}, remainingBytes, err
}
bundleContents = append(bundleContents, &content)
} else {
return OSCBundle{}, remainingBytes, errors.New("bundle contents does not look a bundle or message")
}
remainingBytes = bytesAfterContentSize[contentSize:]
if len(remainingBytes) == 0 {
endOfBundle = true
}
}
return OSCBundle{
TimeTag: timeTag,
Contents: bundleContents,
},
remainingBytes,
nil
}
+89
View File
@@ -0,0 +1,89 @@
package osc
import (
"fmt"
"reflect"
"testing"
)
func TestOSCBundleEncoding(t *testing.T) {
testCases := []struct {
description string
bundle OSCBundle
expected []byte
}{
{
"simple contents single message",
OSCBundle{
TimeTag: OSCTimeTag{
seconds: 32,
fractionalSeconds: 0,
},
Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
},
[]byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0, 32, 47, 111,
115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52,
47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0,
44, 102, 0, 0, 67, 220, 0, 0},
},
}
for _, testCase := range testCases {
actual := testCase.bundle.ToBytes()
if !reflect.DeepEqual(actual, testCase.expected) {
t.Errorf("Test '%s' failed to encode properly", testCase.description)
fmt.Printf("expected: %v\n", testCase.expected)
fmt.Printf("actual: %v\n", actual)
}
}
}
func TestOSCBundleDecoding(t *testing.T) {
testCases := []struct {
description string
expected OSCBundle
bytes []byte
}{
{
"simple contents single message",
OSCBundle{
TimeTag: OSCTimeTag{
seconds: 32,
fractionalSeconds: 0,
},
Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
},
[]byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0, 32, 47, 111,
115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52,
47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0,
44, 102, 0, 0, 67, 220, 0, 0},
},
}
for _, testCase := range testCases {
actual, remainingBytes, error := BundleFromBytes(testCase.bytes)
if error != nil {
fmt.Println(error)
t.Errorf("Test '%s' failed to encode properly", testCase.description)
}
if len(remainingBytes) > 0 {
t.Errorf("Test '%s' should not have any remaining bytes", testCase.description)
}
if !reflect.DeepEqual(actual, testCase.expected) {
t.Errorf("Test '%s' failed to encode bundle properly", testCase.description)
fmt.Printf("expected: %v\n", testCase.expected)
fmt.Printf("actual: %v\n", actual)
}
}
}
+161
View File
@@ -0,0 +1,161 @@
package main
import (
"encoding/hex"
"fmt"
"os"
"strconv"
osc "github.com/jwetzell/osc-go"
"github.com/spf13/cobra"
)
func main() {
var Address string
var Args []string
var Types []string
var Slip bool
var rootCmd = &cobra.Command{
Use: "sendosc",
Run: func(cmd *cobra.Command, args []string) {
make(Address, Args, Types, Slip)
},
}
rootCmd.Flags().StringVar(&Address, "address", "", "OSC address")
rootCmd.Flags().StringArrayVar(&Args, "arg", []string{}, "OSC args")
rootCmd.Flags().StringArrayVar(&Types, "type", []string{}, "OSC types")
rootCmd.Flags().BoolVar(&Slip, "slip", false, "whether to slip encode the OSC Message bytes")
rootCmd.MarkFlagRequired("address")
rootCmd.Execute()
}
func argToTypedArg(rawArg string, oscType string) osc.OSCArg {
switch oscType {
case "s":
return osc.OSCArg{
Value: rawArg,
Type: "s",
}
case "i":
number, err := strconv.ParseInt(rawArg, 10, 32)
if err != nil {
// ... handle error
panic(err)
}
return osc.OSCArg{
Value: int32(number),
Type: "i",
}
case "f":
number, err := strconv.ParseFloat(rawArg, 32)
if err != nil {
// ... handle error
panic(err)
}
return osc.OSCArg{
Value: float32(number),
Type: "f",
}
case "b":
data, err := hex.DecodeString(rawArg)
if err != nil {
// ... handle error
panic(err)
}
return osc.OSCArg{
Value: data,
Type: "b",
}
case "h":
number, err := strconv.ParseInt(rawArg, 10, 64)
if err != nil {
// ... handle error
panic(err)
}
return osc.OSCArg{
Value: int64(number),
Type: "h",
}
case "d":
number, err := strconv.ParseFloat(rawArg, 64)
if err != nil {
// ... handle error
panic(err)
}
return osc.OSCArg{
Value: float64(number),
Type: "d",
}
case "T":
return osc.OSCArg{
Value: true,
Type: "T",
}
case "F":
return osc.OSCArg{
Value: false,
Type: "F",
}
case "N":
return osc.OSCArg{
Value: nil,
Type: "N",
}
default:
fmt.Print("unhandled osc type: ")
fmt.Printf("%s.\n", oscType)
// TODO(jwetzell): something better than this like actual nil, err thing
return osc.OSCArg{}
}
}
func slipEncode(bytes []byte) []byte {
END := byte(0xc0)
ESC := byte(0xdb)
ESC_END := byte(0xdc)
ESC_ESC := byte(0xdd)
var encodedBytes = []byte{}
for _, byteToEncode := range bytes {
if byteToEncode == END {
encodedBytes = append(encodedBytes, ESC_END)
} else if byteToEncode == ESC {
encodedBytes = append(encodedBytes, ESC_ESC)
} else {
encodedBytes = append(encodedBytes, byteToEncode)
}
}
encodedBytes = append(encodedBytes, END)
return encodedBytes
}
func make(address string, args []string, types []string, slip bool) {
oscMessage := osc.OSCMessage{
Address: address,
Args: []osc.OSCArg{},
}
for index, arg := range args {
oscType := "s"
if len(types) > index {
oscType = types[index]
}
oscMessage.Args = append(oscMessage.Args, argToTypedArg(arg, oscType))
}
oscMessageBuffer := oscMessage.ToBytes()
if slip {
oscMessageBuffer = slipEncode(oscMessageBuffer)
}
//TODO write buffer to stdout
os.Stdout.Write(oscMessageBuffer)
}
+157
View File
@@ -0,0 +1,157 @@
package main
import (
"fmt"
"net"
osc "github.com/jwetzell/osc-go"
"github.com/spf13/cobra"
)
func main() {
var Host string
var Port string
var Protocol string
var rootCmd = &cobra.Command{
Use: "sendosc",
Run: func(cmd *cobra.Command, args []string) {
netAddress := Host + ":" + Port
if Protocol == "udp" {
listenUDP(netAddress)
} else if Protocol == "tcp" {
listenTCP(netAddress)
}
},
}
rootCmd.Flags().StringVar(&Host, "host", "127.0.0.1", "host to send OSC message to")
rootCmd.Flags().StringVar(&Port, "port", "8888", "port to send OSC message to")
rootCmd.Flags().StringVar(&Protocol, "protocol", "udp", "protocol to use to send (tcp or udp)")
rootCmd.Execute()
}
func listenTCP(netAddress string) {
socket, err := net.Listen("tcp4", netAddress)
if err != nil {
fmt.Println(err)
return
}
defer socket.Close()
fmt.Printf("listening on %s (tcp w/ SLIP)\n", netAddress)
for {
conn, err := socket.Accept()
if err != nil {
fmt.Println(err)
continue
}
go handleConnection(conn)
}
}
type SLIP struct {
pendingBytes []byte
Messages chan osc.OSCMessage
}
func (s *SLIP) decode(bytes []byte) {
END := byte(0xc0)
ESC := byte(0xdb)
ESC_END := byte(0xdc)
ESC_ESC := byte(0xdd)
escapeNext := false
for _, packetByte := range bytes {
if packetByte == ESC {
escapeNext = true
continue
}
if escapeNext {
if packetByte == ESC_END {
s.pendingBytes = append(s.pendingBytes, END)
} else if packetByte == ESC_ESC {
s.pendingBytes = append(s.pendingBytes, ESC)
}
escapeNext = false
} else if packetByte == END {
if len(s.pendingBytes) > 0 {
message, err := osc.MessageFromBytes(s.pendingBytes)
if err != nil {
fmt.Println(err)
} else {
s.Messages <- message
}
}
s.pendingBytes = []byte{}
} else {
s.pendingBytes = append(s.pendingBytes, packetByte)
}
}
}
func handleMessages(slip SLIP) {
for message := range slip.Messages {
fmt.Printf("%v\n", message)
}
}
func handleConnection(conn net.Conn) {
slip := SLIP{
pendingBytes: []byte{},
Messages: make(chan osc.OSCMessage),
}
go handleMessages(slip)
defer conn.Close()
buffer := make([]byte, 1024)
for {
bytesRead, err := conn.Read(buffer)
if err != nil {
return
}
slip.decode(buffer[0:bytesRead])
}
}
func listenUDP(netAddress string) {
s, err := net.ResolveUDPAddr("udp4", netAddress)
if err != nil {
fmt.Println(err)
return
}
connection, err := net.ListenUDP("udp4", s)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("listening on %s (udp)\n", netAddress)
defer connection.Close()
buffer := make([]byte, 1024)
for {
bytesRead, _, err := connection.ReadFromUDP(buffer)
if err != nil {
panic(err)
}
oscMessage, err := osc.MessageFromBytes(buffer[0:bytesRead])
if err != nil {
panic(err)
}
fmt.Println(oscMessage)
}
}
+57 -138
View File
@@ -1,134 +1,16 @@
package main
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"strconv"
"strings"
osc "github.com/jwetzell/osc-go"
"github.com/spf13/cobra"
)
type OSCArg struct {
Type string
Value any
}
type OSCMessage struct {
Address string
Args []OSCArg
}
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 i := 0; i < padLength; i++ {
sb.WriteString("\u0000")
}
}
return []byte(sb.String())
}
func integerToOSCBytes(number int32) []byte {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
}
return buf.Bytes()
}
func floatToOSCBytes(number float32) []byte {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
}
return buf.Bytes()
}
func byteArrayToOSCBytes(bytes []byte) []byte {
oscBytes := []byte{}
bytesSize := len(bytes)
oscBytes = append(oscBytes, integerToOSCBytes(int32(bytesSize))...)
oscBytes = append(oscBytes, bytes...)
padLength := 4 - (bytesSize % 4)
if padLength < 4 {
for i := 0; i < padLength; i++ {
oscBytes = append(oscBytes, 0)
}
}
return oscBytes
}
func argsToBuffer(args []OSCArg) []byte {
var argBuffers = []byte{}
for _, arg := range args {
switch oscType := arg.Type; oscType {
case "s":
if value, ok := arg.Value.(string); ok {
argBuffers = append(argBuffers, stringToOSCBytes(value)...)
} else {
fmt.Println("OSC arg had string type but non-string value.")
}
case "i":
if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, integerToOSCBytes(value)...)
} else {
fmt.Println("OSC arg had integer type but non-integer value.")
}
case "f":
if value, ok := arg.Value.(float32); ok {
argBuffers = append(argBuffers, floatToOSCBytes(value)...)
} else {
fmt.Println("OSC arg had float type but non-float value.")
}
case "b":
if value, ok := arg.Value.([]byte); ok {
argBuffers = append(argBuffers, byteArrayToOSCBytes(value)...)
} else {
fmt.Println("OSC arg had blob type but non-blob value.")
}
default:
fmt.Print("unhandled osc type: ")
fmt.Printf("%s.\n", oscType)
}
}
return argBuffers
}
func messageToBuffer(message OSCMessage) []byte {
oscBuffer := []byte{}
oscBuffer = append(oscBuffer, stringToOSCBytes(message.Address)...)
var sb strings.Builder
sb.WriteString(",")
for _, arg := range message.Args {
sb.WriteString(arg.Type)
}
oscBuffer = append(oscBuffer, stringToOSCBytes(sb.String())...)
oscBuffer = append(oscBuffer, argsToBuffer(message.Args)...)
return oscBuffer
}
func main() {
var Host string
var Port int32
@@ -157,12 +39,13 @@ func main() {
rootCmd.Execute()
}
func argToTypedArg(rawArg string, oscType string) OSCArg {
func argToTypedArg(rawArg string, oscType string) osc.OSCArg {
switch oscType {
case "s":
return OSCArg{
Type: "s",
return osc.OSCArg{
Value: rawArg,
Type: "s",
}
case "i":
number, err := strconv.ParseInt(rawArg, 10, 32)
@@ -170,9 +53,9 @@ func argToTypedArg(rawArg string, oscType string) OSCArg {
// ... handle error
panic(err)
}
return OSCArg{
Type: "i",
return osc.OSCArg{
Value: int32(number),
Type: "i",
}
case "f":
number, err := strconv.ParseFloat(rawArg, 32)
@@ -180,9 +63,9 @@ func argToTypedArg(rawArg string, oscType string) OSCArg {
// ... handle error
panic(err)
}
return OSCArg{
Type: "f",
return osc.OSCArg{
Value: float32(number),
Type: "f",
}
case "b":
data, err := hex.DecodeString(rawArg)
@@ -190,14 +73,50 @@ func argToTypedArg(rawArg string, oscType string) OSCArg {
// ... handle error
panic(err)
}
return OSCArg{
Type: "b",
return osc.OSCArg{
Value: data,
Type: "b",
}
case "h":
number, err := strconv.ParseInt(rawArg, 10, 64)
if err != nil {
// ... handle error
panic(err)
}
return osc.OSCArg{
Value: int64(number),
Type: "h",
}
case "d":
number, err := strconv.ParseFloat(rawArg, 64)
if err != nil {
// ... handle error
panic(err)
}
return osc.OSCArg{
Value: float64(number),
Type: "d",
}
case "T":
return osc.OSCArg{
Value: true,
Type: "T",
}
case "F":
return osc.OSCArg{
Value: false,
Type: "F",
}
case "N":
return osc.OSCArg{
Value: nil,
Type: "N",
}
default:
fmt.Print("unhandled osc type: ")
fmt.Printf("%s.\n", oscType)
return OSCArg{}
// TODO(jwetzell): something better than this like actual nil, err thing
return osc.OSCArg{}
}
}
@@ -225,23 +144,23 @@ func slipEncode(bytes []byte) []byte {
func send(host string, port int32, address string, args []string, types []string, protocol string, slip bool) {
oscArgs := []OSCArg{}
oscMessage := osc.OSCMessage{
Address: address,
Args: []osc.OSCArg{},
}
for index, arg := range args {
oscType := "s"
if len(types) > index {
oscType = types[index]
}
arg := argToTypedArg(arg, oscType)
oscMessage.Args = append(oscMessage.Args, arg)
oscArgs = append(oscArgs, argToTypedArg(arg, oscType))
}
oscMessage := OSCMessage{
Address: address,
Args: oscArgs,
}
oscMessageBuffer := messageToBuffer(oscMessage)
oscMessageBuffer := oscMessage.ToBytes()
if slip {
oscMessageBuffer = slipEncode(oscMessageBuffer)
+1 -1
View File
@@ -1,4 +1,4 @@
module sendosc
module github.com/jwetzell/osc-go
go 1.23.1
+58
View File
@@ -0,0 +1,58 @@
package osc
import (
"errors"
"strings"
)
func (m *OSCMessage) ToBytes() []byte {
//TODO(jwetzell): add error handling
oscBuffer := []byte{}
oscBuffer = append(oscBuffer, stringToOSCBytes(m.Address)...)
var sb strings.Builder
sb.WriteString(",")
for _, arg := range m.Args {
sb.WriteString(arg.Type)
}
oscBuffer = append(oscBuffer, stringToOSCBytes(sb.String())...)
oscBuffer = append(oscBuffer, argsToBuffer(m.Args)...)
return oscBuffer
}
func MessageFromBytes(bytes []byte) (OSCMessage, error) {
address, typeAndArgBytes := readOSCString(bytes)
if address[0] != 47 {
return OSCMessage{}, errors.New("OSC Message address must start with /")
}
oscMessage := OSCMessage{
Address: address,
Args: []OSCArg{},
}
typeString, argBytes := readOSCString(typeAndArgBytes)
for index, oscType := range typeString {
if index == 0 {
if oscType != ',' {
return OSCMessage{}, errors.New("type string is malformed")
}
} else {
oscArg, remainingBytes, error := readOSCArg(argBytes, string(oscType))
if error != nil {
return oscMessage, error
}
argBytes = remainingBytes
oscMessage.Args = append(oscMessage.Args, oscArg)
}
}
return oscMessage, nil
}
+285
View File
@@ -0,0 +1,285 @@
package osc
import (
"fmt"
"math"
"reflect"
"testing"
)
func TestOSCMessageEncoding(t *testing.T) {
testCases := []struct {
description string
message OSCMessage
expected []byte
}{
{
"simple hello",
OSCMessage{
Address: "/hello",
Args: []OSCArg{},
},
[]byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0},
},
{
"simple address string arg",
OSCMessage{
Address: "/hello",
Args: []OSCArg{
{
Type: "s",
Value: "arg1",
},
},
},
[]byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0},
},
{
description: "simple address integer arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "i", Value: 35}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35},
},
{
description: "simple address float arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "f", Value: 34.5}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0},
},
{
description: "simple address blob arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "b", Value: []byte{98, 108, 111, 98}}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98},
},
{
description: "simple address True arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{OSCArg{Type: "T", Value: true}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0},
},
{
description: "simple address False arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0},
},
{
description: "simple address color arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "r", Value: OSCColor{r: 20, g: 21, b: 22, a: 10}}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10},
},
{
description: "simple address nil arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0},
},
{
description: "simple address int64 arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "h", Value: 281474976710655}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255},
},
{
description: "simple address float64 arg",
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "d", Value: 12.7654763}}},
expected: []byte{
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
},
},
// TODO(jwetzell): get array args working working
// {
// description: "simple address array arg",
// message: OSCMessage{
// Address: "/hello",
// Args: []OSCArg{
// []OSCArg{
// {Type: "d", Value: 12.7654763},
// {Type: "i", Value: 1000},
// },
// },
// },
// expected: []byte{
// 47, 104, 101, 108, 108, 111, 0, 0, 44, 91, 100, 105, 93, 0, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
// 0, 0, 3, 232,
// },
// },
{
description: "osc 1.0 spec example 1",
message: OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: 440}}},
expected: []byte{
47, 111, 115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52, 47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0, 44,
102, 0, 0, 67, 220, 0, 0,
},
},
{
description: "osc 1.0 spec example 2",
message: OSCMessage{
Address: "/foo",
Args: []OSCArg{
{Type: "i", Value: 1000},
{Type: "i", Value: -1},
{Type: "s", Value: "hello"},
// thanks IEEE 754
{Type: "f", Value: 1.2339999675750732421875},
{Type: "f", Value: 5.677999973297119140625},
},
},
expected: []byte{
47, 102, 111, 111, 0, 0, 0, 0, 44, 105, 105, 115, 102, 102, 0, 0, 0, 0, 3, 232, 255, 255, 255, 255, 104, 101, 108,
108, 111, 0, 0, 0, 63, 157, 243, 182, 64, 181, 178, 45,
},
},
}
for _, testCase := range testCases {
actual := testCase.message.ToBytes()
if !reflect.DeepEqual(actual, testCase.expected) {
t.Errorf("Test '%s' failed to encode properly", testCase.description)
fmt.Printf("expected: %v\n", testCase.expected)
fmt.Printf("actual: %v\n", actual)
}
}
}
func TestOSCMessageDecoding(t *testing.T) {
testCases := []struct {
description string
bytes []byte
expected OSCMessage
}{
{
description: "simple address no args",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{}},
},
{
description: "simple address string arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "s", Value: "arg1"}}},
},
{
description: "simple address integer arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "i", Value: int32(35)}}},
},
{
description: "simple address float arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "f", Value: float32(34.5)}}},
},
{
description: "simple address blob arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "b", Value: []byte{98, 108, 111, 98}}}},
},
{
description: "simple address True arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "T", Value: true}}},
},
{
description: "simple address False arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}},
},
{
description: "simple address color arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "r", Value: OSCColor{r: 20, g: 21, b: 22, a: 10}}}},
},
{
description: "simple address nil arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}},
},
{
description: "simple address Inifinitum arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 73, 0, 0},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "I", Value: math.MaxInt32}}},
},
{
description: "simple address int64 arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "h", Value: int64(281474976710655)}}},
},
{
description: "simple address float64 arg",
bytes: []byte{
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "d", Value: float64(12.7654763)}}},
},
// TODO(jwetzell): support OSC array
// {
// description: "simple address array arg",
// bytes: []byte{
// 47, 104, 101, 108, 108, 111, 0, 0, 44, 91, 100, 105, 93, 0, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
// 0, 0, 3, 232,
// },
// expected: OSCMessage{
// Address: "/hello",
// Args: []OSCArg{
// []OSCArg{
// {Type: "d", Value: 12.7654763},
// {Type: "i", Value: 1000},
// },
// },
// },
// },
{
description: "simple address no type string",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0},
expected: OSCMessage{
Address: "/hello",
Args: []OSCArg{},
},
},
{
description: "osc 1.0 spec example 1",
bytes: []byte{
47, 111, 115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52, 47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0, 44,
102, 0, 0, 67, 220, 0, 0,
},
expected: OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}},
},
{
description: "osc 1.0 spec example 2",
bytes: []byte{
47, 102, 111, 111, 0, 0, 0, 0, 44, 105, 105, 115, 102, 102, 0, 0, 0, 0, 3, 232, 255, 255, 255, 255, 104, 101, 108,
108, 111, 0, 0, 0, 63, 157, 243, 182, 64, 181, 178, 45,
},
expected: OSCMessage{
Address: "/foo",
Args: []OSCArg{
{Type: "i", Value: int32(1000)},
{Type: "i", Value: int32(-1)},
{Type: "s", Value: "hello"},
// thanks IEEE 754
{Type: "f", Value: float32(1.2339999675750732421875)},
{Type: "f", Value: float32(5.677999973297119140625)},
},
},
},
}
for _, testCase := range testCases {
actual, error := MessageFromBytes(testCase.bytes)
if error != nil {
fmt.Println(error)
t.Errorf("Test '%s' failed to encode properly", testCase.description)
}
if !reflect.DeepEqual(actual.Address, testCase.expected.Address) {
t.Errorf("Test '%s' failed to encode address properly", testCase.description)
fmt.Printf("expected: %v\n", testCase.expected.Address)
fmt.Printf("actual: %v\n", actual.Address)
}
if !reflect.DeepEqual(actual.Args, testCase.expected.Args) {
t.Errorf("Test '%s' failed to encode args properly", testCase.description)
fmt.Printf("expected: %v\n", testCase.expected.Args)
fmt.Printf("actual: %v\n", actual.Args)
}
}
}
+356
View File
@@ -0,0 +1,356 @@
package osc
// TODO(jwetzell): split things up
import (
"bytes"
"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 i := 0; i < padLength; i++ {
sb.WriteString("\u0000")
}
}
return []byte(sb.String())
}
func int32ToOSCBytes(number int32) []byte {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
}
return buf.Bytes()
}
func int64ToOSCBytes(number int64) []byte {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
}
return buf.Bytes()
}
func float32ToOSCBytes(number float32) []byte {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
}
return buf.Bytes()
}
func float64ToOSCBytes(number float64) []byte {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
}
return buf.Bytes()
}
func byteArrayToOSCBytes(bytes []byte) []byte {
oscBytes := []byte{}
bytesSize := len(bytes)
oscBytes = append(oscBytes, int32ToOSCBytes(int32(bytesSize))...)
oscBytes = append(oscBytes, bytes...)
padLength := 4 - (bytesSize % 4)
if padLength < 4 {
for i := 0; i < padLength; i++ {
oscBytes = append(oscBytes, 0)
}
}
return oscBytes
}
func timeTagToOSCBytes(timeTag OSCTimeTag) []byte {
timeTagBytes := int32ToOSCBytes(timeTag.seconds)
timeTagBytes = append(timeTagBytes, int32ToOSCBytes(timeTag.fractionalSeconds)...)
return timeTagBytes
}
func argsToBuffer(args []OSCArg) []byte {
//TODO(jwetzell): add error handling
var argBuffers = []byte{}
for _, arg := range args {
switch oscType := arg.Type; oscType {
case "s":
if value, ok := arg.Value.(string); ok {
argBuffers = append(argBuffers, stringToOSCBytes(value)...)
} else {
fmt.Println("OSC arg had string type but non-string value.")
}
case "i":
if value, ok := arg.Value.(int); ok {
argBuffers = append(argBuffers, int32ToOSCBytes(int32(value))...)
} else if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, int32ToOSCBytes(value)...)
} else {
fmt.Println("OSC arg had integer type but non-integer value.")
}
case "f":
if value, ok := arg.Value.(float32); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
} else if value, ok := arg.Value.(float64); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
} else if value, ok := arg.Value.(int); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
} else if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
} else if value, ok := arg.Value.(int64); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
} else {
fmt.Println("OSC arg had float type but non-float value.")
}
case "b":
if value, ok := arg.Value.([]byte); ok {
argBuffers = append(argBuffers, byteArrayToOSCBytes(value)...)
} else {
fmt.Println("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 {
colorBytes := []byte{color.r, color.g, color.b, color.a}
argBuffers = append(argBuffers, colorBytes...)
}
case "h":
if value, ok := arg.Value.(int); ok {
argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
} else if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
} else if value, ok := arg.Value.(int64); ok {
argBuffers = append(argBuffers, int64ToOSCBytes(value)...)
} else {
fmt.Println("OSC arg had integer type but non-integer value.")
}
case "d":
if value, ok := arg.Value.(float32); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
} else if value, ok := arg.Value.(float64); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
} else if value, ok := arg.Value.(int); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
} else if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
} else if value, ok := arg.Value.(int64); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
} else {
fmt.Println("OSC arg had float type but non-float value.")
}
default:
fmt.Printf("unhandled osc type: %s.\n", oscType)
}
}
return argBuffers
}
func readOSCString(bytes []byte) (string, []byte) {
//TODO(jwetzell): add error handling
oscString := ""
stringFinished := false
stringEndIndex := 0
remainingBytes := []byte{}
for index, byteIn := range bytes {
if !stringFinished {
if byteIn == 0 {
oscString = string(bytes[0:index])
stringEndIndex = index + 1
break
}
}
}
stringPadding := 4 - (stringEndIndex % 4)
if stringPadding < 4 {
stringEndIndex = stringEndIndex + stringPadding
}
remainingBytes = bytes[stringEndIndex:]
return oscString, remainingBytes
}
func readOSCInt32(bytes []byte) (int32, []byte, error) {
if len(bytes) < 4 {
return 0, bytes, errors.New("int data must be at least 4 bytes large")
}
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("int data must be at least 4 bytes large")
}
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("float data must be at least 4 bytes large")
}
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("float data must be at least 4 bytes large")
}
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("problem reading blob data size")
}
if len(remainingBytes) < int(blobLength) {
return []byte{}, bytes, errors.New("blob data specified a size larger than the remaining message data")
}
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("color data must be at least 4 bytes large")
}
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, err
}
fractionalSeconds, remainingBytes, err := readOSCInt32(bytesAfterSeconds)
if err != nil {
return OSCTimeTag{}, bytes, 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
remainingBytes := []byte{}
//TODO(jwetzell): add error handling
switch oscType {
case "s":
argString, bytesLeft := readOSCString(bytes)
oscArg.Value = argString
remainingBytes = bytesLeft
case "i":
argInt, bytesLeft, error := readOSCInt32(bytes)
if error != nil {
readArgError = error
}
oscArg.Value = argInt
remainingBytes = bytesLeft
case "f":
argFloat, bytesLeft, error := readOSCFloat32(bytes)
if error != nil {
readArgError = error
}
oscArg.Value = argFloat
remainingBytes = bytesLeft
case "b":
argBytes, bytesLeft, error := readOSCBlob(bytes)
if error != nil {
readArgError = error
}
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, error := readOSCColor(bytes)
if error != nil {
readArgError = error
}
oscArg.Value = argColor
remainingBytes = bytesLeft
case "h":
argInt, bytesLeft, error := readOSCInt64(bytes)
if error != nil {
readArgError = error
}
oscArg.Value = argInt
remainingBytes = bytesLeft
case "d":
argFloat, bytesLeft, error := readOSCFloat64(bytes)
if error != nil {
readArgError = error
}
oscArg.Value = argFloat
remainingBytes = bytesLeft
default:
fmt.Printf("unsupported osc type: %s\n", oscType)
readArgError = errors.New("unsupported osc type: " + oscType)
}
return oscArg, remainingBytes, readArgError
}
+32
View File
@@ -0,0 +1,32 @@
package osc
type OSCPacket interface {
ToBytes() []byte
}
type OSCBundle struct {
Contents []OSCPacket
TimeTag OSCTimeTag
}
type OSCArg struct {
Value any
Type string
}
type OSCMessage struct {
Address string
Args []OSCArg
}
type OSCColor struct {
r uint8
g uint8
b uint8
a uint8
}
type OSCTimeTag struct {
seconds int32
fractionalSeconds int32
}