#!/usr/bin/perl #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # #NAME # abctr - abc transpose by interval (scale step). # #SYNOPSIS # abctr [interval] [keys] [file].. # #DESCRIPTION # Read one or more abc files (or stdin), and write output that has the notes # shifted by the given interval or to the given key. # # The [interval] arg, if present, is a '-' or '+' followed by a a number # that is how many scale steps to transpose. Using this method, you can't # include a 'b' or '#' in the target key; this program will pick the most # common target for that note. # # The [keys] arg, if present, is a '-' or '+' followed by "K:" and a key. # -K: gives the source key; +K: gives the target key. The source key is only # needed if it's not in the input. This method lets you specify the full # target key. # #OPTIONS # #BUGS # #AUTHOR # John Chambers #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # $| = 1; $exitstat = 0; ($P = $0) =~ s".*/""; $V = $ENV{"V_$P"} || 2; # Verbose level open(V,">&STDERR"); # Verbose output defaults to STDERR #$steps = 1; # Scale steps for Bb instruments. #$semis = 2; # Semitones for Bb instruments. args: for $arg (@ARGV) { print V "% ARG $arg\n" if $V>1; if ($arg =~ /^([-+]*)(\d+)$/) { print V "% OPT $arg\n" if $V>1; $steps = int($arg); print V "% === steps=$steps\n" if $V>1; } elsif ($arg =~ /^([-+])K:(.*)$/) { print V "% OPT $arg\n" if $V>1; $ud = $1; $ks = $2; print V "% === $ud K: $ks\n" if $V>1; &trgkey($ks); } elsif (-f $arg) { print V "% FIL $arg\n" if $V>1; push @files, $arg; } else { print V "% ??? $arg\n" if $V>1; } } @Key2Semis = ( # Numeric values of white notes "Cb"=>47, "C" =>48, "C#"=>49, "Db"=>49, "D" =>50, "D#"=>51, "Eb"=>51, "E" =>52, "E#"=>53, "Fb"=>52, "F" =>53, "F#"=>54, "Gb"=>54, "G" =>55, "G#"=>56, "Ab"=>56, "A" =>57, "A#"=>58, "Bb"=>58, "B" =>59, "B#"=>60, "cb"=>59, "c" =>60, "c#"=>61, "db"=>61, "d" =>62, "d#"=>63, "eb"=>63, "e" =>64, "e#"=>65, "fb"=>64, "f" =>65, "f#"=>66, "gb"=>66, "g" =>67, "g#"=>68, "ab"=>68, "a" =>69, "a#"=>70, "Bb"=>70, "b" =>71, "b#"=>72, ); @Semis2Key = ( # Numeric values of white notes 48=>"C" , 49=>"C#", 50=>"D" , 51=>"Eb", 52=>"E" , 53=>"F" , 54=>"F#", # 54=>"Gb", 55=>"G" , 56=>"Ab", 57=>"A" , 58=>"Bb", 59=>"B" , 60=>"C" , ); @Nwhite = ( # Numeric values of white notes "C"=>48, "D"=>50, "E"=>52, "F"=>53, "G"=>55, "A"=>57, "B"=>59, "c"=>60, "d"=>62, "e"=>64, "f"=>65, "g"=>67, "a"=>69, "b"=>71, ); @S = ( "C,","D,","E,","F,","G,","A,","B,", "C", "D", "E", "F", "G", "A", "B", "c", "d", "e", "f", "g", "a", "b", "c'","d'","e'","f'","g'","a'","b'", ); for ($i=0; $i<@S; $i++) { $I{$S[$i]} = $i; } $M = '[\^=_]*'; # Modification. $N = '[A-Ga-g][\',]*'; # Note. $L = '[/\d.\<>]*'; # Length. for $file (@files) { ++$files; if (open(FILE,$file)) { print V "% <<< \"$file\"\n" if $V>1; &onefile(*FILE); } else { print V "$P: Can't read \"$file\" ($!)\n" if $V>0; } } unless ($files) { &onefile(*STDIN); } exit $exitstat; #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub onefile { #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # local (*F) = @_; local($frkey,$tokey); line: for $line () { if ($line =~ /^[Ww]:/) {print $line; next line} if ($line =~ /^\s*%/) {print $line; next line} while ($line) { if ($line =~ s/^(\s+)//) {print $1} if ($line =~ s/^(K:\s*)(\w)(\w*)(\s*)//) { $i = $1; $k = $2; $m = $3; $s = $4; print V "% From K: $k $m\n" if $V>1; &srckey($k .$m); $tokey = &trkey($k); print V "% tokey=\"$tokey\"\n" if $V>2; print $i . $tokey . $m . $s; next; } elsif ($line =~ /^[Ww]:/) { next; } elsif ($line =~ s/^"([A-G])(\w*)"//) { $n = $1; $m = $2; $t = &trchord($n); $m = '' if ($m eq 'b') && ($t eq 'C' || $t eq 'F'); $m = '' if ($m eq '#') && ($t eq 'B' || $t eq 'E'); print '"' . $t . $m . '"'; next; } elsif (($hdr,$sp) = ($line =~ /^([A-Z]:)(\s*)/)) { print $line; next line; } elsif ($line =~ s/^($M)($N)($L)(\s*)//) { $m = $1; $n = $2; $l = $3; $s = $4; $x = $m . &trnote($n) . $l . $s; $x =~ s/_([cf])/=$1/i if $m eq '_'; $x =~ s/\^([be])/=$1/i if $m eq '^'; print $x; next; } if ($line =~ s'^(.)'') {print $1; next; } print "Left: \"$line\"\n" if $D>1; } } } sub trnote { # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # $S[$I{$_[0]} + $steps] || "$n#"; } sub trchord { $F= 'trchord'; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # uc(substr(&trnote($_[0]),0,1)); local($frkey) = @_; local($tokey,$frsemi,$tosemi,$tomode); $frsemi = $Key2Semis{$frkey}; $tosemi = $frsemi + $trsemis; print "%$F From \"$frsemi\" to \"$tosemi\"\n" if $V>3; $tokey = $Semis2Key{$tosemi}; print "%$F From \"$frkey\" to \"$tokey\"\n" if $V>3; unless ($tokey) { $tomode = $trgMode; $tomode = '' if lc($tomode) eq 'major'; $tokey = "$trgTonic$tomode$trgAcc"; print "%$F From \"$frkey\" to \"$tokey\"\n" if $V>3; } $tokey = "$trgTonic$tomode$trgAcc" unless %tokey; print "%$F From \"$frkey\" to \"$tokey\"\n" if $V>3; return $tokey; } sub trkey { $F = 'trkey'; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # uc(substr(&trnote($_[0]),0,1)); local($frkey) = @_; local($tokey,$frsemi,$tosemi,$tomode); $frsemi = $Key2Semis{$frkey}; $tosemi = $frsemi + $trsemis; print "%$F From \"$frsemi\" to \"$tosemi\"\n" if $V>2; $tokey = $Semis2Key{$tosemi}; print "%$F From \"$frkey\" to \"$tokey\"\n" if $V>2; unless ($tokey) { $tomode = $trgMode; $tomode = '' if lc($tomode) eq 'major'; $tokey = "$trgTonic$tomode$trgAcc"; print "%$F From \"$frkey\" to \"$tokey\"\n" if $V>2; } return $tokey; } sub transdata { $F = 'transdata'; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # print V "% $F()\n" if $V>2; unless ($steps) { $steps = ord(uc $trgTonic) - ord(uc $srcTonic); $steps += 7 while $steps < 0; $steps -= 7 while $steps > 7; print V "% steps = $steps\n" if $V>1; } else { print V "% steps = $steps (old value used)\n" if $V>1; } } sub srckey { # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Process the source key, setting various globals to indicate the old key, # # the scale and semitone intervals, and the direction on the staff. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # local($tkey) = @_; local($tonic,$mode,$acc); if ($tkey =~ s/([A-G][#b]*)\s*//i) { $tonic = $1; print V "% tonic: $tonic\n" if $V>2; } else { print V "% No tonic.\n" if $V>2; } if ($tkey =~ s/([A-Z]+)\s*//i) { $mode = $1; print V "% mode: $mode\n" if $V>2; } else { print V "% No mode.\n" if $V>2; } if ($tkey =~ s/([_=^][A-G]*)\s*//i) { $acc = $1; print V "% acc: $acc\n" if $V>2; } else { print V "% No accidentals.\n" if $V>2; } print V "% left: \"$tkey\"\n" if $V>2 && $tkey; $mode = 'major' unless $mode || $add; $srcTonic = $tonic; $srcMode = $mode; $trgAcc = $acc; print V "% Source key: $srcTonic $srcMode $srcAcc\n" if $V>2; &transdata() if $trgTonic; } sub trgkey { # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Process the target key, setting various globals to indicate the new key, # # the scale and semitone intervals, and the direction on the staff. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # local($tkey) = @_; local($tonic,$mode,$acc); if ($tkey =~ s/([A-G][b^]*)\s*//i) { $tonic = $1; print V "% tonic: $tonic\n" if $V>2; } else { print V "% No tonic.\n" if $V>2; } if ($tkey =~ s/([A-Z]+[#b])\s*//i) { $mode = $1; print V "% mode: $mode\n" if $V>2; } else { print V "% No mode.\n" if $V>2; } if ($tkey =~ s/([_=^][A-G]*)\s*//i) { $acc = $1; print V "% acc: $acc\n" if $V>2; } else { print V "% No accidentals.\n" if $V>2; } $mode = 'major' unless $mode || $add; print V "% left: \"$tkey\"\n" if $V>2 && $tkey; $trgTonic = $tonic; $trgMode = $mode; $trgAcc = $acc; print V "% Target key: $trgTonic $trgMode $trgAcc\n" if $V>2; &transdata() if $srcTonic; }