From 122f3c401f23f84799802c7b9667bda222646487 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Wed, 8 Apr 2015 03:18:53 -0400 Subject: initial commit --- find_key | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100755 find_key (limited to 'find_key') diff --git a/find_key b/find_key new file mode 100755 index 0000000..485e728 --- /dev/null +++ b/find_key @@ -0,0 +1,176 @@ +#!/usr/bin/perl -w + +# Key detection. Uses libsndfile + its perl bindings, vamp-plugin-sdk, +# qm-vamp-plugins, and mplayer (for decoding mp3 files, since sndfile +# doesn't) + +# Output is the output from the qm-keydetector plugin (with its key numbers +# converted to key names), followed by a list of all keys detected, sorted +# descending by the amount of the song that was in that key. + +# The user is expected to understand what relative keys are: if 70% of the song +# is in G and 30% is in E minor (same actual key, relative minor), it's up to +# the user to listen to the song and decide whether it sounds more major or minor. + +# 20140107 bkw: hack to add support for anything SndFile can't read, by +# using mplayer to convert to tmp.wav + +use Audio::SndFile; + +our @keynames = split /\s/, 'none C Db D Eb E F F# G Ab A Bb B Cm C#m Dm Ebm Em Fm F#m Gm G#m Am Bbm Bm none'; + +our %relative_majors = ( + 'Cm' => 'Eb', + 'C#m' => 'E', + 'Dm' => 'F', + 'Ebm' => 'F#', + 'Em' => 'G', + 'Fm' => 'Ab', + 'F#m' => 'A', + 'Gm' => 'Bb', + 'G#m' => 'B', + 'Am' => 'C', + 'Bbm' => 'Db', + 'Bm' => 'D', + ); + +our %relative_minors = ( + 'Eb' => 'Cm', + 'E' => 'C#m', + 'F' => 'Dm', + 'F#' => 'Ebm', + 'G' => 'Em', + 'Ab' => 'Fm', + 'A' => 'F#m', + 'Bb' => 'Gm', + 'B' => 'G#m', + 'C' => 'Am', + 'Db' => 'Bbm', + 'D' => 'Bm', + ); + +if(!@ARGV || $ARGV[0] =~ /^--?h(elp)?/) { + print <open("<", $_); + }; + if($@) { + warn "Extracting to tmp.wav\n"; + system("mplayer -vo null -ao pcm:fast:file=tmp.wav \"$_\""); + $sf = Audio::SndFile->open("<", "tmp.wav"); + $_ = "tmp.wav"; + } + $sec = ($sf->frames) * (1 / $sf->samplerate); + } + printf "file is %03.2f sec\n", $sec; + + my $oldstamp = -1; + my $oldkey = -1; + + my $got = `vamp-simple-host qm-vamp-plugins:qm-keydetector "$_" 2`; + die "Analysis failed\n" unless defined $got; + + print "\n"; + + my @got = split "\n", $got; +## for my $line (@got) { +## my ($pre, $post) = split(/:/, $line); +## my $gotkey = $keynames[$post]; +## my $gotrel; +## if($gotkey =~ /m$/) { +## $gotrel = get_relative_major($gotkey); +## } else { +## $gotrel = get_relative_minor($gotkey); +## } +## $line =~ s/:\s(\d+)\s*/": $gotkey ($gotrel)"/e; +## print $line . "\n"; +## } + + for(@got) { + my ($key, $relkey); + s/^\s*//; + s/\s*$//; + my ($stamp, $keynum) = split /: /, $_; + $keynum =~ s/\s.*//; + $key = $keynames[$keynum]; + if($fold_major) { + $key = get_relative_major($key); + } elsif($fold_minor) { + $key = get_relative_minor($key); + } + if($key =~ /m$/) { + $relkey = get_relative_major($key); + } else { + $relkey = get_relative_minor($key); + } + if($oldstamp != -1) { + $times{$oldkey} += ($stamp - $oldstamp); + } + $oldstamp = $stamp; + $oldkey = $key; + print "$stamp: $key ($relkey)\n"; + } + + $times{$oldkey} += ($sec - $oldstamp); + + print "\n"; + for(sort { $times{$b} <=> $times{$a} } keys %times) { + printf "%3s: %4.2f sec, %4.2f%%\n", $_, $times{$_}, $times{$_} / $sec * 100; + } +} + +sub get_relative_major { + my $key = shift || die "missing key"; + return $relative_majors{$key} || $key; +} + +sub get_relative_minor { + my $key = shift || die "missing key"; + return $relative_minors{$key} || $key; +} + +__END__ +Output of plugin: +- Estimated key (from C major = 1 to B major = 12 and C minor = 13 to B minor = 24) + +Looks like: + 0.000000000: 23 + 54.984852607: 16 + 59.443083900: 4 + 71.331700680: 16 + 78.019047619: 18 + 78.762086167: 23 + 95.108934240: 18 + 97.338049886: 4 + 110.712743764: 9 + 112.198820861: 23 + 210.279909297: 18 + 215.481179138: 4 + 216.967256235: 18 + 222.168526077: 11 + 231.084988662: 4 + 234.057142857: 2 -- cgit v1.2.3