aboutsummaryrefslogtreecommitdiff
path: root/find_key
diff options
context:
space:
mode:
Diffstat (limited to 'find_key')
-rwxr-xr-xfind_key176
1 files changed, 176 insertions, 0 deletions
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 <<EOF;
+$0: Find key of music in audio (or maybe video) files.
+
+Usage: $0 [-m|-M] file.wav [file.wav ...]
+
+-m: Force all keys to minor
+-M: Force all keys to major
+EOF
+ exit 0;
+}
+
+for(@ARGV) {
+ if(/^-M/) {
+ $fold_major = 1;
+ $fold_minor = 0;
+ next;
+ } elsif(/^-m/) {
+ $fold_major = 0;
+ $fold_minor = 1;
+ next;
+ }
+
+ my $sec;
+ {
+ my $sf;
+ eval {
+ $sf = Audio::SndFile->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