Audio-Analyzer

 view release on metacpan or  search on metacpan

lib/Audio/Analyzer.pm  view on Meta::CPAN

use constant FREQ_CACHE => 9;
use constant SCALER => 10;
use constant BYTES => 11;
use constant EOF_FOUND => 12;
use constant FILELESS_BUFFER => 13;

sub new {
	my ($class, %opts) = @_;

	my $self = [];

	bless($self, $class);

	$self->init(%opts);	

	return $self;
}

sub add_samples {
    my ($self, @samples) = @_;
    push(@{$self->[FILELESS_BUFFER]}, @samples);
    return;
}

sub sample_buffer_size {
    my ($self) = @_;
    return scalar(@{$self->[FILELESS_BUFFER]});
}

sub next {
	my ($self) = @_;
	my @samples;
	my $chunk;
	
	if (defined($self->[INPUT])) {
        my $pcm = $self->read_pcm;
        
        if (! defined($pcm)) {
            return undef;
        }
        
        @samples = $self->convert_pcm($pcm);
        
	} else {
	    @samples = $self->get_fileless_samples;
	    return undef unless @samples;
	}


	my $channels = $self->split_channels(@samples);

	$chunk = Audio::Analyzer::Chunk->new($self, $channels);

	return $chunk;
}

sub progress {
	my ($self) = @_;
	my $bytes = $self->[BYTES];
	my $input = $self->[INPUT];
	my $size = (stat($input))[7];

	return int($bytes / $size * 100);
}

sub freqs {
	my ($self) = @_;
	my $sample_rate = $self->[SAMPLE_RATE];
	my $dft_size = $self->[DFT_SIZE];
	my $freq_cache = $self->[FREQ_CACHE];
	my @freqs;

	if (defined($freq_cache)) {
		return $freq_cache;
	}

	for(my $i = 0; $i < $dft_size / 2; $i++) {
		$freqs[$i] = $i / $dft_size * $sample_rate;
	}

	$self->[FREQ_CACHE] = \@freqs;

	return $self->[FREQ_CACHE];
}

#private interface starts here

sub init {
	my ($self, %opts) = @_;
	my $file;
	my $dft_size;
	my $seek_step;
	my $channels;
	my $bits_per_sample;
	my $sample_rate;
	my $read_size;
	my $scaler;
	my $fps;

    if (exists($opts{'file'})) {
        if (! defined($file = $opts{'file'})) {
            croak "if the file option is given it must have a value";
        }
    } elsif (! defined $opts{fileless}) {
        croak "no file was specified and fileless operation is not configured";
    }

	if (! defined($dft_size = $opts{'dft_size'})) {
		$dft_size = DEFAULT_DFT_SIZE;
	}

	if (! defined($sample_rate = $opts{'sample_rate'})) {
		$sample_rate = DEFAULT_SAMPLE_RATE;
	}

	if (! defined($channels = $opts{'channels'})) {
		$channels = DEFAULT_CHANNELS;
	}

	if (defined($bits_per_sample = $opts{'bits_per_sample'})) {
		if ($bits_per_sample != 8 && $bits_per_sample != 16) {

lib/Audio/Analyzer.pm  view on Meta::CPAN

	return \@split;
}


#converts PCM into floating point representation
sub convert_pcm {
	my ($self, $pcm) = @_;
	my $bytes_per_sample = $self->[BYTES_PER_SAMPLE];
	my @samples;

	if ($bytes_per_sample == 2) {
		while(length($pcm) >= 2) {
			my $sample = unpack('s<', substr($pcm, 0, 2, ''));
			push(@samples, $sample);
		}
	} else {
		die "8 bit PCM isn't implemented yet";
	}

	return @samples;
}

sub get_fileless_samples {
    my ($self) = @_;
    my $input = $self->[INPUT];
    my $read_size = $self->[READ_SIZE];
    my $fileless_buffer = $self->[FILELESS_BUFFER];
    my $samples_needed = $read_size / $self->[BYTES_PER_SAMPLE];
    
    if (defined $input) {
        die "read_buffer() was called for fileless operation but there was an input file ref?";
    }

    return () unless scalar(@$fileless_buffer) >= $samples_needed;
    return splice(@$fileless_buffer, 0, $samples_needed);
}

sub read_pcm {
	my ($self) = @_;
	my $input = $self->[INPUT];
	my $read_size = $self->[READ_SIZE];
	my $seek_step = $self->[SEEK_STEP];
	my $bytes = $self->[BYTES];
	my $EOF_found = $self->[EOF_FOUND];
	my $buf;
	my $ret;
	my $rewind;
	
	die "there was no input filehandle ref" unless defined $input;
	
	$ret = read($input, $buf, $read_size);

	if (! defined($ret)) {
		die "could not read: $!";
	} elsif ($ret == 0) {
		return undef;
	} elsif ($ret < $read_size) {
		#hit the end and did not get enough data for the FFT - seek 
		#backwards a whole read_size and finish the last reading
		#as best as possible
		my $size = (stat($input))[7];
		
		$self->[EOF_FOUND] = 1;

		seek($input, $size - $read_size, 0) or die "could not seek: $!";

		return $self->read_pcm;
	}

	$bytes += $seek_step;

	$rewind = $read_size - $seek_step;

	if ($rewind && ! $EOF_found) {
		seek($input, $rewind * -1, 1) or die "could not seek: $!";
	}

	$self->[BYTES] = $bytes;

	return $buf;
}

sub scaler {
	my ($self) = @_;
	
	return $self->[SCALER];
}

package Audio::Analyzer::Chunk;

our $VERSION = '0.02';

use strict;
use warnings;

sub new {
	my ($class, $analyzer, $channels) = @_;
	my $self = {};

	$self->{analyzer} = $analyzer;
	$self->{channels} = $channels;

	bless($self, $class);

	return $self;
}

sub pcm {
	my ($self) = @_;

	return $self->{channels};
}

sub fft {
	my ($self) = @_;
	my $channels = $self->{channels};
	my @mags;

	for(my $i = 0; $i < scalar(@$channels); $i++) {
		$mags[$i] = $self->do_fft($channels->[$i]);
	}



( run in 2.176 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )