#!/usr/bin/perl
#
# abctrs [interval] [file]..
#
# abc transpose by (multiples of) semitones.
#
# Read an abc file (or passage from stdin), and write output that  has
# the  notes,  keys  and  chords  shifted  by the given interval.  The
# interval is in half-steps (semitones). An initial '-' meaning "down"
# and '+' means "up" (the default).
#
# This is highly experimental ...

$| = 1;
if ($ARGV[0] =~ /([-+]*)(\d+)([#b]*)/) {
	$interval = int("$1$2");
	$shorfl   = $3 || '#';	# Use sharps or flats?
	shift;
} else {
	$interval = 1;	# Default for Bb instruments.
}
@FN = ( # Note names using flats.
	"C,","_D,","D,","_E,","E,","F,","_G,","G,","_A,","A,","_B,","B,",
	"C", "_D", "D", "_E", "E", "F", "_G", "G", "_A", "A", "_B", "B",  
	"c", "_d", "d", "_e", "e", "f", "_g", "g", "_a", "a", "_b", "b",  
	"c'","_d'","d'","_e'","e'","f'","_g'","g'","_a'","a'","_b'","b'",  
);
@SN = ( # Note names using sharps.
	"C,","^C,","D,","^D,","E,","F,","^F,","G,","^G,","A,","^A,","B,",
	"C", "^C", "D", "^D", "E", "F", "^F", "G", "^G", "A", "^A", "B", 
	"c", "^c", "d", "^d", "e", "f", "^f", "g", "^g", "a", "^a", "b", 
	"c'","^c'","d'","^d'","e'","f'","^f'","g'","^g'","a'","^a'","b'",
);
@FC = ( # Chord names using flats.
	"C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B",
);
@SC = ( # Chord names using sharps.
	"C","C#","D","D#","E","F","F#","G","G#","A","A#","B",
);
%KS = (
	'B'  => '^f#,^c#,^g#,^d#,^A#',
	'E'  => '^f#,^c#,^g#,^d#',
	'A'  => '^f#,^c#,^g#',
	'D'  => '^f#,^c#',
	'G'  => '^f#',
	'C'  => '',
	'F'  => '_Bb',
	'Bb' => '_Bb,_eb',
	'Eb' => '_Bb,_eb,_Ab',
	'Ab' => '_Bb,_eb,_Ab,_db',
	'Db' => '_Bb,_eb,_Ab,_db,_Gb',
);
for ($i=0; $i<@FN; $i++) {
	$IFN{$FN[$i]} = $i;	# Indices of flat  notes.
	$ISN{$SN[$i]} = $i;	# Indices of sharp notes.
}
for ($i=0; $i<@FC; $i++) {
	$IFC{$FC[$i]} = $i;	# Indices of flat  chords.
	$ISC{$SC[$i]} = $i;	# Indices of sharp chords.
}
for $k (keys %KS) {
	for $a (split $KS{$k}) {
	}
}

$A = '[\^=_]*';			# Accidentals (notes).
$M = '[b#]*';			# Modifications (chords, keys).
$N = '[A-Ga-g][\',]*';	# Note.
$L = '[/\d.\<>]*';		# Length.

line:
for $line (<>) {
	while ($line) {
		if ($line =~ s/^(\s+)//) {print $1}
		if ($line =~ s/^(K:\s*)($N$M)(\s*)//) {
			$i = $1; $k = $2;  $s = $3;
			print $i . &trchord($k) . $s;
			next;
		} elsif ($line =~ s/^"([A-G][b#]?)(\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/^($A)($N)($L)(\s*)//) {
			 $m = $1; $n = $2;  $l = $3; $s = $4;
			 print $m . &trnote($n) . $l . $s;
			 next;
		}
		if ($line =~ s'^(.)'')          {print $1; next;
		}
		print "Left: \"$line\"\n" if $D>1;
	}
}

sub trnote {
	local($x) = @_;
	local($i,$j,$k);
	if ($shorfl eq '#') {
		$i = $ISN{$x} || $IFN{$x};
		$j = $i + $interval;
		$k = $SN[$j];
#		$SN[$I{$_[0]} + $interval] || "${n}#";
	} else {
		$i = $IFN{$x} || $ISN{$x};
		$j = $i + $interval;
		$k = $FN[$j];
#		$FN[$I{$_[0]} + $interval] || "${n}#";
	}
	$k;
}

sub trchord {
	local($x) = @_;
	local($i,$j,$k);
#	uc(substr(&trnote($_[0]),0,1));
	if ($shorfl eq '#') {
		$i = $ISC{$x} || $IFC{$x};
		$j = $i + $interval;
		while ($j <  0) {$j += 12}
		while ($j > 11) {$j -= 12}
		$k = $SC[$j];
#		uc($SC[$ISC{$x} + $interval] || "${n}#");
	} else {
		$i = $IFC{$x} || $ISC{$x};
		$j = $i + $interval;
		while ($j <  0) {$j += 12}
		while ($j > 11) {$j -= 12}
		$k = $SC[$j];
#		uc($FC[$IFC{$x} + $interval] || "${n}b");
	}
	$k;
}
