-
-
Save anonymous/15c7caabb09e3f7a3f78 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use strictures; | |
package audiofind; | |
use IO::All -binary; | |
use GD::Graph::mixed; | |
use List::Util (); | |
use PDL; | |
use 5.010; | |
use Moo; | |
use Capture::Tiny 'capture'; | |
use Carp::Always; | |
use Devel::Comments; | |
use PDL::IO::FlexRaw; | |
$|++; | |
sub { | |
has $_ => is => 'lazy' | |
for qw( sample sample_source sample_file sample_start sample_length bit_rate sox global_offset ); | |
} | |
->(); | |
my $cmd = $ARGV[0]; | |
audiofind->new->$cmd; | |
### done | |
sub _build_sample_file { $_[0]->raw_path( "sample" ) } | |
sub _build_sample_source { io( "sample_source.mp3" ) } | |
sub _build_sample_start { 17.6 - $_[0]->global_offset } | |
sub _build_sample_length { 0.8 } | |
sub _build_bit_rate { 1200 } | |
sub _build_sox { "C:/Program Files (x86)/sox-14-4-0/sox.exe" } | |
sub _build_global_offset { 10 } | |
sub _build_sample { | |
my ( $self ) = @_; | |
### sample file | |
return $self->_file_to_pdl_array( $self->sample_file ) if $self->sample_file->exists; | |
my $values = $self->raw_for( $self->sample_source ); | |
### saving raw sample data to: $self->sample_file->name | |
my $bit_rate = $self->bit_rate; | |
my $start = $bit_rate * $self->sample_start; | |
my $end = $start + $bit_rate * $self->sample_length - 1; | |
my $sample = $values->slice( "$start:$end" ); | |
io( $self->sample_file->name )->binary->print( pack 's*', $sample->list ); | |
return $sample; | |
} | |
sub raw_for { | |
my ( $self, $file ) = @_; | |
my $raw_path = $self->raw_path( $file ); | |
$self->generate_raw_for( $file ) if !$raw_path->exists; | |
my $raw = $self->_file_to_pdl_array( $raw_path ); | |
return $raw; | |
} | |
sub raw_path { shift->generic_path( @_, "raw" ) } | |
sub corr_path { shift->generic_path( @_, "corr" ) } | |
sub png_path { shift->generic_path( @_, "png" ) } | |
sub generic_path { | |
my ( $self, $file, $type ) = @_; | |
return io->catfile( $type, $self->bit_rate, "$file.$type" ); | |
} | |
sub generate_raw_for { | |
my ( $self, $file ) = @_; | |
### generating raw for: $file->name | |
my $sox = $self->sox; | |
my $bit_rate = $self->bit_rate; | |
my $out = $self->raw_path( $file ); | |
io( $out->filepath )->mkpath; | |
my $start = 20 - $self->global_offset; | |
my $end = 50 - $self->global_offset; | |
my ( $stdout, $err, $ret ) = capture { | |
system qq["$sox" "$file" -t raw -c 1 -b 16 -r $bit_rate "$out" trim $start $end]; | |
}; | |
die $err || "sox failed" if $err or $ret; | |
return; | |
} | |
sub search { | |
my ( $self ) = @_; | |
### finding files to search sample in | |
my $filter = sub { | |
$_->filename =~ /\.mp3$/ && $_->filename !~ /(dialog|bonus)\.mp3$/ && !$self->corr_path( $_ )->exists; | |
}; | |
my @mp3s = io->curdir->filter( $filter )->All_Files; | |
@mp3s = @mp3s[ 0 .. 10 ]; | |
### number of files found: scalar @mp3s | |
### generating pdl data for sample | |
my $sample = $self->sample; | |
my $sample_size = $sample->nelem; | |
my $sample_norm = $sample - avg( $sample ); | |
my $sample_condensed = sum( $sample_norm**2 ); | |
for my $file ( @mp3s ) { ### |===[%] | | |
my $search_space = $self->raw_for( $file ); | |
my $search_space_size = $search_space->nelem; | |
my $max = $search_space_size - 1 - $sample_size; | |
my @correlations; | |
for my $i ( 0 .. $max ) { | |
my $search_slice = $search_space->slice( $i . ":" . ( $i + $sample_size - 1 ) ); | |
my $corr = cross_corr( $search_slice, $sample_norm, $sample_condensed ); | |
push @correlations, $corr; | |
} | |
my $corr = $self->corr_path( $file ); | |
io( $corr->filepath )->mkpath; | |
$corr->print( join "\n", @correlations ); | |
} | |
} | |
sub cross_corr { | |
my ( $search_slice, $sample_norm, $sample_condensed ) = @_; | |
my $search_norm = $search_slice - avg( $search_slice ); | |
my $denom = sqrt( sum( $search_norm**2 ) * $sample_condensed ); | |
my $corr_coeffs = sum( $search_norm * $sample_norm ) / $denom; | |
return $corr_coeffs; | |
} | |
sub graph { | |
my ( $self ) = @_; | |
my $filter = sub { | |
$_->filename =~ /\.mp3$/ | |
&& $_->filename !~ /(dialog|bonus)\.mp3$/ | |
&& $self->corr_path( $_->name )->exists | |
&& !$self->png_path( $_->name )->exists; | |
}; | |
my @mp3s = io->curdir->filter( $filter )->All_Files; | |
my $secs_per_tick = 10; | |
my @spacers = ( '' ) x ( $self->bit_rate * $secs_per_tick - 1 ); | |
my @labels = map { ( $_ * $secs_per_tick + $self->global_offset, @spacers ) } 0 .. 3; | |
for my $file ( @mp3s ) { ### |===[%] | | |
my @vals = split "\n", $self->corr_path( $file->name )->all; | |
my $graph = GD::Graph::mixed->new( 1200, 600 ); | |
$graph->set( | |
title => $file->name, | |
markers => [3], | |
types => [qw(points)], | |
marker_size => 1, | |
zero_axis => 1, | |
y_min_value => -1.01, | |
y_max_value => 1.01, | |
); | |
my $gd = $graph->plot( [ \@labels, \@vals ] ) or die $graph->error; | |
my $png = $self->png_path( $file ); | |
io( $png->filepath )->mkpath; | |
$png->binary->print( $gd->png ); | |
} | |
return; | |
} | |
sub _file_to_pdl_array { | |
my ( $self, $file ) = @_; | |
# create a header for a single vector; compute the length automatically | |
my $n_elements = $file->size / length( pack 's', 0 ); | |
my %header = ( Type => 'short', NDims => 1, Dims => [$n_elements] ); | |
my $data = readflex( $file->name, [ \%header ] ); | |
return $data; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment