Packages

A library to control .wav file in pure Gleam-lang

Current section

Files

Jump to
glwav src glwav.erl
Raw

src/glwav.erl

-module(glwav).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glwav.gleam").
-export([from_bit_array/1, to_bit_array/1]).
-export_type([wave/0, bits/0, from_bit_array_error/0, format_code/0, chunk_data/0, read_chunk_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 wave() :: {wave,
format_code(),
integer(),
integer(),
bits(),
list(float())}.
-type bits() :: u8 | i16 | i24 | f32.
-type from_bit_array_error() :: {riff_format_error,
glriff:from_bit_array_error()} |
invalid_format.
-type format_code() :: p_c_m | ieee_float | alaw | mulaw | extensible.
-type chunk_data() :: {fmt, format_code(), integer(), integer(), bits()} |
{data, bitstring()}.
-type read_chunk_error() :: not_supported |
invalid_format_code |
invalid_channels |
invalid_sample_rate |
invalid_bytes_per_second |
invalid_block_align |
invalid_bits.
-file("src/glwav.gleam", 185).
-spec convert_format_code(bitstring()) -> {ok, format_code()} |
{error, read_chunk_error()}.
convert_format_code(Bits) ->
case Bits of
<<1:16/little>> ->
{ok, p_c_m};
_ ->
{error, not_supported}
end.
-file("src/glwav.gleam", 192).
-spec convert_channels(bitstring()) -> {ok, integer()} |
{error, read_chunk_error()}.
convert_channels(Bits) ->
case Bits of
<<Val:16/little>> ->
{ok, Val};
_ ->
{error, invalid_channels}
end.
-file("src/glwav.gleam", 199).
-spec convert_sample_rate(bitstring()) -> {ok, integer()} |
{error, read_chunk_error()}.
convert_sample_rate(Bits) ->
case Bits of
<<Val:32/little>> ->
{ok, Val};
_ ->
{error, invalid_sample_rate}
end.
-file("src/glwav.gleam", 206).
-spec convert_bits(bitstring()) -> {ok, bits()} | {error, read_chunk_error()}.
convert_bits(Bits) ->
case Bits of
<<Val:16/little>> ->
case Val of
8 ->
{ok, u8};
16 ->
{ok, i16};
24 ->
{ok, i24};
32 ->
{ok, f32};
_ ->
{error, invalid_bits}
end;
_ ->
{error, invalid_bits}
end.
-file("src/glwav.gleam", 155).
-spec read_fmt_chunk(bitstring()) -> {ok, chunk_data()} |
{error, read_chunk_error()}.
read_fmt_chunk(Data) ->
gleam@result:'try'(
begin
_pipe = Data,
_pipe@1 = gleam_stdlib:bit_array_slice(_pipe, 0, 2),
gleam@result:replace_error(_pipe@1, invalid_format_code)
end,
fun(Format_code_bits) ->
gleam@result:'try'(
begin
_pipe@2 = Data,
_pipe@3 = gleam_stdlib:bit_array_slice(_pipe@2, 2, 2),
gleam@result:replace_error(_pipe@3, invalid_channels)
end,
fun(Channels_bits) ->
gleam@result:'try'(
begin
_pipe@4 = Data,
_pipe@5 = gleam_stdlib:bit_array_slice(
_pipe@4,
4,
4
),
gleam@result:replace_error(
_pipe@5,
invalid_sample_rate
)
end,
fun(Sample_rate_bits) ->
gleam@result:'try'(
begin
_pipe@6 = Data,
_pipe@7 = gleam_stdlib:bit_array_slice(
_pipe@6,
14,
2
),
gleam@result:replace_error(
_pipe@7,
invalid_bits
)
end,
fun(Bits_bits) ->
gleam@result:'try'(
begin
_pipe@8 = Format_code_bits,
convert_format_code(_pipe@8)
end,
fun(Format_code) ->
gleam@result:'try'(
begin
_pipe@9 = Channels_bits,
convert_channels(_pipe@9)
end,
fun(Channels) ->
gleam@result:'try'(
begin
_pipe@10 = Sample_rate_bits,
convert_sample_rate(
_pipe@10
)
end,
fun(Sample_rate) ->
gleam@result:'try'(
begin
_pipe@11 = Bits_bits,
convert_bits(
_pipe@11
)
end,
fun(Bits) ->
{ok,
{fmt,
Format_code,
Sample_rate,
Channels,
Bits}}
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/glwav.gleam", 220).
-spec read_data_chunk(bitstring()) -> {ok, chunk_data()} |
{error, read_chunk_error()}.
read_data_chunk(Data) ->
{ok, {data, Data}}.
-file("src/glwav.gleam", 228).
-spec do_parse_u8_samples(bitstring(), list(float())) -> list(float()).
do_parse_u8_samples(Data, Acc) ->
case Data of
<<Val:8/unsigned, Rest/bitstring>> ->
Normalized = (erlang:float(Val) - 128.0) / 128.0,
do_parse_u8_samples(Rest, [Normalized | Acc]);
_ ->
lists:reverse(Acc)
end.
-file("src/glwav.gleam", 224).
-spec parse_u8_samples(bitstring()) -> list(float()).
parse_u8_samples(Data) ->
do_parse_u8_samples(Data, []).
-file("src/glwav.gleam", 242).
-spec do_parse_i16_samples(bitstring(), list(float())) -> list(float()).
do_parse_i16_samples(Data, Acc) ->
case Data of
<<Val:16/signed-little, Rest/bitstring>> ->
Normalized = erlang:float(Val) / 32768.0,
do_parse_i16_samples(Rest, [Normalized | Acc]);
_ ->
lists:reverse(Acc)
end.
-file("src/glwav.gleam", 238).
-spec parse_i16_samples(bitstring()) -> list(float()).
parse_i16_samples(Data) ->
do_parse_i16_samples(Data, []).
-file("src/glwav.gleam", 256).
-spec do_parse_i24_samples(bitstring(), list(float())) -> list(float()).
do_parse_i24_samples(Data, Acc) ->
case Data of
<<Val:24/signed-little, Rest/bitstring>> ->
Normalized = erlang:float(Val) / 8388608.0,
do_parse_i24_samples(Rest, [Normalized | Acc]);
_ ->
lists:reverse(Acc)
end.
-file("src/glwav.gleam", 252).
-spec parse_i24_samples(bitstring()) -> list(float()).
parse_i24_samples(Data) ->
do_parse_i24_samples(Data, []).
-file("src/glwav.gleam", 270).
-spec do_parse_f32_samples(bitstring(), list(float())) -> list(float()).
do_parse_f32_samples(Data, Acc) ->
case Data of
<<Val:32/float-little, Rest/bitstring>> ->
do_parse_f32_samples(Rest, [Val | Acc]);
_ ->
lists:reverse(Acc)
end.
-file("src/glwav.gleam", 266).
-spec parse_f32_samples(bitstring()) -> list(float()).
parse_f32_samples(Data) ->
do_parse_f32_samples(Data, []).
-file("src/glwav.gleam", 98).
?DOC(
" Parse a WAV file from a bit array.\n"
"\n"
" Reads a WAV file in RIFF format and extracts all audio properties\n"
" and samples. Samples are normalized to floating point values in the\n"
" range -1.0 to 1.0.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let assert Ok(bits) = simplifile.read_bits(\"audio.wav\")\n"
" let assert Ok(wave) = glwav.from_bit_array(bits)\n"
" // Access wave properties\n"
" wave.sample_rate // e.g., 44100\n"
" wave.channels // e.g., 2\n"
" wave.samples // List of normalized samples\n"
" ```\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(Wave)` if parsing succeeds\n"
" - `Error(FromBitArrayError)` if the file is invalid or unsupported\n"
).
-spec from_bit_array(bitstring()) -> {ok, wave()} |
{error, from_bit_array_error()}.
from_bit_array(Bits) ->
gleam@result:'try'(
begin
_pipe = Bits,
_pipe@1 = glriff:from_bit_array(_pipe),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {riff_format_error, Field@0} end
)
end,
fun(Riff_chunk) -> case Riff_chunk of
{riff_chunk, _, Chunks} ->
Chunk_data = begin
_pipe@2 = Chunks,
gleam@list:map(_pipe@2, fun(Chunk) -> case Chunk of
{chunk, <<"fmt "/utf8>>, Data} ->
read_fmt_chunk(Data);
{chunk, <<"data"/utf8>>, Data@1} ->
read_data_chunk(Data@1);
_ ->
{error, not_supported}
end end)
end,
case Chunk_data of
[{ok, {fmt, Format_code, Sample_rate, Channels, Bits@1}},
{ok, {data, Data_bits}} |
_] ->
Samples = case Bits@1 of
u8 ->
parse_u8_samples(Data_bits);
i16 ->
parse_i16_samples(Data_bits);
i24 ->
parse_i24_samples(Data_bits);
f32 ->
parse_f32_samples(Data_bits)
end,
{ok,
{wave,
Format_code,
Sample_rate,
Channels,
Bits@1,
Samples}};
_ ->
{error, invalid_format}
end;
_ ->
{error, invalid_format}
end end
).
-file("src/glwav.gleam", 409).
-spec samples_to_f32(list(float())) -> bitstring().
samples_to_f32(Samples) ->
_pipe = Samples,
gleam@list:fold(
_pipe,
<<>>,
fun(Acc, Sample) -> <<Acc/bitstring, Sample:32/float-little>> end
).
-file("src/glwav.gleam", 416).
-spec float_to_int(float()) -> integer().
float_to_int(F) ->
erlang:round(F).
-file("src/glwav.gleam", 369).
-spec samples_to_u8(list(float())) -> bitstring().
samples_to_u8(Samples) ->
_pipe = Samples,
gleam@list:fold(
_pipe,
<<>>,
fun(Acc, Sample) ->
Value = (Sample * 128.0) + 128.0,
Int_value = case Value of
V when V < +0.0 ->
0;
V@1 when V@1 > 255.0 ->
255;
V@2 ->
float_to_int(V@2)
end,
<<Acc/bitstring, Int_value:8>>
end
).
-file("src/glwav.gleam", 383).
-spec samples_to_i16(list(float())) -> bitstring().
samples_to_i16(Samples) ->
_pipe = Samples,
gleam@list:fold(
_pipe,
<<>>,
fun(Acc, Sample) ->
Value = Sample * 32768.0,
Int_value = case Value of
V when V < -32768.0 ->
-32768;
V@1 when V@1 > 32767.0 ->
32767;
V@2 ->
float_to_int(V@2)
end,
<<Acc/bitstring, Int_value:16/little>>
end
).
-file("src/glwav.gleam", 396).
-spec samples_to_i24(list(float())) -> bitstring().
samples_to_i24(Samples) ->
_pipe = Samples,
gleam@list:fold(
_pipe,
<<>>,
fun(Acc, Sample) ->
Value = Sample * 8388608.0,
Int_value = case Value of
V when V < -8388608.0 ->
-8388608;
V@1 when V@1 > 8388607.0 ->
8388607;
V@2 ->
float_to_int(V@2)
end,
<<Acc/bitstring, Int_value:24/little>>
end
).
-file("src/glwav.gleam", 307).
?DOC(
" Convert a Wave structure to a bit array in WAV format.\n"
"\n"
" Creates a complete WAV file including RIFF header, fmt chunk, and data chunk.\n"
" Samples are converted from normalized floating point values (-1.0 to 1.0)\n"
" to the appropriate bit depth specified in the Wave structure.\n"
"\n"
" The `bytes_per_second` and `block_align` values are calculated automatically:\n"
" - `block_align` = `channels * (bits_per_sample / 8)`\n"
" - `bytes_per_second` = `sample_rate * block_align`\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let wave = glwav.Wave(\n"
" format_code: glwav.PCM,\n"
" sample_rate: 44_100,\n"
" channels: 1,\n"
" bits: glwav.I16,\n"
" samples: [0.0, 0.5, 1.0, 0.5, 0.0, -0.5, -1.0],\n"
" )\n"
" let bits = glwav.to_bit_array(wave)\n"
" // Write to file\n"
" simplifile.write_bits(bits, \"output.wav\")\n"
" ```\n"
"\n"
" ## Returns\n"
"\n"
" A bit array containing the complete WAV file data\n"
).
-spec to_bit_array(wave()) -> bitstring().
to_bit_array(Wave) ->
Bits_per_sample = case erlang:element(5, Wave) of
u8 ->
8;
i16 ->
16;
i24 ->
24;
f32 ->
32
end,
Block_align = (erlang:element(4, Wave) * Bits_per_sample) div 8,
Bytes_per_second = erlang:element(3, Wave) * Block_align,
Data_bits = case erlang:element(5, Wave) of
u8 ->
samples_to_u8(erlang:element(6, Wave));
i16 ->
samples_to_i16(erlang:element(6, Wave));
i24 ->
samples_to_i24(erlang:element(6, Wave));
f32 ->
samples_to_f32(erlang:element(6, Wave))
end,
Format_code_bits = case erlang:element(2, Wave) of
p_c_m ->
<<1:16/little>>;
ieee_float ->
<<3:16/little>>;
alaw ->
<<6:16/little>>;
mulaw ->
<<7:16/little>>;
extensible ->
<<65534:16/little>>
end,
Fmt_data = <<Format_code_bits/bitstring,
(erlang:element(4, Wave)):16/little,
(erlang:element(3, Wave)):32/little,
Bytes_per_second:32/little,
Block_align:16/little,
Bits_per_sample:16/little>>,
Fmt_chunk = <<"fmt "/utf8, 16:32/little, Fmt_data/bitstring>>,
Data_size = erlang:byte_size(Data_bits),
Data_chunk = <<"data"/utf8, Data_size:32/little, Data_bits/bitstring>>,
Chunks = <<Fmt_chunk/bitstring, Data_chunk/bitstring>>,
Riff_size = erlang:byte_size(Chunks) + 4,
<<"RIFF"/utf8, Riff_size:32/little, "WAVE"/utf8, Chunks/bitstring>>.