cormanaz has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to read WAV files, specifically to read the data values and turn them into decimal values. I've managed to navigate past all the header material and I can find the byte where the sample data starts. However, I'm not too up on binary operations in PERL. Can anyone give me some pointers on how to read the two bytes that make up a sample in a 16-bit (mono) wav file and turn them into a decimal value? It's stored as a two's-complement signed integer, low byte first.

I did find a module called Audio::Wav that seemingly would take care of all this, but I can't find the module anywhere with PPM.

Thanks in advance....Steve

Replies are listed 'Best First'.
Re: Reading WAV files
by arden (Curate) on Mar 10, 2004 at 00:15 UTC
    Assuming you're using ActiveState (since you mention PPM), you can download Audio::Wav for ActiveState 5.8.x here or for 5.6.1 here. Remember, you can always manually download the modules from ActiveState's site instead of having ppm download them, then have ppm use a "local repository" to install them.

    - - arden.

Re: Reading WAV files
by theorbtwo (Prior) on Mar 10, 2004 at 03:07 UTC

    You're looking for the unpack function, and more specificly, the 's' template character. (Which actualy operates on native-endian values, not specificly little-endian ones. I just noticed that there is no way to specifiy the byteorder using (un)pack for signed values.)

    unpack 's*', $data; will convert all of $data into it's meaning, assuming $data contains a bunch of signed 16-bit values.


    Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

Re: Reading WAV files
by graff (Chancellor) on Mar 10, 2004 at 03:31 UTC
    The part that theorbtwo left out of his reply was the matter of reading binary data into a scalar variable (though maybe you've handled this already, since you say you solved getting past the header).

    You can read a fixed number of bytes at a time by setting $/ to a numeric value, like this:

    $/ = \8; # read 8 bytes at a time; binmode INPUTFH; # don't forget this on MS-Windows! # (you can use binmode on STDIN, as well, if needed) while (<INPUTFH>) { # $_ now holds 8 bytes of data ... }
    Of course, you can use a larger or smaller number of bytes per read, depending on what you want to do. You could also use the "read" or "sysread" functions, where one of the args given to the function is the number of bytes to read. Or if your wav files aren't that big, just seek past the header and slurp the rest all at once into a scalar by setting $/ to undef.
Re: Reading WAV files
by Joost (Canon) on Mar 10, 2004 at 13:46 UTC
    I did find a module called Audio::Wav that seemingly would take care of all this, but I can't find the module anywhere with PPM.
    AFAIK the Audio::Wav::* modules are pure Perl so you should be able to install them using perl -MCPAN install Audio::Wav even if you don't have a C compiler on you system.

    You might even be able to get away with copying the *.pm files to wherever you want them installed.

    Joost.

Re: Reading WAV files
by zentara (Cardinal) on Mar 10, 2004 at 16:46 UTC
    You probably want to read the header, then unpack the data based on that, it might be 8 bit or 16 bit, mono or stereo, etc. Here is a way to read the header and determine it's playlength based on byte size, number of channels, etc.
    #!/usr/bin/perl -w use strict; use Fcntl; my $fnm = shift; sysopen WAV,$fnm,O_RDONLY; my $riff; sysread WAV,$riff,12; my $fmt; sysread WAV,$fmt,24; my $data; sysread WAV,$data,8; close WAV; # RIFF header: 'RIFF', long length, type='WAVE' my ($r1,$r2,$r3) = unpack "A4VA4", $riff; # WAV header, 'fmt ', long length, short unused, short channels, # long samples/second, long bytes per second, short bytes per sample, # short bits per sample my ($f1,$f2,$f3,$f4,$f5,$f6,$f7,$f8) = unpack "A4VvvVVvv",$fmt; # DATA header, 'DATA', long length my ($d1,$d2) = unpack "A4V", $data; my $playlength = $d2/$f6; print << "EOF"; RIFF header: $r1, length $r2, type $r3 Format: $f1, length $f2, always $f3, channels $f4, sample rate $f5, bytes per second $f6, bytes per sample $f7, bits per sample $f8 Data: $d1, length $d2 Playlength: $playlength seconds EOF

    I'm not really a human, but I play one on earth. flash japh
      Thanks for all the suggestions. I was able to get it working with unpack. The header occupies bytes 0-46 of the file and from there
      read IN,$buffer,2; $value = unpack('s',$buffer);
      enclosed in a suitable loop yields the data values (for a 16-bit file).

      I never was able to find Audio-Wav on Activestate. When I search using keyword "Audio" on Komodo/VPM, it doesn't show up. I tried the same thing with command line PPM and got same result. There is Audio-(Daemon, FLAC, Play-MPG123, PSID, Radio-V4L, SID, Tools, WMA) but no Wav.

      Anyway, reading it as above is pretty straighforward so mission accomplished.