Packages

A library to express RIFF format in pure Gleam-lang

Current section

Files

Jump to
glriff src glriff.erl
Raw

src/glriff.erl

-module(glriff).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glriff.gleam").
-export([to_bit_array/1, from_bit_array/1]).
-export_type([chunk/0, from_bit_array_error/0, to_chunk_list_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type chunk() :: {chunk, bitstring(), bitstring()} |
{list_chunk, list(chunk())} |
{riff_chunk, bitstring(), list(chunk())}.
-type from_bit_array_error() :: {failed_to_create_chunk_list,
to_chunk_list_error()} |
invalid_format.
-type to_chunk_list_error() :: invalid_id |
invalid_size |
invalid_data |
{size_is_difference, integer(), integer()}.
-file("src/glriff.gleam", 49).
?DOC(
" Converts a RIFF chunk to its binary representation.\n"
"\n"
" This function serializes a chunk into the RIFF binary format, which consists of:\n"
" - For basic Chunk: FourCC (4 bytes) + Size (4 bytes, little-endian) + Data\n"
" - For ListChunk: \"LIST\" (4 bytes) + Size (4 bytes) + Concatenated sub-chunks\n"
" - For RiffChunk: \"RIFF\" (4 bytes) + Size (4 bytes) + FourCC (4 bytes) + Concatenated sub-chunks\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let chunk = Chunk(four_cc: <<\"fmt \">>, data: <<\"EXAMPLE_DATA\">>)\n"
" let binary = to_bit_array(chunk)\n"
" // Returns: <<\"fmt \", 12:size(32)-little, \"EXAMPLE_DATA\">>\n"
" ```\n"
).
-spec to_bit_array(chunk()) -> bitstring().
to_bit_array(Chunk) ->
case Chunk of
{chunk, Four_cc, Data} ->
Size = <<(erlang:byte_size(Data)):32/little>>,
_pipe = [Four_cc, Size, Data],
gleam_stdlib:bit_array_concat(_pipe);
{list_chunk, Chunk_list} ->
Id = <<"LIST"/utf8>>,
Data@1 = begin
_pipe@1 = Chunk_list,
_pipe@3 = gleam@list:map(_pipe@1, fun(V) -> _pipe@2 = V,
to_bit_array(_pipe@2) end),
gleam_stdlib:bit_array_concat(_pipe@3)
end,
Size@1 = <<(erlang:byte_size(Data@1)):32/little>>,
_pipe@4 = [Id, Size@1, Data@1],
gleam_stdlib:bit_array_concat(_pipe@4);
{riff_chunk, Four_cc@1, Chunk_list@1} ->
Riff_header = <<"RIFF"/utf8>>,
Data@2 = begin
_pipe@5 = Chunk_list@1,
_pipe@7 = gleam@list:map(_pipe@5, fun(V@1) -> _pipe@6 = V@1,
to_bit_array(_pipe@6) end),
gleam_stdlib:bit_array_concat(_pipe@7)
end,
Size@2 = <<(erlang:byte_size(Data@2)):32/little>>,
_pipe@8 = [Riff_header, Size@2, Four_cc@1, Data@2],
gleam_stdlib:bit_array_concat(_pipe@8)
end.
-file("src/glriff.gleam", 222).
?DOC(
" Recursively parses a list of chunks from binary data starting at a given position.\n"
"\n"
" This internal helper function reads chunks sequentially from the binary data,\n"
" starting at the specified byte position. It continues until all data has been\n"
" consumed or an error occurs.\n"
"\n"
" Each chunk consists of:\n"
" - 4 bytes: FourCC identifier\n"
" - 4 bytes: Size (little-endian integer)\n"
" - N bytes: Data (where N = size from previous field)\n"
"\n"
" ## Parameters\n"
"\n"
" - `bits`: The binary data containing RIFF chunks\n"
" - `position`: The byte offset to start reading from\n"
"\n"
" ## Returns\n"
"\n"
" Returns `Ok(chunks)` with a list of successfully parsed chunks, or\n"
" `Error(reason)` if parsing fails.\n"
).
-spec to_chunk_list(bitstring(), integer()) -> {ok, list(chunk())} |
{error, to_chunk_list_error()}.
to_chunk_list(Bits, Position) ->
Total_size = erlang:byte_size(Bits),
case Position >= Total_size of
true ->
{ok, []};
false ->
gleam@result:'try'(
begin
_pipe = gleam_stdlib:bit_array_slice(Bits, Position, 4),
gleam@result:replace_error(_pipe, invalid_id)
end,
fun(Id) ->
gleam@result:'try'(
begin
_pipe@1 = gleam_stdlib:bit_array_slice(
Bits,
Position + 4,
4
),
gleam@result:replace_error(_pipe@1, invalid_size)
end,
fun(Size_bits) ->
Size@1 = case Size_bits of
<<Size:32/little>> -> Size;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"glriff"/utf8>>,
function => <<"to_chunk_list"/utf8>>,
line => 242,
value => _assert_fail,
start => 7654,
'end' => 7701,
pattern_start => 7665,
pattern_end => 7689})
end,
gleam@result:'try'(
begin
_pipe@2 = gleam_stdlib:bit_array_slice(
Bits,
Position + 8,
Size@1
),
gleam@result:replace_error(
_pipe@2,
invalid_data
)
end,
fun(Data) ->
gleam@bool:guard(
Size@1 /= erlang:byte_size(Data),
{error,
{size_is_difference,
Size@1,
erlang:byte_size(Data)}},
fun() ->
Next_position = (Position + 8) + Size@1,
Chunk = {chunk, Id, Data},
Rest_chunks = to_chunk_list(
Bits,
Next_position
),
case Rest_chunks of
{ok, Values} ->
{ok, [Chunk | Values]};
{error, Err} ->
{error, Err}
end
end
)
end
)
end
)
end
)
end.
-file("src/glriff.gleam", 120).
?DOC(
" Parses a RIFF chunk from binary data.\n"
"\n"
" This function reads binary data in RIFF format and constructs the appropriate\n"
" `Chunk` variant based on the chunk identifier:\n"
" - \"RIFF\" → `RiffChunk`\n"
" - \"LIST\" → `ListChunk`\n"
" - Other FourCC → basic `Chunk`\n"
"\n"
" The function validates that:\n"
" - The data contains at least 8 bytes (FourCC + size)\n"
" - The declared size matches the actual data size\n"
" - All sub-chunks (for RIFF and LIST) are valid\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let binary = <<\"fmt \", 12:size(32)-little, \"EXAMPLE_DATA\">>\n"
" case from_bit_array(binary) {\n"
" Ok(chunk) -> // Successfully parsed chunk\n"
" Error(InvalidFormat) -> // Invalid RIFF data\n"
" }\n"
" ```\n"
).
-spec from_bit_array(bitstring()) -> {ok, chunk()} |
{error, from_bit_array_error()}.
from_bit_array(Bits) ->
gleam@result:'try'(
begin
_pipe = gleam_stdlib:bit_array_slice(Bits, 0, 4),
gleam@result:replace_error(_pipe, invalid_format)
end,
fun(Id) ->
gleam@result:'try'(
begin
_pipe@1 = gleam_stdlib:bit_array_slice(Bits, 4, 4),
gleam@result:replace_error(_pipe@1, invalid_format)
end,
fun(Size_bits) ->
Size@1 = case Size_bits of
<<Size:32/little>> -> Size;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"glriff"/utf8>>,
function => <<"from_bit_array"/utf8>>,
line => 131,
value => _assert_fail,
start => 4439,
'end' => 4486,
pattern_start => 4450,
pattern_end => 4474})
end,
case Id of
<<"RIFF"/utf8>> ->
gleam@result:'try'(
begin
_pipe@2 = gleam_stdlib:bit_array_slice(
Bits,
8,
4
),
gleam@result:replace_error(
_pipe@2,
invalid_format
)
end,
fun(Four_cc) ->
Last_index = erlang:byte_size(Bits) - 12,
case Last_index of
0 ->
{ok, {riff_chunk, Four_cc, []}};
_ ->
gleam@result:'try'(
begin
_pipe@3 = to_chunk_list(
Bits,
12
),
gleam@result:map_error(
_pipe@3,
fun(Field@0) -> {failed_to_create_chunk_list, Field@0} end
)
end,
fun(Chunks) ->
{ok,
{riff_chunk,
Four_cc,
Chunks}}
end
)
end
end
);
<<"LIST"/utf8>> ->
Last_index@1 = erlang:byte_size(Bits) - 8,
case Last_index@1 of
0 ->
{ok, {list_chunk, []}};
_ ->
gleam@result:'try'(
begin
_pipe@4 = to_chunk_list(Bits, 8),
gleam@result:map_error(
_pipe@4,
fun(Field@0) -> {failed_to_create_chunk_list, Field@0} end
)
end,
fun(Chunks@1) ->
{ok, {list_chunk, Chunks@1}}
end
)
end;
_ ->
Last_index@2 = erlang:byte_size(Bits) - 8,
gleam@result:'try'(
begin
_pipe@5 = gleam_stdlib:bit_array_slice(
Bits,
8,
Last_index@2
),
gleam@result:replace_error(
_pipe@5,
invalid_format
)
end,
fun(Data) ->
case Size@1 =:= erlang:byte_size(Data) of
true ->
{ok, {chunk, Id, Data}};
false ->
{error, invalid_format}
end
end
)
end
end
)
end
).