-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 <> -> {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 <> -> {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 <> -> 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 <> -> 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 <> -> 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 <> -> 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 <> -> 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) -> <> 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, <> 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, <> 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, <> 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 = <>, 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 = <>, Riff_size = erlang:byte_size(Chunks) + 4, <<"RIFF"/utf8, Riff_size:32/little, "WAVE"/utf8, Chunks/bitstring>>.