View Source OSCx.Decoder (OSCx v0.1.0)

Helpers to decode an OSC message into Elixir.

This module contains helper functions that parse OSC binary data and converts them into equivalent Elixir types.

To decode the full OSC data, these functions are chained together and are used by OSCx.Message.decode/1 and OSCx.Bundle.decode/1.

Tip

Note that in practice you'll will likely not need to use this module directly and instead use OSCx.decode/1.

Automatically decoded types

Types with arguments

The following types can be automatically decoded:

Type tagUnicode numberTypeOSC spec version
i10532 bit integer1.0+ required
f10232 bit float1.0+ required
s115String1.0+ required
b98Blob1.0+ required
h10464-bit big-endian two’s complement integer1.0 non-standard
d10064 bit (“double”) IEEE 754 floating point number1.0 non-standard
m1094 byte MIDI message1.0 non-standard
t116OSC time tag1.1+ required
r14432 bit RGBA color1.0 non-standard
c99An ascii character, sent as 32 bits1.0 non-standard
S83Symbol1.0 non-standard
[ and ]91 and 93List1.0 non-standard

Symbols, Atoms and Strings

By default, Symbols are converted to Elixir atoms. This behaviour can be changed, see OSCx.Decoder.set_symbol_to_atom/1.

Special string tag types

The following string type tags are also automatically decoded via the tag/1 function:

Type tagUnicode numberTypeOSC spec version
T84True (tag only, no arguments)1.1+ required
F80False (tag only, no arguments)1.1+ required
N78Null (tag only, no arguments)1.1+ required
I73Impulse, also known as Infinitum in the OSC 1.0 Spec, or 'Bang' (tag only, no arguments)1.1+ required

More information

To learn more about how this library decodes and encodes data, see: Arguments and types.

Summary

Primary function

Decodes arguments from the binary.

Type functions

Extracts the address from the binary.

Extracts a blob (non-string binary sequence) from the binary.

Decodes a 32-bit char value.

Decodes a 32-bit float from the head of the binary.

Decodes a 64-bit float from the head of the binary.

Decodes a 32-bit integer from the head of the binary.

Decodes a 64-bit integer from the head of the binary.

Parses a list (or array as it is called in OSC).

Extracts a MIDI message from the head of the binary.

Extracts a MIDI message from the head of the binary.

Decodes 'special' string tag types without arguments, such as: True, False, Null and Impulse.

Extracts a string from the binary.

Extracts a symbols from the binary.

Extracts the list of tags.

Extracts a time-tag from the head of the binary.

Helper functions

Removes padding prepended to remaining binary data.

Generates a list showing each 'row' of the encoded OSC message.

Sets the default behaviour when decoding of OSC Symbol types.

Parses a tag string for special tag types.

Primary function

Link to this function

decode_arg(tag_list, arg_data, acc \\ [])

View Source

Decodes arguments from the binary.

Currently this automatically decodes the following argument types:

Type tagUnicode numberTypeOSC spec version
i10532 bit integer1.0+ required
f10232 bit float1.0+ required
s115String1.0+ required
b98Blob1.0+ required
h10464-bit big-endian two’s complement integer1.0 non-standard
d10064 bit (“double”) IEEE 754 floating point number1.0 non-standard
m1094 byte MIDI message1.0 non-standard
c99An ascii character, sent as 32 bits1.0 non-standard
t116OSC time tag1.1+ required
r14432 bit RGBA color1.0 non-standard
S83Symbol1.0 non-standard
[ and ]91 and 93List1.0 non-standard

This function takes the following parameters:

  • List (charlist) of tag types (e.g. ?s for string, ?i for integer, etc)
  • Binary data, with the address and tag type string removed so that only argument data remains

Returns a tuple with the first element containing the decoded data, and the second element any remaining data. The second element will be an empty binary if all data was successfully decoded to the end.

Type functions

Extracts the address from the binary.

This function assumes the OSC binary is in the following format:

<OSC address> + <OSC tag type string with padding> + <OSC arguments>

It returns a tuple with the first element containing the OSC address, and the second the remaining OSC data which will be the tag type string followed by OSC arguments.

Extracts a blob (non-string binary sequence) from the binary.

This function assumes that the binary starts with the blob type.

Any additional padding added to the blob is removed.

Returns a tuple with the:

  • first element: the blob value
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.

This is similar to the OSCx.Decoder.string/2 function.

Decodes a 32-bit char value.

Returns it as an Elixir charlist, e.g.:application

# This is the char 'a' in 32-bit
iex> binary = <<0, 0, 0, 97>>
<<0, 0, 0, 97>>

iex> {char, _rem_bin, _rem_tags} = OSCx.Decoder.char(binary, [])
{~c"a", "", []}

iex> char
~c"a"
Link to this function

float(binary, rest_tags)

View Source

Decodes a 32-bit float from the head of the binary.

Returns a tuple with the first element the float value, and the second element the remaining binary data.

Returns a tuple with the:

  • first element: the float value
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.
Link to this function

float_64bit(binary, rest_tags)

View Source

Decodes a 64-bit float from the head of the binary.

Returns a tuple with the first element the float value, and the second element the remaining binary data.

Returns a tuple with the:

  • first element: the float value
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.
Link to this function

integer(binary, rest_tags)

View Source

Decodes a 32-bit integer from the head of the binary.

Returns a tuple with the:

  • first element: the integer value
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.
Link to this function

integer_64bit(binary, rest_tags)

View Source

Decodes a 64-bit integer from the head of the binary.

Returns a tuple with the:

  • first element: the integer value
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.
Link to this function

list(binary, type_tag_string)

View Source

Parses a list (or array as it is called in OSC).

Returns a tuple with the:

  • first element: a list containing the decoded array data
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.

Extracts a MIDI message from the head of the binary.

This function assumes that the binary starts with a MIDI message.

Returns a tuple with the:

  • first element: a map with a 4-byte MIDI value, in the format: %{midi: value}
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.

Extracts a MIDI message from the head of the binary.

This function assumes that the binary starts with a MIDI message.

Returns a tuple with the:

  • first element: a map with a 4-byte MIDI value, in the format: %{midi: value}
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.
Link to this function

special_tag(arg_data, rest_tags, tag_value)

View Source

Decodes 'special' string tag types without arguments, such as: True, False, Null and Impulse.

This function is used by decode_arg/3 as part of the decoding process. It takes the following parameters:

  • argument data (binary)
  • charlist of remaing tags to process
  • the value to return (e.g. true for ?T)

OSC specifies a number of tags which don't carry arguments. These are:

Type tagUnicode numberOSC TypeOSC spec versionElixir value
T84True (tag only, no arguments)1.1+ requiredtrue
F80False (tag only, no arguments)1.1+ requiredfalse
N78Null (tag only, no arguments)1.1+ requirednil
I73Impulse, also known as Infinitum in the OSC 1.0 Spec, or 'Bang' (tag only, no arguments)1.1+ required:impulse

Returns a tuple with the:

  • first element: the decoded value as an Elixir type
  • second element: the remaining binary data (unlike other functions, this leaves the binary data unchanged)
  • third element: the rest of the type tags to be processed (this is also left unchanged).
Link to this function

string(binary, rest_tags)

View Source

Extracts a string from the binary.

This function assumes that the binary starts with a string type.

Any additional padding added to the string is removed.

Returns a tuple with the:

  • first element: the string value
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.

This is similar to the OSCx.Decoder.blob/2 function.

Example

iex> bin_string = <<72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 0, 0, 0>>
<<72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 0, 0, 0>>

iex> OSCx.Decoder.string(bin_string)
{"Hello, world!", ""}
Link to this function

symbol(binary, rest_tags)

View Source

Extracts a symbols from the binary.

In the OSC 1.0 Specification, this is considered an alternate OSC-string representation, used for example in systems that differentiate “symbols” from “strings”.

## This function assumes that the binary starts with a symbol type.

Any additional padding added to the symbol is removed.

Returns a tuple with the:

  • first element: the symbol as an atom (or string if default behaviour is changed, see below)
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.

This is similar to the OSCx.Decoder.string/2 function.

Symbols become :atoms by default

In this library, Elixir's atom type is considered an OSC Symbol type.

Uncontrolled creation of atoms is not considered good pratice on the BEAM. If there are potentially a lot of Symbols being decoded, consider decoding to a String instead.

This default behaviour can be changed by calling OSCx.Decoder.set_symbol_to_atom(false) or setting the application environment Application.get_env(:OSCx, :symbol_to_atom, false).

For more information, see OSCx.Decoder.set_symbol_to_atom/1.

Extracts the list of tags.

Takes the binary data from the message. This assumes that the address has been removed from the start of the OSC message, and only the tag type string and arguments are remaining:

<OSC address> + <OSC tag type string with padding> + <OSC arguments>

Returns a tuple with the first element containing the tags (with the leading comma removed), and the second element the remaining binary data with tag type string and it's padding removed.

Extracts a time-tag from the head of the binary.

This function assumes that the binary starts with a time-tag.

Returns a tuple with the:

  • first element: the map value for a time tag (see below)
  • second element: the remaining binary data
  • third element: the rest of the type tags to be processed.

The time tag map is in the format:

%{time: value, seconds: seconds, fraction: fraction}

Where:

  • time is the full 64-bit integer time code
  • seconds is the number of seconds since midnight on January 1, 1900 (32-bit integer)
  • fraction is the fractional part of a second to a precision of about 200 picoseconds (32-bit integer).

The 32-bit :seconds and :fraction have been extracted from the 64-bit :time value, so you can pattern match on whatever is most useful in the context of your application.

Helper functions

Link to this function

de_pad(string_or_blob_or_size, bin_data)

View Source

Removes padding prepended to remaining binary data.

OSC messages are encoded in chunks of 4 bytes. When arbitary data, such as a string or blob is less that 4 bytes, padding using a value of <<0>> is added.

This function takes the following parameters:

  • String or Blob (both are binary data of an arbitary length) OR byte size of the string or blob
  • Remaning binary data once the String or Blob has been extracted

Returns the remaing binary data with any leading padding removed.

Example

String

iex> bin_string = <<72, 101, 108, 108, 111, 0, 0, 0, 119, 111, 114, 108, 100, 33, 0, 0>>
<<72, 101, 108, 108, 111, 0, 0, 0, 119, 111, 114, 108, 100, 33, 0, 0>>

iex> [string, rest] = :binary.split(bin_string, <<0>>)
["Hello", <<0, 0, 119, 111, 114, 108, 100, 33, 0, 0>>]

# Leading padding will be removed, based on the previous strings size
iex> OSCx.Decoder.de_pad(string, rest)
<<119, 111, 114, 108, 100, 33, 0, 0>>

Byte size

iex> bin_string = <<72, 101, 108, 108, 111, 0, 0, 0, 119, 111, 114, 108, 100, 33, 0, 0>>
<<72, 101, 108, 108, 111, 0, 0, 0, 119, 111, 114, 108, 100, 33, 0, 0>>

iex> [string, rest] = :binary.split(bin_string, <<0>>)
["Hello", <<0, 0, 119, 111, 114, 108, 100, 33, 0, 0>>]

# Set byte size to 5 ("Hello" has byte size of 5). Any leading padding will be removed based on this size.
iex> OSCx.Decoder.de_pad(5, rest)
<<119, 111, 114, 108, 100, 33, 0, 0>>

Generates a list showing each 'row' of the encoded OSC message.

This maybe useful in debugging OSC binary messages.

The OSCx.Decoder.inspect() shows:

  • the character code of each byte
  • its printable utf8 value
  • underscore (_) is used to denote either a 0 value or padding
  • (D) is a non-printable data value

Example

iex> encoded_msg =
  %OSCx.Message{address: "/target/address", arguments: [1, 2.0, "string data"]}
  |> OSCx.encode()

# Inspect the message to see how it is encoded
iex> encoded_msg |> OSCx.Decoder.inspect()
[
  {0, ["47 '/'", "116 't'", "97 'a'", "114 'r'"]},
  {1, ["103 'g'", "101 'e'", "116 't'", "47 '/'"]},
  {2, ["97 'a'", "100 'd'", "100 'd'", "114 'r'"]},
  {3, ["101 'e'", "115 's'", "115 's'", "0 (_)"]},
  {4, ["44 ','", "105 'i'", "102 'f'", "115 's'"]},
  {5, ["0 (_)", "0 (_)", "0 (_)", "0 (_)"]},
  {6, ["0 (_)", "0 (_)", "0 (_)", "1 (D)"]},
  {7, ["64 '@'", "0 (_)", "0 (_)", "0 (_)"]},
  {8, ["115 's'", "116 't'", "114 'r'", "105 'i'"]},
  {9, ["110 'n'", "103 'g'", "32 ' '", "100 'd'"]},
  {10, ["97 'a'", "116 't'", "97 'a'", "0 (_)"]}
]
Link to this function

set_symbol_to_atom(true_or_false \\ true)

View Source

Sets the default behaviour when decoding of OSC Symbol types.

Takes as its first parameter a boolean representing the following behaviour:

  • true: OSC Symbols are converted to Elixir atoms. This is the default behaviour.
  • false: OSC Symbols are converted to Elixir strings.

This function changes the :OSCx, :symbol_to_atom application evironment, e.g.: Application.put_env(:OSCx, :symbol_to_atom, false).

Why this option?

Uncontrolled dynamic creation of atoms on the BEAM may not be considered good practice. Atoms are not garbage collected by the Erlang Virtual Machine, so they live in memory during a software's entire execution lifetime. Although there is a high-limit on the number of atoms that can be created on the BEAM, for situations where there may be a large variety of atoms, converting to Elixir's string type may be preferrable. For more information on this see Code-related anti-patterns.

Parses a tag string for special tag types.

Takes a type tag string as the first parameter, with or without the leading comma.

Parses a tag string to check if any of the following special types are present:

OSC string tag typeOSC characterElixir type
TrueTtrue
FalseFfalse
NullNnil
Impulse or Infinitum or 'Bang'I:impulse

Return type is a list of zero or more of the above.

Example

# Single 'true' type present
iex> Decoder.tag(",T")
[true]

# Single 'false' type present
iex> Decoder.tag(",F")
[false]

# Single 'null' type present, will return nil in Elixir
iex> Decoder.tag(",N")
[nil]

# Multiple special types given.
iex> Decoder.tag(",FTNI")
[false, true, nil, :impulse]

 # No special types present, other tags ignored. Empty list is returned.
iex> Decoder.tag(",ifs")
[]