circuits_uart_midi_framing/lib/circuits/uart/framing/midi/buffer.ex
2020-05-04 21:26:45 +12:00

100 lines
3 KiB
Elixir

defmodule Circuits.UART.Framing.MIDI.Buffer do
alias __MODULE__
defstruct buffer: <<>>
@type t :: %Buffer{buffer: binary}
@moduledoc """
Implements a buffer for incoming serial data.
"""
@doc """
Initialise a new empty buffer.
"""
@spec init :: Buffer.t()
def init, do: %Buffer{}
@doc """
Consume as much of the buffer as possible, splitting it into messages.
"""
def get_packets(%Buffer{buffer: buffer}) do
with {:ok, buffer} <- drop_leading_data_bytes(buffer),
{:ok, messages, buffer} <- consume_messages([], buffer) do
{:ok, messages, %Buffer{buffer: buffer}}
else
:error -> {:ok, [], %Buffer{buffer: buffer}}
end
end
@doc """
Apend new data onto the end of the buffer.
"""
@spec append(Buffer.t(), binary) :: Buffer.t()
def append(%Buffer{buffer: buffer}, data) when is_binary(data),
do: %Buffer{buffer: buffer <> data}
@doc """
Indicates whether the buffer is empty or not.
"""
@spec empty?(Buffer.t()) :: boolean
def empty?(%Buffer{buffer: <<>>}), do: true
def empty?(%Buffer{}), do: false
defp consume_status_byte(<<1::integer-size(1), byte::bitstring-size(7), rest::binary>>),
do: {:ok, <<1::integer-size(1), byte::bitstring-size(7)>>, rest}
defp consume_status_byte(data), do: {:error, data}
defp consume_data_byte(<<0::integer-size(1), byte::bitstring-size(7), rest::binary>>),
do: {:ok, <<0::integer-size(1), byte::bitstring-size(7)>>, rest}
defp consume_data_byte(data), do: {:error, data}
defp drop_leading_data_bytes(data) when is_binary(data) do
data
|> consume_data_byte()
|> drop_leading_data_bytes()
end
defp drop_leading_data_bytes({:ok, _byte, <<>>}), do: :error
defp drop_leading_data_bytes({:ok, _byte, data}) do
data
|> consume_data_byte()
|> drop_leading_data_bytes()
end
defp drop_leading_data_bytes({:error, data}), do: {:ok, data}
defp consume_data_bytes(bytes, <<>>), do: {:ok, bytes, <<>>}
defp consume_data_bytes(bytes, data) do
case consume_data_byte(data) do
{:ok, byte, rest} -> consume_data_bytes(bytes <> byte, rest)
{:error, rest} -> {:ok, bytes, rest}
end
end
defp consume_messages(messages, <<>>), do: {:ok, Enum.reverse(messages), <<>>}
defp consume_messages(messages, data) when is_binary(data) do
case consume_message(data) do
{:ok, message, rest} -> consume_messages([message | messages], rest)
{:error, rest} -> {:ok, messages, rest}
end
end
defp consume_message(data) when is_binary(data) do
case consume_status_byte(data) do
{:ok, <<0xF0>>, rest} -> consume_sysex_payload(<<0xF0>>, rest)
{:ok, status_byte, rest} -> consume_data_bytes(status_byte, rest)
{:error, rest} -> {:error, rest}
end
end
defp consume_sysex_payload(payload, <<0xF7, rest::binary>>), do: {:ok, payload, rest}
defp consume_sysex_payload(payload, <<>>), do: {:ok, <<>>, payload}
defp consume_sysex_payload(payload, <<byte::binary-size(1), rest::binary>>),
do: consume_sysex_payload(payload <> byte, rest)
end