commit 4e0eba7d4f7cbc6602bc4b4f76cca13bcf4c6d47 Author: Joel Wetzell Date: Sun Sep 29 14:05:53 2024 -0500 initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c32143f --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module sendosc + +go 1.23.1 + +require github.com/spf13/cobra v1.8.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..912390a --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sendosc.go b/sendosc.go new file mode 100644 index 0000000..9ed07bf --- /dev/null +++ b/sendosc.go @@ -0,0 +1,204 @@ +package main + +import ( + "bytes" + "encoding/binary" + "fmt" + "net" + "os" + "strconv" + "strings" + + "github.com/spf13/cobra" +) + +type OSCArg struct { + Type string + Value any +} + +type OSCMessage struct { + Address string + Args []OSCArg +} + +func stringToBuffer(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 integerToBuffer(number int32) []byte { + var buf bytes.Buffer + err := binary.Write(&buf, binary.BigEndian, number) + if err != nil { + fmt.Println("binary.Write failed:", err) + } + return buf.Bytes() +} + +func floatToBuffer(number float32) []byte { + var buf bytes.Buffer + err := binary.Write(&buf, binary.BigEndian, number) + if err != nil { + fmt.Println("binary.Write failed:", err) + } + return buf.Bytes() +} + +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, stringToBuffer(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, integerToBuffer(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, floatToBuffer(value)...) + } else { + fmt.Println("OSC arg had float type but non-float value.") + } + default: + fmt.Print("unhandled osc type: ") + fmt.Printf("%s.\n", oscType) + } + } + return argBuffers +} + +func messageToBuffer(message OSCMessage) []byte { + oscBuffer := []byte{} + + oscBuffer = append(oscBuffer, stringToBuffer(message.Address)...) + + var sb strings.Builder + + sb.WriteString(",") + + for _, arg := range message.Args { + sb.WriteString(arg.Type) + } + + oscBuffer = append(oscBuffer, stringToBuffer(sb.String())...) + oscBuffer = append(oscBuffer, argsToBuffer(message.Args)...) + + return oscBuffer +} + +func main() { + var Host string + var Port int32 + var Address string + var Args []string + var Types []string + + var rootCmd = &cobra.Command{ + Use: "sendosc", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(Host) + fmt.Println(Port) + fmt.Println(Address) + fmt.Println(Args) + send(Host, Port, Address, Args, Types) + }, + } + rootCmd.Flags().StringVar(&Host, "host", "", "host to send OSC message to") + rootCmd.Flags().Int32Var(&Port, "port", 9999, "port to send OSC message to") + rootCmd.Flags().StringVar(&Address, "address", "", "OSC address") + rootCmd.Flags().StringArrayVar(&Args, "arg", []string{}, "OSC args") + rootCmd.Flags().StringArrayVar(&Types, "type", []string{}, "OSC types") + rootCmd.MarkFlagRequired("host") + rootCmd.MarkFlagRequired("port") + rootCmd.MarkFlagRequired("address") + rootCmd.Execute() +} + +func argToTypedArg(rawArg string, oscType string) OSCArg { + switch oscType { + case "s": + return OSCArg{ + Type: "s", + Value: rawArg, + } + case "i": + number, err := strconv.ParseInt(rawArg, 10, 32) + if err != nil { + // ... handle error + panic(err) + } + return OSCArg{ + Type: "i", + Value: int32(number), + } + case "f": + number, err := strconv.ParseFloat(rawArg, 32) + if err != nil { + // ... handle error + panic(err) + } + return OSCArg{ + Type: "f", + Value: float32(number), + } + default: + fmt.Print("unhandled osc type: ") + fmt.Printf("%s.\n", oscType) + return OSCArg{} + } +} + +func send(host string, port int32, address string, args []string, types []string) { + + oscArgs := []OSCArg{} + + for index, arg := range args { + oscType := "s" + if len(types) > index { + oscType = types[index] + } + + oscArgs = append(oscArgs, argToTypedArg(arg, oscType)) + } + fmt.Println(oscArgs) + + oscMessage := OSCMessage{ + Address: address, + Args: oscArgs, + } + + oscMessageBuffer := messageToBuffer(oscMessage) + + netAddress := fmt.Sprintf("%s:%d", host, port) + conn, err := net.Dial("udp", netAddress) + if err != nil { + fmt.Printf("Dial err %v", err) + os.Exit(-1) + } + defer conn.Close() + + if _, err = conn.Write([]byte(oscMessageBuffer)); err != nil { + fmt.Printf("Write err %v", err) + os.Exit(-1) + } +}