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/chunk/format.ex
defmodule Wavex.Chunk.Format do
@moduledoc """
Read a format chunk.
"""
alias Wavex.Utils
alias Wavex.Error.{
BlockAlignMismatch,
ByteRateMismatch,
UnexpectedEOF,
UnexpectedFormatSize,
UnexpectedFourCC,
UnsupportedBitsPerSample,
UnsupportedFormat,
ZeroChannels
}
@enforce_keys [
:channels,
:sample_rate,
:byte_rate,
:block_align,
:bits_per_sample
]
defstruct [
:channels,
:sample_rate,
:byte_rate,
:block_align,
:bits_per_sample
]
@type t :: %__MODULE__{
channels: pos_integer,
sample_rate: pos_integer,
byte_rate: pos_integer,
block_align: pos_integer,
bits_per_sample: pos_integer
}
@spec verify_size(non_neg_integer) :: :ok | {:error, UnexpectedFormatSize.t()}
defp verify_size(16), do: :ok
defp verify_size(actual), do: {:error, %UnexpectedFormatSize{actual: actual}}
@spec verify_format(non_neg_integer) :: :ok | {:error, UnsupportedFormat.t()}
defp verify_format(1), do: :ok
defp verify_format(actual), do: {:error, %UnsupportedFormat{actual: actual}}
@spec verify_bits_per_sample(non_neg_integer) :: :ok | {:error, UnsupportedBitsPerSample.t()}
defp verify_bits_per_sample(actual) when actual in [8, 16, 24], do: :ok
defp verify_bits_per_sample(actual), do: {:error, %UnsupportedBitsPerSample{actual: actual}}
@spec verify_channels(non_neg_integer) :: :ok | {:error, ZeroChannels.t()}
defp verify_channels(0), do: {:error, %ZeroChannels{}}
defp verify_channels(_), do: :ok
@spec verify_block_align(non_neg_integer, non_neg_integer) ::
:ok | {:error, BlockAlignMismatch.t()}
defp verify_block_align(expected, expected), do: :ok
defp verify_block_align(expected, actual) do
{:error, %BlockAlignMismatch{expected: expected, actual: actual}}
end
@spec verify_byte_rate(non_neg_integer, non_neg_integer) :: :ok | {:error, ByteRateMismatch.t()}
defp verify_byte_rate(expected, expected), do: :ok
defp verify_byte_rate(expected, actual) do
{:error, %ByteRateMismatch{expected: expected, actual: actual}}
end
@doc ~S"""
Read a format chunk.
## Example
Reading the format chunk of a 16-bit stereo 88.2kb/s LPCM file.
iex> Wavex.Chunk.Format.read(<<
...> 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
...> >>)
{:ok,
%Wavex.Chunk.Format{
bits_per_sample: 16,
block_align: 4,
byte_rate: 88_200,
channels: 2,
sample_rate: 22_050
}, ""}
## Caveats
Bytes 1-4 must read `"fmt "` to indicate a format chunk. The following
example gives an error because bytes 1-4 read `"data"` instead of `"fmt "`.
iex> Wavex.Chunk.Format.read(<<
...> 0x64, 0x61, 0x74, 0x61, # d a t a
...> 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
...> >>)
{:error, %Wavex.Error.UnexpectedFourCC{expected: "fmt ", actual: "data"}}
The format size at bytes 5-8 is expected to be `16`, the format size for the
LPCM format. The following example gives an error because the format size is
`18` instead of `16`.
iex> Wavex.Chunk.Format.read(<<
...> 0x66, 0x6d, 0x74, 0x20, # f m t \s
...> 0x12, 0x00, 0x00, 0x00, # 18
...> 0x01, 0x00, 0x02, 0x00, # 1 2
...> 0x22, 0x56, 0x00, 0x00, # 22050
...> 0x88, 0x58, 0x01, 0x00, # 88200
...> 0x04, 0x00, 0x10, 0x00 # 4 16
...> >>)
{:error, %Wavex.Error.UnexpectedFormatSize{actual: 18}}
The format at bytes 9-10 must be `0x0001` (LPCM), as other formats are not
supported. The following example gives an error because the format is
`0x0032` instead of `0x0001`.
iex> Wavex.Chunk.Format.read(<<
...> 0x66, 0x6d, 0x74, 0x20, # f m t \s
...> 0x10, 0x00, 0x00, 0x00, # 16
...> 0x32, 0x00, 0x02, 0x00, # 50 2
...> 0x22, 0x56, 0x00, 0x00, # 22050
...> 0x88, 0x58, 0x01, 0x00, # 88200
...> 0x04, 0x00, 0x10, 0x00 # 4 16
...> >>)
{:error, %Wavex.Error.UnsupportedFormat{actual: 0x0032}}
The format at bytes 11-12 must not be `0`, because there has to be at least
one channel. The following example gives an error because the number of
channels is `0`.
iex> Wavex.Chunk.Format.read(<<
...> 0x66, 0x6d, 0x74, 0x20, # f m t \s
...> 0x10, 0x00, 0x00, 0x00, # 16
...> 0x01, 0x00, 0x00, 0x00, # 1 0
...> 0x22, 0x56, 0x00, 0x00, # 22050
...> 0x88, 0x58, 0x01, 0x00, # 88200
...> 0x04, 0x00, 0x10, 0x00 # 4 16
...> >>)
{:error, %Wavex.Error.ZeroChannels{}}
The byte rate at bytes 17-20 must be equal to `sample_rate * block_align`.
The following example gives an error because `44100` is not equal to
`22050 * 4 = 88200`.
iex> Wavex.Chunk.Format.read(<<
...> 0x66, 0x6d, 0x74, 0x20, # f m t \s
...> 0x10, 0x00, 0x00, 0x00, # 16
...> 0x01, 0x00, 0x02, 0x00, # 1 2
...> 0x22, 0x56, 0x00, 0x00, # 22050
...> 0x44, 0xAC, 0x00, 0x00, # 44100
...> 0x04, 0x00, 0x10, 0x00 # 4 16
...> >>)
{:error, %Wavex.Error.ByteRateMismatch{expected: 88200, actual: 44100}}
The block alignment at bytes 21-22 must be equal to
`channels * bits_per_sample / 8`. The following example gives an error
because `2` is not equal to `2 * 16 / 8 = 4`.
iex> Wavex.Chunk.Format.read(<<
...> 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
...> 0x02, 0x00, 0x10, 0x00 # 2 16
...> >>)
{:error, %Wavex.Error.BlockAlignMismatch{expected: 4, actual: 2}}
The bits per second at bytes 23-24 must be `8`, `16`, or `24`, as other bit
rates are not supported. The following example gives an error because the
bit rate is `32`.
iex> Wavex.Chunk.Format.read(<<
...> 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, 0x20, 0x00 # 4 32
...> >>)
{:error, %Wavex.Error.UnsupportedBitsPerSample{actual: 32}}
"""
@spec read(binary) ::
{:ok, t, binary}
| {:error,
BlockAlignMismatch.t()
| ByteRateMismatch.t()
| UnexpectedEOF.t()
| UnexpectedFormatSize.t()
| UnexpectedFourCC.t()
| UnsupportedBitsPerSample.t()
| UnsupportedFormat.t()
| ZeroChannels.t()}
def read(<<
fmt_id::binary-size(4),
size::32-little,
format::16-little,
channels::16-little,
sample_rate::32-little,
byte_rate::32-little,
block_align::16-little,
bits_per_sample::16-little,
etc::binary
>>) do
with :ok <- Utils.verify_four_cc(fmt_id, "fmt "),
:ok <- verify_size(size),
:ok <- verify_format(format),
:ok <- verify_channels(channels),
:ok <- verify_bits_per_sample(bits_per_sample),
:ok <- verify_block_align(channels * div(bits_per_sample, 8), block_align),
:ok <- verify_byte_rate(sample_rate * block_align, byte_rate) do
{:ok,
%__MODULE__{
bits_per_sample: bits_per_sample,
block_align: block_align,
byte_rate: byte_rate,
channels: channels,
sample_rate: sample_rate
}, etc}
end
end
def read(binary) when is_binary(binary), do: {:error, %UnexpectedEOF{}}
end