defmodule OSCx do
@moduledoc """
This is the main OSCx module.
OSCx is an Elixir library for encoding and decoding Open Sound Control (OSC) messages and bundles.
OSC is a flexible protocol that can be used for a wide variety of real-time tasks, such as controlling multimedia devices.
Note that this library is deliberately minimilistic and no network transport or process logic is included.
## Concepts, messages and bundles
You can learn more about OSC and it's concepts and how to use this library by:
- accessing the [README file](README.md)
- taking the [Livebook tour](livebook/oscx_tour.livemd)
- browsing the simple [Examples](examples.md)
- reading the `OSCx.Message` or `OSCx.Bundle` module documentation
- reading about OSC [arguments and types](arguments_and_types.md)
### Example
To encode a basic message:
```
iex> my_message = %OSCx.Message{address: "/my_synth/volume", arguments: [0.25]}
%OSCx.Message{address: "/my_synth/volume", arguments: [0.25]}
iex> binary_message = OSCx.encode(my_message)
<<47, 109, 121, 95, 115, 121, 110, 116, 104, 47, 118, 111, 108, 117, 109, 101,
0, 0, 0, 0, 44, 102, 0, 0, 62, 128, 0, 0>>
```
To decode a binary OSC message:
```
iex> binary_message = <<47, 109, 121, 95, 115, 121, 110, 116, 104, 47, 118, 111, 108, 117, 109, 101, 0, 0, 0, 0, 44, 102, 0, 0, 62, 128, 0, 0>>
<<47, 109, 121, 95, 115, 121, 110, 116, 104, 47, 118, 111, 108, 117, 109, 101,
0, 0, 0, 0, 44, 102, 0, 0, 62, 128, 0, 0>>
iex> decoded_message = OSCx.decode(binary_message)
%OSCx.Message{address: "/my_synth/volume", arguments: [0.25]}
```
## TCP
If using TCP as the transport mechanism, append the `:tcp` atom to the encode function:
```
OSCx.encode(my_message_or_bundle, :tcp)
```
This frames the OSC packet with the message length in the header. This isn't required by UDP. See `encode/2` for more information.
"""
@doc """
Encodes an `%OSCx.Message{}` or `%OSCx.Bundle{}` struct as OSC binary data.
Takes as `%OSCx.Message{}` or `%OSCx.Bundle{}` as the first parameter.
## Example
```
# Encode a message
iex> %OSCx.Message{address: "/status", arguments: [1]} |> OSCx.encode()
<<47, 115, 116, 97, 116, 117, 115, 0, 44, 105, 0, 0, 0, 0, 0, 1>>
# Encode a bundle with a message
iex> OSCx.Bundle.new(
elements: [OSCx.Message.new()],
time: %{seconds: 1, fraction: 100}
)
|> OSCx.encode()
[
<<35, 98, 117, 110, 100, 108, 101, 0>>,
{116, <<0, 0, 0, 1, 0, 0, 0, 100>>},
[<<0, 0, 0, 8, 47, 0, 0, 0, 44, 0, 0, 0>>]
]
```
## TCP
If using TCP as the transport mechansim, pass the `:tcp` atom as the second parameter:
```
OSCx.encode(my_message_or_bundle, :tcp)
```
This will prefix the length (byte size) of the OSC binary data in 32-bits.
This is because in stream-based protocols such as TCP, the outgoing OSC packets are required to be framed using the packet's length in the header.
This isn't needed if using UDP.
"""
def encode(message_or_bundle, :tcp), do: encode(message_or_bundle) |> prefix_size_for_tcp()
@spec encode(map()) :: binary()
def encode(message_or_bundle) when is_struct(message_or_bundle, OSCx.Message), do: OSCx.Message.encode(message_or_bundle)
def encode(message_or_bundle) when is_struct(message_or_bundle, OSCx.Bundle), do: OSCx.Bundle.encode(message_or_bundle)
def encode(_message_or_bundle), do: raise("Not a Message or Bundle. Only %OSCx.Messages{} and %OSCx.Bundles{} can be encoded.")
@doc """
Decodes a binary OSC Message or Bundle.
## Example
```
iex> binary_msg = <<47, 115, 116, 97, 116, 117, 115, 0, 44, 105, 0, 0, 0, 0, 0, 1>>
<<47, 115, 116, 97, 116, 117, 115, 0, 44, 105, 0, 0, 0, 0, 0, 1>>
iex> binary_msg |> OSCx.decode()
%OSCx.Message{address: "/status", arguments: [0]}
```
"""
def decode(<<35, 98, 117, 110, 100, 108, 101, 0, _rest::binary>>=message_or_bundle) when is_binary(message_or_bundle), do: OSCx.Bundle.decode(message_or_bundle)
def decode(message_or_bundle) when is_binary(message_or_bundle), do: OSCx.Message.decode(message_or_bundle)
# Helper method for TCP, prepends the size of the data as a 32-bit value
defp prefix_size_for_tcp(encoded_data), do: OSCx.Encoder.prefix_size(encoded_data, 32)
end