View Source OSCx.Bundle (OSCx v0.1.0)

A module and struct for manipulating and representing OSC bundles.

The struct has two keys:

  • time: a map representing an OSC time tag, in the format %{time: _, fraction: _}
  • elements: an Elixir list of Messages or Bundles.

The two main functions are:

  • encode/1 which takes an %OSCx.Bundle{} struct and encodes it to the OSC bundle format
  • decode/1 which takes an OSC bundle recieved (e.g. via UDP) and decodes it into an %OSCx.Bundle{} struct.

About OSC bundles

Bundles are a way of grouping OSC messages and even other OSC bundles together, so they can be received by the OSC server simultaneously.

Structure of an OSC bundle

A bundle is made up of three parts:

  • Bundle identifer: which is the the string “#bundle”.
  • Time: a time tag which is a 64-bit time identifier. The first 32 bits specify the number of seconds since midnight on January 1, 1900, and the last 32 bits specify fractional parts of a second to a precision of about 200 picoseconds. This representation is used by Internet NTP timestamps.
  • Elements: the payload of the bundle, which can be any number of messages or bundles. Each of these are preceded by a 4-byte integer byte count.

OSC bundle diagram

Calling OSCx.encode/1 or OSCx.Bundle.encode/1 on an %OSCx.Bundle{} will create the OSC binary version matching the above.

Creating a bundle

To create a bundle, you only need to specify the time tag and add the elements (which are %OSCx.Messages{} or other %OSCx.Bundle{} structs) to be included within the bundle:

iex> my_bundle =
  %OSCx.Bundle{
    time: %{seconds: 1, fraction: 0},
    elements: [%OSCx.Message{address: "/", arguments: []}]
  }

Or you can use OSCx.Bundle.new/1 to build the struct.

Sending the bundle

The bundle is sent just like an OSCx.Message, for example, using UDP:

# IP or host and port number for the UDP connection
ip_address = '127.0.0.1' # This could be changed to named address, like 'localhost'
port_num = 8000 # In this example, this is the default port used by [Protokol](https://hexler.net/protokol)

# Open a port
{:ok, port} = :gen_udp.open(0, [:binary, {:active, true}])

# Encode the message
my_bundle =
  %OSCx.Bundle{
    time: %{seconds: 1, fraction: 0},
    elements: [
      %OSCx.Message{address: "/msg/one", arguments: [1]},
      %OSCx.Message{address: "/msg/two", arguments: [2]},
      %OSCx.Message{address: "/msg/three", arguments: [3]}
      ]
  } |> OSCx.encode()

# Send message
:gen_udp.send(port, ip_address, port_num, my_bundle)

Summary

Functions

Decodes an OSC binary bundle into an %OSCx.Bundle{} struct.

Encodes an %OSCx.Bundle{} struct to an OSC Bundle binary.

Convenience for creating a new %OSCx.Bundle{} struct.

Functions

Decodes an OSC binary bundle into an %OSCx.Bundle{} struct.

Example

iex> alias OSCx.{Bundle, Message}
[OSCx.Bundle, OSCx.Message]

iex> binary_osc_bundle =
  Bundle.new(
    time: %{seconds: 1, fraction: 1},
    elements: [
      Message.new(address: "/synth/play", arguments: [440.0]),
      Message.new(address: "/synth/stop", arguments: [440.0]),
      Bundle.new(
        time: %{seconds: 1, fraction: 2},
        elements: [ Message.new(address: "/inner_bundle/message", arguments: ["yes, they can be nested!"]) ]
      )
    ]
  ) |> OSCx.encode()
<<35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 20, 47,
115, 121, 110, 116, 104, 47, 112, 108, 97, 121, 0, 44, 102, 0, 0, 67, 220, 0,
0, 0, 0, 0, 20, 47, 115, 121, 110, 116, 104, ...>>

# Decode the binary OSC message above, back into a struct
iex> binary_osc_bundle |> OSCx.decode()
%OSCx.Bundle{
  time: %{seconds: 1, fraction: 1},
  elements: [
    %OSCx.Message{address: "/synth/play", arguments: [440.0]},
    %OSCx.Message{address: "/synth/stop", arguments: [440.0]},
    %OSCx.Bundle{
      time: %{seconds: 1, fraction: 2},
      elements: [
        %OSCx.Message{
          address: "/inner_bundle/message",
          arguments: ["yes, they can be nested!"]
        }
      ]
    }
  ]
}

Encodes an %OSCx.Bundle{} struct to an OSC Bundle binary.

Takes a %OSCx.Bundle{} struct as it's first parameter.

In practice, just use OSCx.encode/1 instead as it will encode Bundle or Message types.

Example

iex> my_bundle =
  Bundle.new(
    time: %{seconds: 1, fraction: 1},
    elements: [
      Message.new(address: "/synth/play", arguments: [440.0]),
      Message.new(address: "/synth/stop", arguments: [440.0])
    ]
  )
%OSCx.Bundle{
  time: %{seconds: 1, fraction: 1},
  elements: [
    %OSCx.Message{address: "/synth/play", arguments: [440.0]},
    %OSCx.Message{address: "/synth/stop", arguments: [440.0]}
  ]
}

iex> my_bundle |> OSCx.Bundle.encode()
<<35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 20, 47,
115, 121, 110, 116, 104, 47, 112, 108, 97, 121, 0, 44, 102, 0, 0, 67, 220, 0,
0, 0, 0, 0, 20, 47, 115, 121, 110, 116, 104, ...>>

Convenience for creating a new %OSCx.Bundle{} struct.

Optionally takes a keyword list with the following keys:

  • time: which can be used to set the OSC time tag map, e.g. %{seconds: 1, fraction: 1}
  • elements: which is a list of elements to include in the bundle. Elements can include zero, 1 or more %OSCx.Message{} or %OSCx.Bundle{} structs.

Example

Bundle.new(
    time: %{seconds: 1, fraction: 1},
    elements: [
      Message.new(address: "/synth/play", arguments: [440.0]),
      Message.new(address: "/synth/stop", arguments: [440.0]),
    ]
  )