AIFF Perl Modules

A series of Perl modules for parsing AIFF (Audio Interchange Files) headers.

 ##############################################################################
 #
 #	File:			AESD.pm
 #
 #
 #	Author(s):		David Ackerman
 #
 #	Copyright:		Copyright (c) 2001 David Ackerman
 #					All Rights Reserved.
 #
 #	Source:			
 #
 #	Notes:			
 #
 #	Change History:
 #			7/23/01 [12:09 AM]	 Source started
 ##############################################################################

#!perl -w

package AIFF::AESD;

use strict;
use vars qw($VERSION);
$VERSION = "0.1";

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self = {};
	$self->{RAW_DATA} = shift;
	$self->{AES_DATA} = [];
	bless($self, $class);
	return $self;
}

sub unpackChunk {
	my $self = shift;
	for (my $i = 0; $i < 24; $i++){
		${ $self->{AES_DATA} }[$i] = unpack('C', substr($self->{RAW_DATA}, $i, 1));
	}	
}

sub packChunk {
	my $self = shift;
	for (my $i = 0; $i < 24; $i++){
		${ $self->{AES_DATA} }[$i] = pack('C', ${ $self->{AES_DATA} }[$i]);
	}
}

sub dumpToScreen {
	my $self = shift;
	$self->unpackChunk();
	print "AESD DATA = ";
	for (my $i = 0; $i < 24; $i++){
		print "${ $self->{AES_DATA} }[$i]";
	}
	print "\n";
	print "Channel status block: ";
	(${ $self->{AES_DATA} }[0] & 1) ? print "Professional\n" : print "Consumer\n";
	
	$self->packChunk();
}

1;	


 ##############################################################################
 #
 #	File:			AIFF.pm
 #
 #
 #	Author(s):		David Ackerman
 #
 #	Copyright:		Copyright (c) 2001 David Ackerman
 #					All Rights Reserved.
 #
 #	Source:			
 #
 #	Notes:			
 #
 #	Change History:
 #			6/28/01 [5:35 PM]	Source started
 #	
 ##############################################################################


package AIFF::AIFF;

use strict;
use AIFF::COMM;
use AIFF::SSND;
#use AIFF::SSYS;
use AIFF::AESD;
use vars qw($VERSION);
$VERSION = "0.1";

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self = {};
	$self->{FILE} = shift;
	$self->{COMM_CHUNK} = undef;
	$self->{SSND_CHUNK} = undef;
	$self->{SSYS_CHUNK} = undef;
	$self->{AESD_CHUNK}= undef;
	$self->{SSND_DATA_BYTE_ZERO} = undef;
	$self->{SSND_DATA_FILE_MARK} = undef;
	bless($self, $class);
	if (defined($self->{FILE})) {
		#init aiff with file given, if an error occurs return nil
		my $r = $self->_init();
		if (defined($r)) {
			return $self;
		} else {
			return 0;
		}
	} else {
		#no file given create an empty aiff
		return $self;
	}
}


##################################################################################
#	
#	compareCOMMChunk ()
#	Recieves: another AIFF object to be compared to
#	Returns: 1 if both objects have identical COMM chunks, 0 if they differ
#	Side effects: none
#
sub compareCOMMChunk {
	my $self = shift;
	my $other = shift;
	if ($self->{COMM_CHUNK}->getNumChannels() == $other->{COMM_CHUNK}->getNumChannels() &&
	$self->{COMM_CHUNK}->getNumSampleFrames() == $other->{COMM_CHUNK}->getNumSampleFrames() &&
	$self->{COMM_CHUNK}->getSampleSize() == $other->{COMM_CHUNK}->getSampleSize() &&
	$self->{COMM_CHUNK}->getSampleRate() == $other->{COMM_CHUNK}->getSampleRate()) {
		return 1;
	} else {
		return 0;
	}
}


sub exists {
	my $self = shift;
	my $temp = shift;
	
	return ($self->{$temp}) ? '1' : '0';
}

sub exportCOMMChunk {
	my $self = shift;
	my $data = $self->{COMM_CHUNK}->getChunkPacked();
	my $size = length($data);
	my $header = "COMM" . pack('l', $size);
	return ($header . $data);
}

sub exportSSNDHeader {
	my $self = shift;
	my $data = $self->{SSND_CHUNK}->getChunkPacked();
	my $size = length($data);
	my $header = "SSND" . pack('l', $size);
	return ($header . $data);
}

sub _init {
	my $self = shift;
	open (SOUND_FILE, "<$self->{FILE}") or die ($!);
	my ($chunkID, $chunkSize, $formSignature, $formSize);
	read (SOUND_FILE, $chunkID, 4);
	if ($chunkID eq 'FORM') {
		read (SOUND_FILE, $formSize, 4);
		$formSize = unpack ('l', $formSize);
		read (SOUND_FILE, $formSignature, 4);
		if (($formSignature eq 'AIFF') || ($formSignature eq 'AIFC')) {
			my $fileMark = 4; # 4 bytes past FORM header, i.e. just read signature!
			my $temp = undef;
			
			while ($fileMark < $formSize) {
				read (SOUND_FILE, $chunkID, 4);
				read (SOUND_FILE, $chunkSize, 4);
				$chunkSize = unpack ('l', $chunkSize);
				$fileMark += 8; #chunk header
				
				if ($chunkID eq 'COMM') {
					read (SOUND_FILE, $temp, $chunkSize);
					$self->{COMM_CHUNK} = AIFF::COMM->new($temp);
				} elsif ($chunkID eq 'SSND') {
					read (SOUND_FILE, $temp, 8);
					$self->{SSND_CHUNK} = AIFF::SSND->new($chunkSize, $temp);
					seek (SOUND_FILE, ($chunkSize - 8), 1); #skip sound data for now
					$self->{SSND_DATA_BYTE_ZERO} = $fileMark + 16; #8 for offset + blocksize & 8 for FORM header!
					$self->{SSND_DATA_FILE_MARK} = $self->{SSND_DATA_BYTE_ZERO};
				} elsif ($chunkID eq 'AESD') {
					read (SOUND_FILE, $temp, $chunkSize);
					$self->{AESD_CHUNK} = AIFF::AESD->new($temp);
				} elsif ($chunkID eq 'APPL') {
					read (SOUND_FILE, $temp, 4); #check signature
					#if ($temp eq 'SSYS') {
						#read (SOUND_FILE, $temp, ($chunkSize - 4));
						#$self->{SSYS_CHUNK} = AIFF::SSYS->new($temp);
					#} else {
						seek (SOUND_FILE, ($chunkSize - 4), 1); #skip unrecognized APPL chunk
					#}
				} else {
					#unrecognized chunk
					warn ("$chunkID is unrecognized, skipped!");
					seek (SOUND_FILE, $chunkSize, 1);
				}
				$fileMark += $chunkSize;
				
				if ($chunkSize % 2 == 1) {
					seek (SOUND_FILE, 1, 1);
					$fileMark++;
				}
			}
			
			close (SOUND_FILE);
			return 1;
		} else {
			#not an aiff file
			warn ("Bad FORM Signature");
			return undef;
		}
	} else {
		#not an IFF document
		warn ("Not an IFF document");
		return undef;
	}
}

1;

 ##############################################################################
 #
 #	File:			COMM.pm
 #
 #
 #	Author(s):		David Ackerman
 #
 #	Copyright:		Copyright (c) 2001 David Ackerman
 #					All Rights Reserved.
 #
 #	Source:			
 #
 #	Notes:			
 #
 #	Change History:
 #			6/28/01 [5:35 PM]	Source started
 #	
 ##############################################################################

#!perl -w

package AIFF::COMM;

use strict;
use vars qw($VERSION);
$VERSION = "0.1";

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self = {};
	$self->{RAW_DATA} = shift;
	$self->{NUM_CHANNELS} = substr($self->{RAW_DATA}, 0, 2);
	$self->{NUM_SAMPLE_FRAMES} = substr($self->{RAW_DATA}, 2, 4);
	$self->{SAMPLE_SIZE}= substr($self->{RAW_DATA}, 6, 2);
	$self->{SAMPLE_RATE} = substr($self->{RAW_DATA}, 8, 10);
	bless($self, $class);
	return $self;
}

sub unpackChunk {
	my $self = shift;
	$self->{NUM_CHANNELS} = unpack('s', $self->{NUM_CHANNELS});
	$self->{NUM_SAMPLE_FRAMES} = unpack('L', $self->{NUM_SAMPLE_FRAMES});
	$self->{SAMPLE_SIZE} = unpack('s', $self->{SAMPLE_SIZE});
	my @temp = split(//, $self->{SAMPLE_RATE});
	foreach $_ (@temp)
	{
		$_ = unpack('C', $_);
	}
	$self->{SAMPLE_RATE} = (($temp[2] * 256) + $temp[3])/(2**(14 - $temp[1]));
}

sub packChunk {
	my $self = shift;
	$self->{NUM_CHANNELS} = pack('s', $self->{NUM_CHANNELS});
	$self->{NUM_SAMPLE_FRAMES} = pack('L', $self->{NUM_SAMPLE_FRAMES});
	$self->{SAMPLE_SIZE} = pack('s', $self->{SAMPLE_SIZE});
	#whole sample rates only!
	$self->{SAMPLE_RATE} = doubleToExtended($self->{SAMPLE_RATE});
}

sub getChunkPacked {
	my $self = shift;
	$self->{RAW_DATA} = undef;
	$self->{RAW_DATA} = $self->{NUM_CHANNELS} . $self->{NUM_SAMPLE_FRAMES} . $self->{SAMPLE_SIZE} . $self->{SAMPLE_RATE};
	return $self->{RAW_DATA};
}

sub getNumChannels {
	my $self = shift;
	$self->unpackChunk();
	my $r = $self->{NUM_CHANNELS};
	$self->packChunk();
	return $r;
}

sub getNumSampleFrames {
	my $self = shift;
	$self->unpackChunk();
	my $r = $self->{NUM_SAMPLE_FRAMES};
	$self->packChunk();
	return $r;
}

sub getSampleSize {
	my $self = shift;
	$self->unpackChunk();
	my $r =  $self->{SAMPLE_SIZE};
	$self->packChunk();
	return $r;
}

sub getSampleRate {
	my $self = shift;
	$self->unpackChunk();
	my $r =  $self->{SAMPLE_RATE};
	$self->packChunk();
	return $r;
}

sub dumpToScreen {
	my $self = shift;
	$self->unpackChunk();
	print "Num Channels = $self->{NUM_CHANNELS}\nNum Sample Frames = $self->{NUM_SAMPLE_FRAMES}\n";
	print "Sample Size = $self->{SAMPLE_SIZE}\nSample Rate = $self->{SAMPLE_RATE}\n";
	$self->packChunk();
}

sub doubleToExtended {
	my $sr = shift;
	$sr = pack('d', $sr);
	$sr = unpack('B*', $sr);
	my $exponent = 0;
	my $e = substr($sr, 1, 11);
	for (my $i = length($e) - 1; $i >= 0; $i--)
	{
		if (substr($e, $i, 1) == 1)
		{
			$exponent += 2**(length($e) - 1 - $i);		
		}	
	}
	$exponent -= 1023; #remove bias of double
	
	my $m = substr($sr, 12);
	$exponent += 16383; # add bias for extended
	
	$m = 1 . $m;
	$m .= 0 x 11;
	
	my $result = pack('S', $exponent);
	
	for (my $i = 0; $i < length($m); $i += 8)
	{
		$result .= pack('B8', substr($m, $i, 8));
	}
	
	return $result;	
}

1;	

 ##############################################################################
 #
 #	File:			SSND.pm
 #
 #
 #	Author(s):		David Ackerman
 #
 #	Copyright:		Copyright (c) 2001 David Ackerman
 #					All Rights Reserved.
 #
 #	Source:			
 #
 #	Notes:			
 #
 #	Change History:
 #			7/22/01 [6:12 PM]	Source started
 #	
 ##############################################################################

#!perl -w

package AIFF::SSND;

use strict;
use vars qw($VERSION);
$VERSION = "0.1";

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self = {};
	$self->{CHUNK_SIZE} = shift;
	
	$self->{RAW_DATA} = shift;	
	$self->{OFFSET} = substr($self->{RAW_DATA}, 0, 4);
	$self->{BLOCKSIZE} = substr($self->{RAW_DATA}, 4, 4);
	bless($self, $class);
	return $self;
}

sub getOffset {
	my $self = shift;
	$self->unpackChunk();
	my $r = $self->{OFFSET};
	$self->packChunk();
	return $r;
}

sub getBlocksize {
	my $self = shift;
	$self->unpackChunk();
	my $r = $self->{BLOCKSIZE};
	$self->packChunk();
	return $r;
}

sub setOffset {
	my $self = shift;
	$self->unpackChunk();
	$self->{OFFSET} = shift;
	$self->packChunk();
}

sub setBlocksize {
	my $self = shift;
	$self->unpackChunk();
	$self->{BLOCKSIZE} = shift;
	$self->packChunk();
}

sub getChunkPacked {
	my $self = shift;
	$self->{RAW_DATA} = undef;
	$self->{RAW_DATA} = $self->{OFFSET} . $self->{BLOCKSIZE};
	return $self->{RAW_DATA};
}

sub unpackChunk {
	my $self = shift;
	$self->{OFFSET} = unpack('L', $self->{OFFSET});
	$self->{BLOCKSIZE} = unpack('L', $self->{BLOCKSIZE});
}

sub packChunk {
	my $self = shift;
	$self->{OFFSET} = pack('L', $self->{OFFSET});
	$self->{BLOCKSIZE} = pack('L', $self->{BLOCKSIZE});
}

sub dumpToScreen {
	my $self = shift;
	$self->unpackChunk();
	print "ChunkSize = $self->{CHUNK_SIZE}\nOffset = $self->{OFFSET}\nBlocksize = $self->{BLOCKSIZE}\n";
	$self->packChunk();
}

sub countBlocks {
	my $self = shift;
	$self->unpackChunk();
	return $self->{CHUNK_SIZE}/ $self->{BLOCKSIZE};
	$self->packChunk();
}

1;