#!/usr/bin/perl

# FILENAME
# tranpose_abc.pl

# AUTHOR
# Matthew J. Fisher

# REVISION HISTORY
# Version 1: June 16, 2001.

# DESCRIPTION
# This is a Perl script for transposing abc files.
# Permission to copy, redistribute, modify, etc.
# is granted without restriction -- especially if
# you feel like converting this to a CGI script.

# INSTRUCTIONS
# This program is designed to be run from the command line. To
# run it, you will need to have a Perl distribution installed 
# on your computer, e.g.,
#
#   ActivePerl
#   http://www.activestate.com/
#   http://www.activestate.com/ASPN/Downloads/ActivePerl/
#
# Usage: transpose_abc.pl <input_file> <output_file> <offset>";
# Example: transpose_abc.pl song1.abc song2.abc 3";
# Offset is given in semitones, and may be positive
# or negative.";

# BUGS AND AREAS FOR IMPROVEMENT
# 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.

$INPUT_FILE = $ARGV[0]; # input filename
$OUTPUT_FILE = '>' . $ARGV[1]; # output filename

$offset = $ARGV[2]; # how many semitones to transpose

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

$old_key;
$new_key;

&open_files;
&do_work();
&close_files;

# subroutines follow this line

sub open_files {

	open(OUTPUT_FILE) or print("file not found: $OUTPUT_FILE");
	open(INPUT_FILE) or print OUTPUT_FILE "file not found: $INPUT_FILE";

}

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

sub do_work {

	$numLines = 0;
	foreach $line (<INPUT_FILE>) {
		chomp($line);
		print OUTPUT_FILE &transpose_line($line) . "\n";
	}

	close(INPUT_FILE);
}

sub transpose_line {

	$_ = shift(@_);

	# return comment lines unchanged
	if (/^%/) {
		return $_;
	}

	# 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 ($comment ne '') {
				$comment = ' % ' . $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 "\nOriginal key: *" . $old_display_key . "*\n\n";
			foreach $hash_key (sort keys %old_key_signature) {
				print " " . $hash_key . ": " . $old_key_signature{$hash_key} . "\n";
			}

			print "\nNew key: *" . $new_display_key . "*\n\n";
			foreach $hash_key (sort keys %new_key_signature) {
				print " " . $hash_key . ": " . $new_key_signature{$hash_key} . "\n";
			}

			# 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 {
	
	$key = shift(@_);
	$modifiers = shift(@_);

	$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;
			}
		}
	}

	return %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} = 0;
		$num = $nums{$note}; 
	} else {
		if ($old_accidentals{$note} != $accidental) {
			if ($accidental != 0) {
				$old_accidentals{$note} = $accidental;
			}
		}
		$num = $nums{$note} + $old_accidentals{$note}; 
	}

	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;
}

