# $Id: Wav.pm,v 1.5 2003/12/14 09:43:56 ianb Exp $
package MP3::Archive::Lint::Tools::Wav;

use strict;
use warnings;

use vars qw(@ISA $VERSION);
@ISA = qw(MP3::Archive::Lint::Tool);
$VERSION = '0.01';

=head1 wav - Tool to check .wav files

Checks wav files by parsing the RIFF header.

=head2 Tests

=over 4

=cut

sub new
{
	my $proto=shift;
	my $class=ref($proto) || $proto;
	my $opts=shift;
	my $self=$class->SUPER::new($opts);;
	bless($self,$class);

	if($self->config->get("lint_allow_wav"))
	{
		$self->settests("samplerate","samplesize","channels","data");
	}
	else
	{
		$self->settests("allow");
	}

	%{$self->{wavformats}}=(
							1  => "Microsoft PCM",
							2  => "Microsoft ADPCM",
							6  => "ITU G.711 a-law",
							7  => "ITU G.711 u-law",
							17 => "IMA ADPCM",
							20 => "ITU G.723 ADPCM (Yamaha)",
							49 => "GSM 6.10",
							64 => "ITU G.721 ADPCM",
							80 => "MPEG",
							85 => "MPEG Layer 3");

	
	return $self;
}


sub initscan
{
	my $self=shift;

	return 0 unless ($self->{filename}=~/\.wav$/i);
	return 0 if (-z $self->{file});	
	return 0 if (-d $self->{file});	
	unless($self->config->get("lint_allow_wav"))
	{
		# don't need to scan file, only testing if is .wav
		return 1;
	}

	my @ret;
	@ret=$self->parsewavheader($self->{file});
	unless(scalar(@ret))
	{
		# fake test name 
		$self->say("data:not a wav file or header corrupt");
		return 0;
	}

	($self->{riff},
	 $self->{wave},
	 $self->{format},
	 $self->{channels},
	 $self->{samplerate},
	 $self->{bitsize}) = @ret;

	return 1;
}

=item B<allow>

If $lint_allow_wav is set to zero, complains if it finds a .wav file. This is useful if the
only use you have for wav files is while ripping, so a .wav file means
a problem with your ripping process.

Variables:

=over 4

=item $lint_allow_wav

Whether to complain (boolean (1/0), default 1)

=back

=cut


sub allow
{
	my $self=shift;
	$self->say("wav file");
}

=item B<samplerate>

Tests sample rate of wav file is at least $lint_minsamplerate Hz.

Variables:

=over 4

=item $lint_minsamplerate

Minimum sample rate in Hz (number, default 44100)

=back

=cut


sub samplerate
{
	my $self=shift;
	my $min=$self->config->get("lint_minsamplerate");
	if($self->{samplerate} < $min)
	{
		$self->say("low samplerate:$self->{samplerate}Hz (should be ${min}Hz)");
	}
}

=item B<samplesize>

Test samplesize of wav file is at least $lint_minbitsize bits.

Variables:

=over 4

=item $lint_minbitsize

(number, default 16)

=back

=cut


sub samplesize
{
	my $self=shift;
	my $min=$self->config->get("lint_minbitsize");
	if(($self->{format} == 1) && # bitsize field only valid with PCM
	   ($self->{bitsize} < $min))
	{
		$self->say("low bitsize:$self->{bitsize} (should be $min)");
	}
}

=item B<channels>

Tests file is stereo.

=cut

sub channels
{
	my $self=shift;
	if($self->{channels} == 1)
	{
			$self->say("mono");
	}
	elsif($self->{channels} > 2)
	{
		$self->say("not stereo:$self->{channels} channels");
	}
}

=item B<data>

Tests if file is a valid WAV file, and if it is an mp3 hiding in a wav
file or is some other sort of non-pcm audio.

=cut

# also includes a message generated in initscan that pretends to be from data()
sub data
{
	my $self=shift;

	if(($self->{riff} ne "RIFF") ||
	   ($self->{wave} ne "WAVE"))
	{
		$self->say("not a wav file or header corrupt");
	}
	elsif(($self->{format} == 80) || # MPEG
	   ($self->{format} == 85))      # MPEG Layer 3
	{
		$self->say("mp3 hiding in wav file");
	}
	elsif($self->{format} != 1) # "Microsoft" PCM
	{
		$self->say("not PCM audio:$self->{wavformats}{$self->{format}}");
	}
}

sub parsewavheader
{
	my ($self,$file)=@_;
	unless(open(WAV,$file))
	{
		print "cannot open:$file:$!\n";
		return(());
	}
	my $len=36; # how much header we need
	my $buf;
	my $readlen=read(WAV,$buf,$len,0);
	close(WAV);
	return(()) unless($readlen == $len);

	my         ($riff,undef,$wave,undef,$format,$channels,$samplerate,undef,$bitsize)=
		unpack("A4    A4    A4    A8    v       v         V           A6    v",$buf);

#	print "riff: $riff wave: $wave\n";
#	print "format: $format channels: $channels samplerate: $samplerate bitsize: $bitsize\n";
	
	return($riff,$wave,$format,$channels,$samplerate,$bitsize);
}

# WAV file format
# Extracted from magic(5) database
# 
# On debian this is /usr/share/misc/magic
# On many other (non FHS-compliant) unix systems this is /etc/magic
# 
# 0	string		RIFF		RIFF (little-endian) data
# # Microsoft WAVE format (*.wav)
# >8	string		WAVE		\b, WAVE audio
# >>20	leshort		1		\b, Microsoft PCM
# >>>34	leshort		>0		\b, %d bit
# >>20	leshort		2		\b, Microsoft ADPCM
# >>20	leshort		6		\b, ITU G.711 a-law
# >>20	leshort		7		\b, ITU G.711 u-law
# >>20	leshort		17		\b, IMA ADPCM
# >>20	leshort		20		\b, ITU G.723 ADPCM (Yamaha)
# >>20	leshort		49		\b, GSM 6.10
# >>20	leshort		64		\b, ITU G.721 ADPCM
# >>20	leshort		80		\b, MPEG
# >>20	leshort		85		\b, MPEG Layer 3
# >>22	leshort		=1		\b, mono
# >>22	leshort		=2		\b, stereo
# >>22	leshort		>2		\b, %d channels
# >>24	lelong		>0		%d Hz

=back

=cut

1; 
