ANN: wave.erl
Ivan Uemlianin
ivan@REDACTED
Tue Jun 1 11:02:27 CEST 2010
Dear All
Please find below wave.erl, an erlang script for reading and writing
.wav audio files. I am releasing it under the ISC license.
The script exports functions read/1, write/3 and write/2, and the wave
record:
-record(wave, {audio_format, sample_rate, data}).
where:
- audio_format is an unsigned integer (currently only 1, i.e. pcm
encoding, is supported);
- sample_rate is an unsigned integer (i.e., samples per second, in kHz);
- data is a list of lists of signed integers, one list for each channel
(e.g., stereo will have two lists of integers).
read(FileName) reads a wav file and returns a wave record.
write(WavRecord, FileName, BitsPerSample) writes the wave record
WavRecord to the file FileName with the sample size BitsPerSample.
write(WavRecord, FileName) just calls write/3 with BitsPerSample = 16.
[identical announcement at
http://llaisdy.wordpress.com/2010/06/01/wave-erl-an-erlang-script-to-read-and-write-wav-files/]
With thanks and best wishes
Ivan
%% Copyright © 2010 by Ivan Uemlianin (ivan@REDACTED)
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS AND COPYRIGHT HOLDERS
%% DISCLAIM ALL WARRANTIES WITH SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
%% HOLDERS BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
%% DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
%% PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
%% ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
%% THIS SOFTWARE.
%% * wave.erl --- Erlang functions to read and write .wav files.
%%
%% ** version 1.0:
%% - does not support compression
%% - only pcm encoding (AudioFormat = 1)
%%
%% ** todo
%% - set debug flags to disable io:format in read/1 and write/3
%% - test wav file for well-formed-ness
%% - function to parse and return header (ie without reading whole file)
%% - pread(FileName, Offset, NFrames)
%%
%% ** refs
%%
%% [1] http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
%% [2] http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
%% [3] http://chlorophil.blogspot.com/2007/06/erlang-binary-map.html
-module(wave).
-export([read/1, write/2, write/3]).
% audio_format = integer (see wav docs for formats)
% sample_rate in kHz
% data is list of lists of integers (one list for each channel)
-record(wave, {audio_format, sample_rate, data}).
read(FileName) -> %% returns wave record
{ok, Binary} = file:read_file(FileName),
<<ChunkID:4/binary,
ChunkSize:4/little-unsigned-integer-unit:8,
Format:4/binary,
SubChunk1ID:4/binary,
SubChunk1Size:4/little-unsigned-integer-unit:8,
AudioFormat:2/little-unsigned-integer-unit:8, % 1 = pcm
NumChannels:2/little-unsigned-integer-unit:8, % 1 = mono
SampleRate:4/little-unsigned-integer-unit:8, % 16000, etc
ByteRate:4/little-unsigned-integer-unit:8, % SampleRate *
NumChannels * BitsPerSample/8
BlockAlign:2/little-unsigned-integer-unit:8, % NumChannels *
BitsPerSample/8
BitsPerSample:2/little-unsigned-integer-unit:8, % 16, etc
SubChunk2ID:4/binary,
SubChunk2Size:4/little-unsigned-integer-unit:8,
Data/binary>> = Binary,
% TODO: test file for well-formedness, e.g.:
% ChunkID == "RIFF"
% ChunkSize == SubChunk2Size + 36
% Format == "WAVE"
% AudioFormat = 1 % no other encodings supported in v1.0
% SubChunk1ID == "fmt "
% SubChunk1Size == 16
% SubChunk2ID == "data"
% SubChunk2Size == NumSamples * NumChannels * BitsPerSample div 8
Channels = data2channels(Data, NumChannels, BitsPerSample),
io:format("* ~p~n~n- ChunkID: ~p~n- ChunkSize: ~B~n- Format: ~p~n-
SubChunk1ID: ~p~n- SubChunk1Size: ~B~n- AudioFormat: ~B~n- NumChannels:
~B~n- SampleRate: ~B~n- ByteRate: ~B~n- BlockAlign: ~B~n- BitsPerSample:
~B~n- SubChunk2ID: ~p~n- SubChunk2Size: ~B~n- Data: [...]~n~n",
[FileName,
binary_to_list(ChunkID),
ChunkSize,
binary_to_list(Format),
binary_to_list(SubChunk1ID),
SubChunk1Size,
AudioFormat,
NumChannels,
SampleRate,
ByteRate,
BlockAlign,
BitsPerSample,
binary_to_list(SubChunk2ID),
SubChunk2Size
]),
#wave{sample_rate=SampleRate,
audio_format=AudioFormat,
data=Channels
}.
mono(Data, NBytes) ->
[[ X || <<X:NBytes/little-signed-integer-unit:8>> <= Data ]].
stereo(Data, NBytes) ->
LP = [ [A,B] || << A:NBytes/little-signed-integer-unit:8,
B:NBytes/little-signed-integer-unit:8 >> <= Data],
lp2pl(LP, [], []).
% list of pairs to pair of lists
lp2pl([], Left, Right) ->
[lists:reverse(Left), lists:reverse(Right)];
lp2pl([[L,R]|T], Left, Right) ->
lp2pl(T, [L|Left], [R|Right]).
data2channels(Data, NumChannels, BitsPerSample) ->
NBytes = BitsPerSample div 8,
case NumChannels of
1 -> mono(Data, NBytes);
2 -> stereo(Data, NBytes)
end.
lt2l([], Acc) ->
lists:reverse(Acc);
lt2l([H|T], Acc) ->
lt2l(T, [element(2, H), element(1, H) | Acc]).
pl2l(PL) ->
X = lists:zip(lists:nth(1, PL), lists:nth(2, PL)),
lt2l(X, []).
channels2dataBin(Channels, BitsPerSample) ->
case length(Channels) of
1 -> Data = lists:nth(1, Channels);
2 -> Data = pl2l(Channels)
end,
Size = length(Data) * (BitsPerSample div 8),
DataBin = list_to_binary([ <<X:2/little-signed-integer-unit:8>> ||
X <- Data]),
{Size, DataBin}.
write(WavRecord, FileName) ->
write(WavRecord, FileName, 16).
write(WavRecord, FileName, BitsPerSample) ->
{SubChunk2Size, Data} = channels2dataBin(WavRecord#wave.data,
BitsPerSample),
ChunkID = list_to_binary("RIFF"),
Format = list_to_binary("WAVE"),
SubChunk1ID = list_to_binary("fmt "),
SubChunk1Size = 16,
AudioFormat = 1, % only pcm encoding supported
NumChannels = length(WavRecord#wave.data),
SampleRate = WavRecord#wave.sample_rate,
ByteRate = SampleRate * NumChannels * BitsPerSample div 8,
BlockAlign = NumChannels * BitsPerSample div 8,
SubChunk2ID = list_to_binary("data"),
ChunkSize = SubChunk2Size + 36,
io:format("* ~p~n~n- ChunkID: ~p~n- ChunkSize: ~B~n- Format: ~p~n-
SubChunk1ID: ~p~n- SubChunk1Size: ~B~n- AudioFormat: ~B~n- NumChannels:
~B~n- SampleRate: ~B~n- ByteRate: ~B~n- BlockAlign: ~B~n- BitsPerSample:
~B~n- SubChunk2ID: ~p~n- SubChunk2Size: ~B~n- Data: [...]~n~n",
[FileName,
binary_to_list(ChunkID),
ChunkSize,
binary_to_list(Format),
binary_to_list(SubChunk1ID),
SubChunk1Size,
AudioFormat,
NumChannels,
SampleRate,
ByteRate,
BlockAlign,
BitsPerSample,
binary_to_list(SubChunk2ID),
SubChunk2Size
]),
Binary = <<ChunkID:4/binary,
ChunkSize:4/little-unsigned-integer-unit:8, %%
Format:4/binary,
SubChunk1ID:4/binary,
SubChunk1Size:4/little-unsigned-integer-unit:8,
AudioFormat:2/little-unsigned-integer-unit:8,
NumChannels:2/little-unsigned-integer-unit:8,
SampleRate:4/little-unsigned-integer-unit:8,
ByteRate:4/little-unsigned-integer-unit:8,
BlockAlign:2/little-unsigned-integer-unit:8,
BitsPerSample:2/little-unsigned-integer-unit:8,
SubChunk2ID:4/binary,
SubChunk2Size:4/little-unsigned-integer-unit:8, %%
Data/binary
>>,
file:write_file(FileName, Binary),
{ok, FileName}.
--
============================================================
Ivan A. Uemlianin
Speech Technology Research and Development
ivan@REDACTED
www.llaisdy.com
llaisdy.wordpress.com
www.linkedin.com/in/ivanuemlianin
"Froh, froh! Wie seine Sonnen, seine Sonnen fliegen"
(Schiller, Beethoven)
============================================================
More information about the erlang-questions
mailing list