[erlang-questions] Reading and Writing .wav files

Mazen Harake mazen.harake@REDACTED
Mon May 31 13:29:21 CEST 2010


Perhaps you should always put a license on it...

/Mazen

On 31/05/2010 14:03, Ivan Uemlianin wrote:
> Dear All
>
> Please find below wave.erl, containing two functions to read and write 
> .wav files.
>
> wave:read(FileName) reads a wav file and returns a record containing 
> the audio sample rate and the raw data; wave:write(WavRecord, 
> FileName) does the opposite.
>
> The script was an experiment in parsing and packing binaries and I'm 
> very pleased at how straightforward it was (mostly: I did ask for help 
> here for one point) and how clearly readable the code is.
>
> It all works (tested on mono and stereo wav files).
>
> There are a couple of ancillary functions, which perhaps re-implement 
> functions already in the standard library, or are otherwise 
> sub-optimal:  lp2pl converts a list of pairs into a pair of lists, and 
> lt2l does (almost) the opposite, converting a list of tuples into a 
> flat list.
> Comments welcome.
>
> Best wishes
>
> Ivan
>
> % ----- wave.erl
>
> -module(wave).
> -export([read/1, write/2, write/3]).
>
> %% Erlang package to read and write .wav files.
>
> %% wave record format
> %% version 1.0:
> %% - does not support compression
> %% - only pcm encoding (AudioFormat = 1)
>
> % wav file data always little-endian
> % 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}.
>
>
>
> % ----- wave.erl
>

---------------------------------------------------

---------------------------------------------------

WE'VE CHANGED NAMES!

Since January 1st 2010 Erlang Training and Consulting Ltd. has become ERLANG SOLUTIONS LTD.

www.erlang-solutions.com



More information about the erlang-questions mailing list