#!/usr/local/bin/perl -w
###########################################
# banshee-bpm-update
# Mike Schilli, 2011 (m@perlmeister.com)
###########################################
use strict;
use Log::Log4perl qw(:easy);
use DBI qw(:sql_types);
use DBD::SQLite;
use Sysadm::Install qw(tap);
use URI::Escape;
use File::Temp qw(tempfile);
use POSIX;

my $SAMPLE_RATE = 10_000;
my $OFFSET      = 60;
my $SAMPLE_SECS = 30;
my $MIN_SIZE    = 500;
my $MIN_DROP    = 0.7;
my $NWINDOWS    = 20;

Log::Log4perl->easy_init({ level => $INFO, 
                    category => "main" });
my $db  = glob 
          "~/.config/banshee-1/banshee.db";
my $dbh = DBI->connect( "dbi:SQLite:$db", 
    "", "", { RaiseError => 1, 
              AutoCommit => 1 });
bpm_update( $dbh );
$dbh->disconnect();

###########################################
sub bpm_update {
###########################################
  my( $dbh ) = @_;

  my $sth = $dbh->prepare( 
      "SELECT Uri FROM CoreTracks" );
  $sth->execute();

  my $upd_sth = $dbh->prepare(
    "UPDATE CoreTracks SET BPM=? " .
    "WHERE Uri = ?");

  while( (my $uri) = 
         $sth->fetchrow_array() ) {
    my $file = uri_unescape( $uri );
    $file =~ s#^file://##;
    INFO "Updating $uri";
    $upd_sth->execute( bpm( $file ),
                       $uri );
  }
  $upd_sth->finish();
  $sth->finish();
}

###########################################
sub bpm {
###########################################
  my( $file ) = @_;

  my $rawfile;

  if( $file =~ /\.raw$/ ) {
    $rawfile = $file;
  } else {
    $rawfile = File::Temp->new( 
       SUFFIX => ".raw", UNLINK => 1 );
      
    my($stdout, $stderr, $rc) = 
      tap "sox", $file, "-r", $SAMPLE_RATE, 
        "-c", 2, "-b", 16, "-t", "raw", 
        "-e", "signed", $rawfile, 
        "bandpass", 100, 1, 
        "trim", $OFFSET, $SAMPLE_SECS;
      
    if( $rc ) {
      LOGWARN "sox $file: $stderr";
      return 0;
    }
  }

  return raw_bpm( 
           samples( $rawfile->filename ) );
}

###########################################
sub samples {
###########################################
  my( $file ) = @_;

  my @vals = ();
  sysopen FILE, "$file", O_RDONLY 
      or LOGDIE "$file: $!";

  while( sysread( FILE, my $val, 4 ) ) {
    my($c1, $c2) = unpack 'ss', $val;
    $c1 = 0 if $c1 < $MIN_SIZE;
    push @vals, $c1;
  }
  close FILE;
  return @vals;
}

###########################################
sub raw_bpm {
###########################################
  my(@samples) = @_;

  my $win   = scalar @samples / 
              ($NWINDOWS*$SAMPLE_SECS);
  my($bumps, $pmax, $slope) = (0, 0, "up");

  for( my $o = 0; $o <= $#samples - $win; 
       $o += $win ) {
    my $max = 0;
    for( my $i = $o; $i <= $o + $win; 
         $i++ ) {
      if( $samples[$i] > $max ) {
        $max = $samples[$i];
      }
    }

    if( $slope eq "up" ) {
      if( $max < $MIN_DROP * $pmax ) {
        $slope = "down";
        $bumps++;
      }
    } else {
      $slope = "up" if  $max > $pmax;
    }
    $pmax = $max;
  }

  return int($bumps / $SAMPLE_SECS * 60.0);
}
