#!/usr/bin/perl
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#NAME
#   trabc
#
#SYNOPSIS
# trabc offset [input_file [output_file]]
#
#AUTHOR
#  Matthew J. Fisher
#  Modified for stdin/stdout by John Chambers
#2005-12:
#  Modified by Doug <dsreyn2@comcast.net> to handle accidentals better
#  Permission  to  copy,  redistribute,  modify,  etc.   is  granted  without
#  restriction.
#
#REVISION HISTORY
#  Version 1:   16 Jun 2001
#  Version 2.0: 12 May 2005
#
#DESCRIPTION
#  This is a Perl script for transposing abc files.
#
#  This program is designed to be run from the command line.
#
#  Offset is given in semitones, and may be positive or negative.";
#
#EXAMPLES
#  trabc 3 <song1.abc >song2.abc
#
#BUGS AND AREAS FOR IMPROVEMENT
#  This program currently only transposes whole tunes,  because  it  requires
#  the K: line to determine the old key. We need a way to give the K: info on
#  the command line, so we can transpose fragments.
#
#  Areas for improvement include proper handling  of  double  sharps,  double
#  flats,  and  key  signature  modifiers.  As things stand now, pitches with
#  these things in the input file are correctly transposed in terms of pitch.
#  But  the  way the pitch is encoded in the output file may need tweaking to
#  satisfy a true nerd's desire to see double-flats, double sharps,  and  key
#  signature modifiers in the output.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #

$| = 1;
($P = $0) =~ s"^.*/"";	# This program's name, without directories
$V = $ENV{"V_$P"} || $ENV{"D_$P"} || 1;
open(V,">&STDERR");		# Verbosity output file.
print "% $P V=$V.\n" if $V>0;

$offset      = shift;	# how many semitones to transpose
$INPUT_FILE  = shift;	# input filename
$OUTPUT_FILE = shift;	# output filename
print V "$P: offset=$offset\n" if $V>1;
print V "INPUT_FILE=\"$INPUT_FILE\"\n" if $V>1;
print V "OUTPUT_FILE=\"$OUTPUT_FILE\"\n" if $V>1;

if ($offset eq '') {
	print "\nUsage: trabc <input_file> <output_file> <offset>";
	print "\n\nExample: trabc song1.abc song2.abc 3";
	print "\n\nOffset is given in semitones, and may be positive or negative.";
	die "\n";
}

%accidental = (		# Maps numerical accidental to ABC accidental
	-2 => '__',
	-1 => '_',
	 0 => '',
	 1 => '^',
	 2 => '^^',
);

#$old_key;
#$new_key;

&open_files;
&do_work();
&close_files;

# subroutines follow this line

sub open_files {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	if ($OUTPUT_FILE) {
		open(STDOUT,">$OUTPUT_FILE")
			or die "$P: Can't write \"$OUTPUT_FILE\" ($!)\n";
	}
	if ($INPUT_FILE) {
		open(STDIN,$INPUT_FILE)
			or die "$P: Can't read \"$INPUT_FILE\" ($!)\n";
	}

}

sub close_files {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	close(OUTPUT_FILE);
#	close(INPUT_FILE);
}

sub do_work {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	$numLines = 0;
	foreach $line (<>) {
		$line =~ s/[\r\s]+$//;
		print &transpose_line($line) . "\n";
	}

}

sub transpose_line {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	$_ = shift(@_);

	return '' unless $_;		# Return blank lines unchanged
	return $_ if (/^%/);		# Return comment lines unchanged

	# Transpose key in K: header line
	# Return other header lines, part labels, etc., unchanged
	# Header lines, part labels, etc. start with [a-z,A-Z]:

	if (/^[a-z]\:/i) {

		if (!(/^K:/i)) {

			return $_ ;

		} else {

			# split K: header line into $label and $comment
			($label, $comment) = split(/%/, $_, 2);
			if (defined $comment && $comment ne '') {
				$comment = ' % ' . $comment;
			} else {
				$comment = '';
			}

			# split $label into $label, $delim, $old_key
			($label, $delim, $old_display_key) = split /(:\s*)/, $label, 3;

			# split $old_display_key into $old_display_key, $modifiers
			($old_display_key, $old_modifiers) = split /\s/, $old_display_key, 2;

			# standardize the $old_key to its relative major, if needed
			$old_key = &relative_major($old_display_key);

			# get the $new_key as a major key, which we'll use internally
			$new_key = &transpose_key($old_key);

			# get the $new_display_key, something matching $old_display_key
			$new_display_key = &get_new_display_key($old_display_key, $new_key);

			# transpose the key signature modifiers
			$new_modifiers = lc(&transpose_line($old_modifiers));
			$new_modifiers =~ s/[\'\,]//g;

			# I haven't quite thought through how to implement modifiers
			# in the new key. So for now, I'm going to ignore them.
			# The pitches will transpose correctly, but the notation will
			# be done in the unmodified new key.
			#
			$new_modifiers = ''; # this line goes away eventually....

			# get old and new key signatures
			%old_key_signature = get_key_signature($old_key, $old_modifiers);
			%new_key_signature = get_key_signature($new_key, $new_modifers);

			print "%\n% Original key: " . $old_display_key . "\n%\n" if $V>2;
			foreach $hash_key (sort keys %old_key_signature) {
				print "% " . $hash_key . ": " . $old_key_signature{$hash_key} . "\n" if $V>2;
			}

			print "%\n% New key: " . $new_display_key . "\n%\n" if $V>2;
			foreach $hash_key (sort keys %new_key_signature) {
				print "% " . $hash_key . ": " . $new_key_signature{$hash_key} . "\n" if $V>2;
			}

			# return new K: header line
			return $label . $delim . $new_display_key . ' ' . $new_modifiers . $comment

		}

	}

	# split off end-of-line comments for later use
	$comment = '';
	($music, $comment) = split(/%/, $_, 2);
	if ($comment ne '') {
		$comment = ' % ' . $comment;
	}

	# initialize or reset the the accidentals hashes
	%old_accidentals = (%old_key_signature);
	%new_accidentals = (
		c => 'none',
		d => 'none',
		e => 'none',
		f => 'none',
		g => 'none',
		a => 'none',
		b => 'none'
	);

	# read tokens from $music, transpose them, and
	# append them onto $new_music

	# while characters left in $music
	# read a character
	# append it to existing token
	# or transpose the existing token
	# then append it to $new_music
	# and then start a new token

	@music = split //, $music;
	$new_music = '';

	$char = '';
	$token = '';

	$is_chord = 0;
	$is_note = 0;
	$is_other = 0;

	foreach $char (@music) {
		if ($char eq '"') {
			if ($is_chord == 1) {
				# chord is ending
				# append this last $char, and output $token
				# then reset $is_chord and $token
				$token .= $char;
				$new_music .= &transpose_chord($token);
				$is_chord = 0;
				$token = '';
			} else {
				# chord is beginning
				# output previous $token and start a new one
				if ($is_note == 1) {
					$new_music .= &transpose_note($token);
					$is_note = 0;
				} else {
					$new_music .= $token;
				}
				$is_chord = 1;
				$token = $char;
			}
		} else {
			if ($char eq '|') {
				# reset accidentals
				%old_accidentals = (%old_key_signature);
				%new_accidentals = (
					c => 'none',
					d => 'none',
					e => 'none',
					f => 'none',
					g => 'none',
					a => 'none',
					b => 'none'
				);
			}
			if ($is_note == 1) {
				$_ = $char;
				if (
					(/[\',\,]/) ||
					(($token eq '^') || ($token eq '_')) ||
					(($token eq '^^') || ($token eq '__')) ||
					($token eq '=')
				) {
					# note is continuing
					$token .= $char;
				} else {
					# note ended with previous $char
					$new_music .= &transpose_note($token);
					$token = $char;
					$_ = $char;
					if (/[\^,\_,a-g]/i) {
						# new note is beginning
						$is_note = 1;
					} else {
						$is_note = 0;
					}
				}
			} else {
				if ($is_chord == 0) {
					$_ = $char;
					if (/[\=,\^,\_,a-g]/i) {
						# note is beginning
						$new_music .= $token;
						$token = $char;
						$is_note = 1;
					} else {
						$token .= $char;
					}
				} else {
					$token .= $char;
				}
			}
		}
	}
	$new_music .= $token;
	return $new_music . $comment;
}

sub relative_major {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	$_ = shift(@_);

	# these key names correspond to those recognized by abc2midi
	if (/C$/||/CMaj$/||/DDor$/||/EPhr$/||/FLyd$/
		|| /GMix$/||/Am$/||/AMin$/||/AAeo$/||/BLoc$/
	) {
		return 'C';
	}
	if (/F$/||/FMaj$/||/GDor$/||/APhr$/||/BbLyd$/
		|| /CMix$/||/Dm$/||/DMin$/||/DAeo$/||/ELoc$/
	) {
		return 'F';
	}
	if (/Bb$/||/BbMaj$/||/CDor$/||/DPhr$/||/EbLyd$/
		|| /FMix$/||/Gm$/||/GMin$/||/GAeo$/||/ALoc$/
	) {
		return 'Bb';
	}
	if (/Eb$/||/EbMaj$/||/FDor$/||/GPhr$/||/AbLyd$/
		|| /BbMix$/||/Cm$/||/CMin$/||/CAeo$/||/DLoc$/
	) {
		return 'Eb';
	}
	if (/Ab$/||/AbMaj$/||/BbDor$/||/CPhr$/||/DbLyd$/
		|| /EbMix$/||/Fm$/||/FMin$/||/FAeo$/||/GLoc$/
	) {
		return 'Ab';
	}
	if (/Db$/||/DbMaj$/||/EbDor$/||/FPhr$/||/GbLyd$/
		|| /AbMix$/||/Bbm$/||/BbMib$/||/BbAeo$/||/CLoc$/
	) {
		return 'Db';
	}
	if (/C#$/||/C#Maj$/||/D#Dor$/||/E#Phr$/||/F#Lyd$/
		|| /G#Mix$/||/A#m$/||/A#Min$/||/A#Aeo$/||/B#Loc$/
	) {
		return 'C#';
	}
	if (/Gb$/||/GbMaj$/||/AbDor$/||/BbPhr$/||/CbLyd$/
		|| /DbMix$/||/Ebm$/||/EbMin$/||/EbAeo$/||/FLoc$/
	) {
		return 'Gb';
	}
	if (/F#$/||/F#Maj$/||/G#Dor$/||/A#Phr$/||/BLyd$/
		|| /C#Mix$/||/D#m$/||/D#Min$/||/D#Aeo$/||/E#Loc$/
	) {
		return 'F#';
	}
	if (/Cb$/||/CbMaj$/||/DbDor$/||/EbPhr$/||/FbLyd$/
		|| /GbMix$/||/Abm$/||/AbMin$/||/AbAeo$/||/BbLoc$/
	) {
		return 'Cb';
	}
	if (/B$/||/BMaj$/||/C#Dor$/||/D#Phr$/||/ELyd$/
		|| /F#Mix$/||/G#m$/||/G#Min$/||/G#Aeo$/||/A#Loc$/
	) {
		return 'B';
	}
	if (/E$/||/EMaj$/||/F#Dor$/||/G#Phr$/||/ALyd$/
		|| /BMix$/||/C#m$/||/C#Min$/||/C#Aeo$/||/D#Loc$/
	) {
		return 'E';
	}
	if (/A/||/AMaj$/||/BDor$/||/C#Phr$/||/DLyd$/
		|| /EMix$/||/F#m$/||/F#Min$/||/F#Aeo$/||/G#Loc$/
	) {
		return 'A';
	}
	if (/D$/||/DMaj$/||/EDor$/||/F#Phr$/||/GLyd$/
		|| /AMix$/||/Bm$/||/BMin$/||/BAeo$/||/C#Loc$/
	) {
		return 'D';
	}
	if (/G$/||/GMaj$/||/ADor$/||/BPhr$/||/CLyd$/
		|| /DMix$/||/Em$/||/EMin$/||/EAeo$/||/F#Loc$/
	) {
		return 'G';
	}

}

sub get_new_display_key {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	$old_display_key = shift(@_);
	$new_key = shift(@_);

	@display_keys = (
		['C','CMaj','DDor','EPhr','FLyd','GMix','Am','AMin','AAeo','BLoc'],
		['F','FMaj','GDor','APhr','BbLyd','CMix','Dm','DMin','DAeo','ELoc'],
		['Bb','BbMaj','CDor','DPhr','EbLyd','FMix','Gm','GMin','GAeo','ALoc'],
		['Eb','EbMaj','FDor','GPhr','AbLyd','BbMix','Cm','CMin','CAeo','DLoc'],
		['Ab','AbMaj','BbDor','CPhr','DbLyd','EbMix','Fm','FMin','FAeo','GLoc'],
		['Db','DbMaj','EbDor','FPhr','GbLyd','AbMix','Bbm','BbMib','BbAeo','CLoc'],
		['C#','C#Maj','D#Dor','E#Phr','F#Lyd','G#Mix','A#m','A#Min','A#Aeo','B#Loc'],
		['Gb','GbMaj','AbDor','BbPhr','CbLyd','DbMix','Ebm','EbMin','EbAeo','FLoc'],
		['F#','F#Maj','G#Dor','A#Phr','BLyd','C#Mix','D#m','D#Min','D#Aeo','E#Loc'],
		['Cb','CbMaj','DbDor','EbPhr','FbLyd','GbMix','Abm','AbMin','AbAeo','BbLoc'],
		['B','BMaj','C#Dor','D#Phr','ELyd','F#Mix','G#m','G#Min','G#Aeo','A#Loc'],
		['E','EMaj','F#Dor','G#Phr','ALyd','BMix','C#m','C#Min','C#Aeo','D#Loc'],
		['A','AMaj','BDor','C#Phr','DLyd','EMix','F#m','F#Min','F#Aeo','G#Loc'],
		['D','DMaj','EDor','F#Phr','GLyd','AMix','Bm','BMin','BAeo','C#Loc'],
		['G','GMaj','ADor','BPhr','CLyd','DMix','Em','EMin','EAeo','F#Loc'],
	);

	# Find $old_display_key in the array, and note the column in which you found it.
	# Find $new_key in the array, and note the row in which you found it.
	# In that row, go the column where you found $old_display_key.
	# That's your new display key.

#	$row_where_found;
#	$col_where_found;

	for ($row = 0; $row <= 14; $row++) {
		for ($col = 0; $col <= 9; $col++) {
			if ($display_keys[$row][$col] eq $old_display_key) {
				$col_where_found = $col;
			}
		}
	}
	for ($row = 0; $row <= 14; $row++) {
		if ($display_keys[$row][0] eq $new_key) {
			$row_where_found = $row;
		}
	}

	return $display_keys[$row_where_found][$col_where_found];

}

sub get_key_signature {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Takes a key (such as "Eb" or "Fm") and returns a hash that maps the letters #
# 'a'..'b' to the accidental for that note in the key signature. This is only #
# used in verbose messages, so if $V<2 it can probably just return.           #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	$key = shift(@_);
	$modifiers = shift(@_);
	local($note);
#
##	return () if $V < 2;
	$key =~ s/\#/sharp/;
	$key =~ s/ //g;
#
	%sig = ( c => 0, d => 0, e => 0, f => 0, g => 0, a => 0, b => 0, );
	$_ = $key;
#
	SWITCH: {
		if (/C$/) {
			last SWITCH;
		}
		if (/F$/) {
			$sig{'b'} = -1;
			last SWITCH;
		}
		if (/Bb$/) {
			$sig{'b'} = -1;
			$sig{'e'} = -1;
			last SWITCH;
		}
		if (/Eb$/) {
			$sig{'b'} = -1;
			$sig{'e'} = -1;
			$sig{'a'} = -1;
			last SWITCH;
		}
		if (/Ab$/) {
			$sig{'b'} = -1;
			$sig{'e'} = -1;
			$sig{'a'} = -1;
			$sig{'d'} = -1;
			last SWITCH;
		}
		if (/Db$/) {
			$sig{'b'} = -1;
			$sig{'e'} = -1;
			$sig{'a'} = -1;
			$sig{'d'} = -1;
			$sig{'g'} = -1;
			last SWITCH;
		}
		if (/Csharp$/) {
			$sig{'f'} = 1;
			$sig{'c'} = 1;
			$sig{'g'} = 1;
			$sig{'d'} = 1;
			$sig{'a'} = 1;
			$sig{'e'} = 1;
			$sig{'b'} = 1;
			last SWITCH;
		}
		if (/Gb$/) {
			$sig{'b'} = -1;
			$sig{'e'} = -1;
			$sig{'a'} = -1;
			$sig{'d'} = -1;
			$sig{'g'} = -1;
			$sig{'c'} = -1;
			last SWITCH;
		}
		if (/Fsharp$/) {
			$sig{'f'} = 1;
			$sig{'c'} = 1;
			$sig{'g'} = 1;
			$sig{'d'} = 1;
			$sig{'a'} = 1;
			$sig{'e'} = 1;
			last SWITCH;
		}
		if (/Cb$/) {
			$sig{'b'} = -1;
			$sig{'e'} = -1;
			$sig{'a'} = -1;
			$sig{'d'} = -1;
			$sig{'g'} = -1;
			$sig{'c'} = -1;
			$sig{'f'} = -1;
			last SWITCH;
		}
		if (/B$/) {
			$sig{'f'} = 1;
			$sig{'c'} = 1;
			$sig{'g'} = 1;
			$sig{'d'} = 1;
			$sig{'a'} = 1;
			last SWITCH;
		}
		if (/E$/) {
			$sig{'f'} = 1;
			$sig{'c'} = 1;
			$sig{'g'} = 1;
			$sig{'d'} = 1;
			last SWITCH;
		}
		if (/A$/) {
			$sig{'f'} = 1;
			$sig{'c'} = 1;
			$sig{'g'} = 1;
			last SWITCH;
		}
		if (/D$/) {
			$sig{'f'} = 1;
			$sig{'c'} = 1;
			last SWITCH;
		}
		if (/G$/) {
			$sig{'f'} = 1;
			last SWITCH;
		}
	}
#
	@notes = ('c', 'd', 'e', 'f', 'g', 'a', 'b');
#
	for ($index = 0; $index <= 6; $index++) {
		$_ = $modifiers;
		SWITCH: {
			if (/\^\^($notes[$index])/) {
				$sig{$1} = 2;
				last SWITCH;
			}
			if (/\^($notes[$index])/) {
				$sig{$1} = 1;
				last SWITCH;
			}
			if (/\_\_($notes[$index])/) {
				$sig{$1} = -2;
				last SWITCH;
			}
			if (/\_($notes[$index])/) {
				$sig{$1} = -1;
				last SWITCH;
			}
			if (/\=($notes[$index])/) {
				$sig{$1} = -0;
				last SWITCH;
			}
		}
	}
	for $note (keys %sig) {
		$acc{$note} = $accidental{$sig{$note}}
	}
	if ($V>3) {
		for $note (sort keys %acc) {
			print V "% acc: $acc{$note}$note\n";
		}
	}
	return %acc;	# Was %sig;
}

sub transpose_note {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	$note = shift(@_);
	$octave = 0;
	$accidental = 0;
	$_ = $note;

	if (/[A-G]/) {
		$octave = -1;
		while (m/\,/g) {
			$octave--;
		}
	} else {
		while (m/\'/g) {
			$octave++;
		}
	}

	while (m/\_/g) {
		$accidental--;
	}

	while (m/\^/g) {
		$accidental++;
	}

	if (/\=/) {
		$natural = 1;
		$accidental = 0;
	} else {
		$natural = 0;
	};

	return &num_to_note(
		&note_to_num(
			$note,
			$natural,
			$accidental
		),
		$octave,
		$offset
	);

}

sub num_to_note {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	my $num = shift(@_);
	my $octave = shift(@_);
	my $offset = shift(@_);

	if ($offset < 0) {
		if (($num - (-$offset)%12) < 0) {
			$octave -= (1 + int((-$offset)/12));
		}
	} else {
		$octave += int(($num + $offset)/12);
	}

	$num = ($num + $offset)%12;

	my $key = $new_key;
	$key =~ s/\#/sharp/;
	$key =~ s/ //g;

	%notes = ();

	$_ = $key;

	SWITCH: {
		if (/C$/) {
			%notes = (
				0 => 'c',
				1 => 'sharpc',
				2 => 'd',
				3 => 'flate',
				4 => 'e',
				5 => 'f',
				6 => 'sharpf',
				7 => 'g',
				8 => 'sharpg',
				9 => 'a',
				10 => 'flatb',
				11 => 'b',
			);
			last SWITCH;
		}
		if (/F$/) {
			%notes = (
				0 => 'c', # c
				1 => 'flatd', # flatd
				2 => 'd', # d
				3 => 'flate', # flate
				4 => 'e', # e
				5 => 'f', # f
				6 => 'flatg', # flatg
				7 => 'g', # g
				8 => 'flata', # flata
				9 => 'a', # a
				10 => 'b', # b
				11 => 'naturalb', # naturalb
			);
			last SWITCH;
		}
		if (/Bb$/) {
			%notes = (
				0 => 'c', # c
				1 => 'flatd', # flatd
				2 => 'd', # d
				3 => 'e', # flate
				4 => 'naturale', # e
				5 => 'f', # f
				6 => 'flatg', # flatg
				7 => 'g', # g
				8 => 'flata', # flata
				9 => 'a', # a
				10 => 'b', # b
				11 => 'naturalb', # naturalb
			);
			last SWITCH;
		}
		if (/Eb$/) {
			%notes = (
				0 => 'c', # c
				1 => 'flatd', # flatd
				2 => 'd', # d
				3 => 'e', # flate
				4 => 'naturale', # e
				5 => 'f', # f
				6 => 'flatg', # flatg
				7 => 'g', # g
				8 => 'a', # flata
				9 => 'naturala', # a
				10 => 'b', # b
				11 => 'naturalb', # naturalb
			);
			last SWITCH;
		}
		if (/Ab$/) {
			%notes = (
				0 => 'c', # c
				1 => 'd', # flatd
				2 => 'naturald', # d
				3 => 'e', # flate
				4 => 'naturale', # e
				5 => 'f', # f
				6 => 'flatg', # flatg
				7 => 'g', # g
				8 => 'a', # flata
				9 => 'naturala', # a
				10 => 'b', # b
				11 => 'naturalb', # naturalb
			);
			last SWITCH;
		}
		if (/Db$/) {
			%notes = (
				0 => 'c', # c
				1 => 'd', # flatd
				2 => 'naturald', # d
				3 => 'e', # flate
				4 => 'naturale', # e
				5 => 'f', # f
				6 => 'g', # flatg
				7 => 'naturalg', # g
				8 => 'a', # flata
				9 => 'naturala', # a
				10 => 'b', # b
				11 => 'naturalb', # naturalb
			);
			last SWITCH;
		}
		if (/Csharp$/) {
			%notes = (
				0 => 'b', # c
				1 => 'c', # flatd
				2 => 'naturald', # d
				3 => 'd', # flate
				4 => 'naturale', # e
				5 => 'e', # f
				6 => 'f', # flatg
				7 => 'naturalg', # g
				8 => 'g', # flata
				9 => 'naturala', # a
				10 => 'a', # b
				11 => 'naturalb', # naturalb
			);
			last SWITCH;
		}
		if (/Gb$/) {
			%notes = (
				0 => 'naturalc', # c
				1 => 'd', # flatd
				2 => 'naturald', # d
				3 => 'e', # flate
				4 => 'naturale', # e
				5 => 'f', # f
				6 => 'g', # flatg
				7 => 'naturalg', # g
				8 => 'a', # flata
				9 => 'naturala', # a
				10 => 'b', # b
				11 => 'c', # naturalb
			);
			last SWITCH;
		}
		if (/Fsharp$/) {
			%notes = (
				0 => 'naturalc', # c
				1 => 'c', # flatd
				2 => 'naturald', # d
				3 => 'd', # flate
				4 => 'naturale', # e
				5 => 'e', # f
				6 => 'f', # flatg
				7 => 'naturalg', # g
				8 => 'g', # flata
				9 => 'naturala', # a
				10 => 'a', # b
				11 => 'b', # naturalb
			);
			last SWITCH;
		}
		if (/Cb$/) {
			%notes = (
				0 => 'naturalc', # c
				1 => 'd', # flatd
				2 => 'naturald', # d
				3 => 'e', # flate
				4 => 'f', # e
				5 => 'naturalf', # f
				6 => 'g', # flatg
				7 => 'naturalg', # g
				8 => 'a', # flata
				9 => 'naturala', # a
				10 => 'b', # b
				11 => 'c', # naturalb
			);
			last SWITCH;
		}
		if (/B$/) {
			%notes = (
				0 => 'naturalc', # c
				1 => 'c', # flatd
				2 => 'naturald', # d
				3 => 'd', # flate
				4 => 'e', # e
				5 => 'sharpe', # f
				6 => 'f', # flatg
				7 => 'naturalg', # g
				8 => 'g', # flata
				9 => 'naturala', # a
				10 => 'a', # b
				11 => 'b', # naturalb
			);
			last SWITCH;
		}
		if (/E$/) {
			%notes = (
				0 => 'naturalc', # c
				1 => 'c', # flatd
				2 => 'naturald', # d
				3 => 'd', # flate
				4 => 'e', # e
				5 => 'sharpe', # f
				6 => 'f', # flatg
				7 => 'naturalg', # g
				8 => 'g', # flata
				9 => 'a', # a
				10 => 'sharpa', # b
				11 => 'b', # naturalb
			);
			last SWITCH;
		}
		if (/A$/) {
			%notes = (
				0 => 'naturalc', # c
				1 => 'c', # flatd
				2 => 'd', # d
				3 => 'sharpd', # flate
				4 => 'e', # e
				5 => 'sharpe', # f
				6 => 'f', # flatg
				7 => 'naturalg', # g
				8 => 'g', # flata
				9 => 'a', # a
				10 => 'sharpa', # b
				11 => 'b', # naturalb
			);
			last SWITCH;
		}
		if (/D$/) {
			%notes = (
				0 => 'naturalc', # c
				1 => 'c', # flatd
				2 => 'd', # d
				3 => 'sharpd', # flate
				4 => 'e', # e
				5 => 'sharpe', # f
				6 => 'f', # flatg
				7 => 'g', # g
				8 => 'sharpg', # flata
				9 => 'a', # a
				10 => 'sharpa', # b
				11 => 'b', # naturalb
			);
			last SWITCH;
		}
		if (/G$/) {
			%notes = (
				0 => 'c', # c
				1 => 'sharpc', # flatd
				2 => 'd', # d
				3 => 'sharpd', # flate
				4 => 'e', # e
				5 => 'sharpe', # f
				6 => 'f', # flatg
				7 => 'g', # g
				8 => 'sharpg', # flata
				9 => 'a', # a
				10 => 'sharpa', # b
				11 => 'b', # naturalb
			);
			last SWITCH;
		}
	}

	$note = $notes{$num};

	if (($key eq 'Csharp') && ($num == 0)) {
		# zero is c
		# in this key, c is written as b-sharp
		# so it actually falls in the octave below
		$octave -= 1;
	}

	$note_letter = substr($note, -1, 1);
	$accidental = substr($note, 0, (length($note) -1));

	if (length($note) > 1) {
		if ($new_accidentals{$note_letter} eq 'none') {
			$new_accidentals{$note_letter} = $accidental;
		} else {
			if ($new_accidentals{$note_letter} eq $accidental) {
				$note = $note_letter;
			} else {
				$new_accidentals{$note_letter} = $accidental;
			}
		}
	} else {
		if ($new_accidentals{$note_letter} ne 'none') {

			$new_accidentals{$note_letter} = 'none';

			if ($new_key_signature{$note_letter} == -1) {
				$note = 'flat' . $note;
			}

			if ($new_key_signature{$note_letter} == 0) {
				$note = 'natural' . $note;
			}

			if ($new_key_signature{$note_letter} == 1) {
				$note = 'sharp' . $note;
			}

		}
	}

	$note =~ s/sharp/\^/;
	$note =~ s/flat/\_/;
	$note =~ s/natural/\=/;

	if ($octave > 0) {
		$note .= "'" x $octave;
	}
	if ($octave < 0) {
		$note = uc $note;
		$note .= "," x (-1 - $octave);
	}

	return $note;

}

sub note_to_num {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	my $note = shift(@_);
	my $natural = shift(@_);
	my $accidental = shift(@_);

	my $num;

	$note =~ s/[\=,\^,\_,\',\,]//g;
	$note = lc $note;

	%nums = (
		c => 0,
		d => 2,
		e => 4,
		f => 5,
		g => 7,
		a => 9,
		b => 11,
	);

	if ($natural == 1) {
		$old_accidentals{$note} = '';
		$num = $nums{$note};
	} else {
		if ($old_accidentals{$note} != $accidental) {
			if ($accidental != '') {
				$old_accidentals{$note} = $accidental;
			}
		}
#		$num = $nums{$note} + $old_accidentals{$note};
	    my $acc_shift = 0;
	    if ($old_accidentals{$note} eq '__') {
		$acc_shift = -2;
	    }
	    elsif ($old_accidentals{$note} eq '_') {
		$acc_shift = -1;
	    }
	    elsif ($old_accidentals{$note} eq '^') {
		$acc_shift = 1;
	    }
	    elsif ($old_accidentals{$note} eq '^^') {
		$acc_shift = 2;
	    }

	    $num = $nums{$note} + $acc_shift;
	}
	return $num;
}

sub transpose_chord {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	$chord = shift(@_);
	$chord =~ s/\"//g;
	@chord_chars = split //, $chord;
	$root = '';
	$bass_root = '';
	$voicing = '';
	$in_a_root = 1;
	foreach $char (@chord_chars) {
		if ($in_a_root == 1) {
			$_ = $char;
			if (/[A-G,b,\#]/) {
				if ($voicing eq '') {
					$root .= $char;
				} else {
					$bass_root .= $char;
				}
			} else {
				$in_a_root = 0;
				$voicing .= $char;
			}
		} else {
			$voicing .= $char;
			$_ = $char;
			if (/\//) {
				$in_a_root = 1;
			}
		}
	}
	if ($bass_root eq '') {
		return '"' . &transpose_key($root)
			. $voicing
			. '"'
		;
	} else {
		return '"' . &transpose_key($root)
			. $voicing
			. &transpose_key($bass_root)
			. '"'
		;
	}

}

sub transpose_key {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	%keys = (
		C => 0,
		Db => 1, Csharp => 1,
		D => 2,
		Eb => 3,
		E => 4,
		F => 5,
		Gb => 6, Fsharp => 6,
		G => 7,
		Ab => 8,
		A => 9,
		Bb => 10, Asharp => 10,
		B => 11, Cb => 11
	);
	$key = shift(@_);
	$key =~ s/\#/sharp/;
	$key =~ s/ //g;
	$key_number = ($keys{$key} + $offset)%12;
	ITERATION: while (($hash_key, $hash_value) = each %keys) {
		if ($hash_value == $key_number) {
			$key = $hash_key;
			last ITERATION;
		}
	}
	$key =~ s/sharp/\#/;
	return $key;
}
