Packages
wavex
0.9.1
0.14.1
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.4
0.9.3
0.9.1
0.9.0
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.5.5
0.5.4
0.5.3
0.5.2
0.5.0
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.0
0.3.0
0.2.16
0.2.13
0.2.11
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.2
0.2.1
0.2.0
0.1.8
0.1.0
Read WAVE LPCM data
Current section
Files
Jump to
Current section
Files
lib/wavex.ex
defmodule Wavex do
@moduledoc """
Read LPCM WAVE data.
"""
alias Wavex.Chunk.{Data, Format, RIFF}
alias Wavex.Error
alias Wavex.Error.RIFFSizeMismatch
@enforce_keys [
:riff,
:format,
:data
]
defstruct [
:riff,
:format,
:data
]
@type t :: %__MODULE__{
riff: RIFF.t(),
format: Format.t(),
data: Data.t()
}
@spec verify_riff_size(non_neg_integer, binary) :: :ok | {:error, RIFFSizeMismatch.t()}
defp verify_riff_size(actual, binary) do
case byte_size(binary) - 8 do
^actual -> :ok
expected -> {:error, %RIFFSizeMismatch{expected: expected, actual: actual}}
end
end
@doc """
The duration of a wave file in seconds.
## Examples
Calculating the duration of 100000 samples at 88200b/s.
iex> Wavex.duration(%Wavex{
...> data: %Wavex.Chunk.Data{
...> data:
...> 0
...> |> List.duplicate(100_000)
...> |> List.to_string(),
...> size: 100_000
...> },
...> format: %Wavex.Chunk.Format{
...> bits_per_sample: 8,
...> block_align: 2,
...> byte_rate: 88_200,
...> channels: 2,
...> sample_rate: 44_100
...> },
...> riff: %Wavex.Chunk.RIFF{size: 100_036}
...> })
1.1337868480725624
Calculating the duration of 100000 samples at 176400b/s.
iex> Wavex.duration(%Wavex{
...> data: %Wavex.Chunk.Data{
...> data:
...> 0
...> |> List.duplicate(100_000)
...> |> List.to_string(),
...> size: 100_000
...> },
...> format: %Wavex.Chunk.Format{
...> bits_per_sample: 16,
...> block_align: 4,
...> byte_rate: 176_400,
...> channels: 2,
...> sample_rate: 44_100
...> },
...> riff: %Wavex.Chunk.RIFF{size: 100_036}
...> })
0.5668934240362812
"""
@spec duration(t) :: number
def duration(%__MODULE__{
data: %Data{size: size},
format: %Format{byte_rate: byte_rate}
}) do
size / byte_rate
end
@doc ~S"""
Map over the data of a `%Wavex{}` value.
## Examples
Mapping over an 8-bit `%Wavex{}` value.
iex> wave = %Wavex{
...> riff: %RIFF{
...> size: 60
...> },
...> format: %Format{
...> bits_per_sample: 8,
...> block_align: 2,
...> byte_rate: 88_200,
...> channels: 2,
...> sample_rate: 44_100
...> },
...> data: %Data{
...> size: 24,
...> data: <<
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00
...> >>
...> }
...> }
iex> wave = Wavex.map(wave, &(&1 + 0x12))
iex> wave.data.data
<<
0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12
>>
Mapping over a 16-bit `%Wavex{}` value.
iex> wave = %Wavex{
...> riff: %RIFF{
...> size: 60
...> },
...> format: %Format{
...> bits_per_sample: 16,
...> block_align: 4,
...> byte_rate: 176_400,
...> channels: 2,
...> sample_rate: 44_100
...> },
...> data: %Data{
...> size: 24,
...> data: <<
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00
...> >>
...> }
...> }
iex> wave = Wavex.map(wave, &(&1 - 0x1234))
iex> wave.data.data
<<
-0x1234::16-signed-little, -0x1234::16-signed-little,
-0x1234::16-signed-little, -0x1234::16-signed-little,
-0x1234::16-signed-little, -0x1234::16-signed-little,
-0x1234::16-signed-little, -0x1234::16-signed-little,
-0x1234::16-signed-little, -0x1234::16-signed-little,
-0x1234::16-signed-little, -0x1234::16-signed-little
>>
Mapping over a 24-bit `%Wavex{}` value.
iex> wave = %Wavex{
...> riff: %RIFF{
...> size: 60
...> },
...> format: %Format{
...> bits_per_sample: 24,
...> block_align: 4,
...> byte_rate: 176_400,
...> channels: 2,
...> sample_rate: 44_100
...> },
...> data: %Data{
...> size: 24,
...> data: <<
...> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
...> >>
...> }
...> }
iex> wave = Wavex.map(wave, &(&1 + 0x123456))
iex> wave.data.data
<<
0x123456::24-signed-little, 0x123456::24-signed-little,
0x123456::24-signed-little, 0x123456::24-signed-little,
0x123456::24-signed-little, 0x123456::24-signed-little,
0x123456::24-signed-little, 0x123456::24-signed-little
>>
"""
@spec map(t, (integer -> integer)) :: t
def map(
%__MODULE__{
data: %Data{data: data} = data_chunk,
format: %Format{bits_per_sample: bits_per_sample}
} = wave,
function
) do
data =
case bits_per_sample do
8 -> map8(data, function)
16 -> map16(data, function)
24 -> map24(data, function)
end
%__MODULE__{wave | data: %Data{data_chunk | data: data}}
end
@spec map8(binary, (integer -> integer), binary) :: binary
defp map8(binary, function, acc \\ <<>>)
defp map8(<<sample, etc::binary>>, function, acc) do
map8(etc, function, <<function.(sample)>> <> acc)
end
defp map8(<<>>, _, acc), do: String.reverse(acc)
@spec map16(binary, (integer -> integer), binary) :: binary
defp map16(binary, function, acc \\ <<>>)
defp map16(<<sample::16-signed-little, etc::binary>>, function, acc) do
map16(etc, function, <<function.(sample)::16-signed-big>> <> acc)
end
defp map16(<<>>, _, acc), do: String.reverse(acc)
@spec map24(binary, (integer -> integer), binary) :: binary
defp map24(binary, function, acc \\ <<>>)
defp map24(<<sample::24-signed-little, etc::binary>>, function, acc) do
map24(etc, function, <<function.(sample)::24-signed-big>> <> acc)
end
defp map24(<<>>, _, acc), do: String.reverse(acc)
@doc ~S"""
Read LPCM WAVE data.
For more details, see `Wavex.Chunk.RIFF.read/1`, `Wavex.Chunk.Format.read/1`,
and `Wavex.Chunk.Data.read/1`.
## Examples
Reading a 16-bit stereo 88200b/s LPCM file:
iex> Wavex.read(<<
...> 0x52, 0x49, 0x46, 0x46, # R I F F
...> 0x2C, 0x00, 0x00, 0x00, # 44
...> 0x57, 0x41, 0x56, 0x45, # W A V E
...> 0x66, 0x6D, 0x74, 0x20, # f m t \s
...> 0x10, 0x00, 0x00, 0x00, # 16
...> 0x01, 0x00, 0x02, 0x00, # 1 2
...> 0x22, 0x56, 0x00, 0x00, # 22050
...> 0x88, 0x58, 0x01, 0x00, # 88200
...> 0x04, 0x00, 0x10, 0x00, # 4 16
...> 0x64, 0x61, 0x74, 0x61, # d a t a
...> 0x08, 0x00, 0x00, 0x00, # 8
...> 0x00, 0x00, 0x00, 0x00, # 0
...> 0x00, 0x00, 0x00, 0x00 # 0
...> >>)
{:ok,
%Wavex{
data: %Wavex.Chunk.Data{
data: <<0, 0, 0, 0, 0, 0, 0, 0>>,
size: 8
},
format: %Wavex.Chunk.Format{
bits_per_sample: 16,
block_align: 4,
byte_rate: 88_200,
channels: 2,
sample_rate: 22_050
},
riff: %Wavex.Chunk.RIFF{size: 44}
}}
Reading a 16-bit mono 22050/s LPCM file:
iex> Wavex.read(<<
...> 0x52, 0x49, 0x46, 0x46, # R I F F
...> 0x28, 0x00, 0x00, 0x00, # 40
...> 0x57, 0x41, 0x56, 0x45, # W A V E
...> 0x66, 0x6D, 0x74, 0x20, # f m t \s
...> 0x10, 0x00, 0x00, 0x00, # 16
...> 0x01, 0x00, 0x01, 0x00, # 1 1
...> 0x11, 0x2B, 0x00, 0x00, # 11025
...> 0x22, 0x56, 0x00, 0x00, # 22050
...> 0x02, 0x00, 0x10, 0x00, # 2 16
...> 0x64, 0x61, 0x74, 0x61, # d a t a
...> 0x04, 0x00, 0x00, 0x00, # 4
...> 0x00, 0x00, 0xFE, 0xFF # 0 0 254 255
...> >>)
{:ok,
%Wavex{
data: %Wavex.Chunk.Data{data: <<0, 0, 254, 255>>, size: 4},
format: %Wavex.Chunk.Format{
bits_per_sample: 16,
block_align: 2,
byte_rate: 22_050,
channels: 1,
sample_rate: 11_025
},
riff: %Wavex.Chunk.RIFF{size: 40}
}}
"""
@spec read(binary) :: {:ok, t} | {:error, Error.t()}
def read(binary) when is_binary(binary) do
with {:ok, %RIFF{size: riff_size} = riff, etc} <- RIFF.read(binary),
:ok <- verify_riff_size(riff_size, binary),
{:ok, %Format{} = format, etc} <- Format.read(etc),
{:ok, %Data{} = data} <- Data.read(etc) do
{:ok, %Wavex{riff: riff, format: format, data: data}}
end
end
end