The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#! perl

# Author          : Johan Vromans
# Created On      : Tue Jan 15 15:59:16 2008
# Last Modified By: Johan Vromans
# Last Modified On: Wed Oct  5 12:00:16 2011
# Update Count    : 10
# Status          : Unknown, Use with caution!

package App::Music::PlayTab::LyChord;

use strict;
use warnings;

our $VERSION = "1.006";

use App::Music::PlayTab::Note;
use Carp;
use base qw(App::Music::PlayTab::Chord);

sub parse {
    my ($self, $chord) = @_;

    $self = $self->new unless ref($self);

    $self->{_unparsed} = $chord;
    $self->{_debug} = 1 if $chord =~ s/^\?//;
    $self->{_isrest} = 0;
    delete( $self->{bass} );

    my $key = $chord;
    my $mod = '';

    # Catch rests.
    if ( $chord =~ /(^[rs])(\d+\.*)?/ ) {
	$self->{_isrest} = 1;
	if ( defined $2 ) {
	    my ($dur, $xt) = $2 =~ /^(\d+)(\.*)$/;
	    $dur = $self->duration_base / $dur;
	    $dur *= 1.5 foreach split(//, $xt);
	    $self->{duration} = $dur;
	}
	return $self;
    }

    # Treat power chords as modifications.
    $chord =~ s;^([[a-g](?:es|is)?(\d+\.*)?)/;$1:/;;

    # Separate the chord key from the modifications.
    if ( $chord =~ /(^[a-g](?:es|is)?)(\d+\.*)?(?::(.*))?/ ) {
	$key = $1;
	$mod = $3;
	if ( defined $2 ) {
	    my ($dur, $xt) = $2 =~ /^(\d+)(\.*)$/;
	    $dur = $self->duration_base / $dur;
	    $dur *= 1.5 foreach split(//, $xt);
	    $self->{duration} = $dur;
	}
    }

    # Parse key.
    eval { $self->{key} = App::Music::PlayTab::Note->parse($key) };
    croak("Unrecognized pitch in chord: ".$self->{_unparsed})
      unless defined $self->{key};

    # Encodings: a bit is set in $chflags for every note in the chord.
    # The corresponding element of $chmods is 0 (natural), -1
    # (lowered), 1 (raised) or undef (suppressed).

    my $chflags = '';
    my @chmods = (0) x 14;

    # Assume major triad.
    vec($chflags,3,1) = 1;
    vec($chflags,5,1) = 1;
    $chmods[3] = 0;
    $chmods[5] = 0;

    # Then other modifications.
    while ( $mod && $mod ne '' ) {

	if ( $mod =~ /^maj7?(?:\.?(.*))?/ ) {	# Maj7
	    $mod = $+;
	    vec($chflags,7,1) = 1;
	    $chmods[7] = 1;
	    next;
	}
	if ( $mod =~ /^m(?:in)?7(?:\.?(.*))?/ ) {	# Minor triad 7
	    $mod = $+;
	    vec($chflags,7,1) = 1;
	    $chmods[7] = 0;
	    vec($chflags,3,1) = 1;
	    $chmods[3] = -1;
	    next;
	}
	if ( $mod =~ /^m(?:\.?(.*))?/ ) {	# Minor triad
	    $mod = $+;
	    vec($chflags,3,1) = 1;
	    $chmods[3] = -1;
	    next;
	}

	# Transform 7sus4 into something we can parse.
	$mod =~ s/^(\d+)sus(\d?)/sus$2.$1/;

	if ( $mod =~ /^sus2(?:\.(.*))?/ ) {	# Suspended second
	    $mod = $+;
	    vec($chflags,3,1) = 0;
	    undef $chmods[3];
	    next;
	}
	if ( $mod =~ /^sus4?(?:\.(.*))?/ ) {	# Suspended fourth
	    $mod = $+;
	    vec($chflags,4,1) = 1;	# does it?
	    undef $chmods[3];
	    $chmods[4] = 0;
	    next;
	}
	if ( $mod =~ /^aug(?:\.?(.*))?/ ) {		# Augmented
	    $mod = $+;
	    vec($chflags,5,1) = 1;
	    $chmods[5] = 1;
	    next;
	}
	if ( $mod =~ /^dim(?:\.?(.*))?/ ) {	# Diminished
	    $mod = $+;
	    vec($chflags,3,1) = 1;
	    vec($chflags,5,1) = 1;
	    vec($chflags,7,1) = 1;
	    $chmods[3] = -1;
	    $chmods[5] = -1;
	    $chmods[7] = -1;
	    next;
	}
#	if ( $mod =~ /^%(.*)/ ) {	# half-diminished 7
#	    $mod = $+;
#	    $chflags = '';
#	    vec($chflags,3,1) = 1;
#	    vec($chflags,5,1) = 1;
#	    vec($chflags,7,1) = 1;
#	    $chmods[3] = -1;
#	    $chmods[5] = -1;
#	    $chmods[7] = 0;
#	    next;
#	}
	if ( $mod =~ /^(2|5|6|7|9|10|11|13)([-+])?(?:\.(.*))?/ ) { # addition
	    $mod = $3;
	    # 13th implies 11th implies 9th implies 7th...
	    if ( $1 > 7 && !(vec($chflags,7,1)) ) {
		vec($chflags,7,1) = 1;
		$chmods[7] = 0;
	    }
	    if ( $1 > 10 && !(vec($chflags,9,1)) ) {
		vec($chflags,9,1) = 1;
		$chmods[9] = 0;
	    }
	    if ( $1 > 11 && !(vec($chflags,11,1)) ) {
		vec($chflags,11,1) = 1;
		$chmods[11] = 1;
	    }
	    vec($chflags,$1,1) = 1;
	    $chmods[$1] = 0;
	    if ( defined $2 ) {
		$chmods[$1] = ($2 eq '+') ? 1 : -1;
	    }
	    next;
	}
	if ( $mod =~ /^\^(\d+)(?:\.(.*))?/ ) {
	    $mod = $2;
	    vec($chflags,$1,1) = 1;
	    undef $chmods[$1];
	    next;
	}

	# Power chords.
	if ( $mod =~ /^\/(.+)/ ) {
	    my @ch = split(/\//, $1);
	    foreach my $c ( @ch ) {
	#	my $p = eval { App::Music::PlayTab::Note->parse($c) };
		my $p = eval { App::Music::PlayTab::Chord->parse($c) };
		croak("Unrecognized bass of chord: ".$self->{_unparsed})
		  unless defined $p;
		$self->{bass} ||= [];
		push(@{$self->{bass}}, $p);
	    }
	    last;
	}
	croak("Unrecognized modification of chord: ".$self->{_unparsed});
    }

    my @vec = (0);
    for ( 1..13 ) {
	next unless vec($chflags,$_,1);
	next unless defined $chmods[$_];
	push (@vec, (0,0,2,4,5,7,9,10,12,14,16,17,19,21)[$_]+$chmods[$_]);
    }

    $self->{vec} = [@vec];

    warn("=> Chord ", $self->{_unparsed}, ": ", $self->{key}->key,
	 " (", $self->{key}->name, ") [ @vec ]\n")
      if $self->{_debug};

    $self;
}

1;

__END__

=head1 NAME

App::Music::PlayTab::LyChord - Parse LilyPond chords.

=head1 DESCRIPTION

This is an internal module for the App::Music::PlayTab application.