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;