Current section
Files
Jump to
Current section
Files
lib/ex_midi/midi_syx.ex
defmodule ExMidi.MidiSyx do
import Bitwise
@moduledoc """
Read and write SYX (System Exclusive) file format.
Handles both binary and plain text (hex) formats.
"""
@midi :midi
@doc "Read sysex messages from a SYX file."
def read_file(path) do
case File.read(path) do
{:ok, data} ->
if byte_size(data) == 0 do
[]
else
parser = ExMidi.MidiParser.new()
parser =
if binary_part(data, 0, 1) == <<0xF0>> do
# Binary format — feed all bytes
ExMidi.MidiParser.feed_bytes(parser, :binary.bin_to_list(data))
else
# Text format — parse hex
text = to_string(data)
hex_string = String.replace(text, ~r/\s/, "")
bytes =
for <<h::binary-size(2) <- hex_string>> do
{val, _} = Integer.parse(h, 16)
val
end
ExMidi.MidiParser.feed_bytes(parser, bytes)
end
{messages, _} = ExMidi.MidiParser.flush(parser)
for {@midi, {:sys_ex, _}} = msg <- messages, do: msg
end
{:error, reason} ->
{:error, reason}
end
end
@doc "Write sysex messages to a SYX file."
def write_file(path, messages, opts \\ []) do
plaintext = Keyword.get(opts, :plaintext, false)
sysex_data = for {@midi, {:sys_ex, data}} <- messages, do: data
if plaintext do
content =
Enum.map_join(sysex_data, "\n", fn data ->
hex = data |> Enum.map_join(" ", &String.pad_leading(Integer.to_string(&1, 16), 2, "0"))
"F0 #{hex} F7"
end)
File.write(path, content <> "\n")
else
# Binary format: wrap each sysex data with F0...F7
content =
Enum.map(sysex_data, fn data ->
Enum.reduce(data, <<0xF0>>, fn b, acc -> acc <> <<b &&& 0xFF>> end) <> <<0xF7>>
end)
File.write(path, content)
end
end
end