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

use strict;
use warnings;
use utf8;

package Music::ChordBot::Song;

=head1 NAME

Music::ChordBot::Song - Generate ChordBot songs.

=head1 SYNOPSIS

    use Music::ChordBot::Song;
    song "All Of Me";
    tempo 105;

    section "All Of Me 1";
    style "Chicago";

    C 4; C; E7; E7; A7; A7; Dm7; Dm7;
    E7; E7; Am7; Am7; D7; D7; Dm7; G7;

    section "All Of Me 2";
    style "Swingatron";

    C 4; C; E7; E7; A7; A7; Dm7; Dm7;
    Dm7; Ebdim7; Em7; A9; Dm7b5; G13;
    C 2; Ebdim7; Dm7; G7;

=head1 DESCRIPTION

Music::ChordBot::Song exports a number of subroutines that can be used
to construct a CordBot song. Upon program termination, the song is
written out to standard output in JSON format, suitable for import into
the ChordBot app.

=cut

our $VERSION = 0.01;

use Music::ChordBot::Opus;
use Music::ChordBot::Opus::Section;

our @EXPORT = qw( song chord section tempo style );
use base 'Exporter';

my $song;
my $section;

=head1 SUBROUTINES

=head2 song I<title>

Starts a new song with the given title.

=cut

sub song($) {
    _export() if $song;
    $song = Music::ChordBot::Opus->new( name => shift );
    undef $section;
}

=head2 tempo I<bpm>

Sets the tempo in beats per minute.

=cut

sub tempo($) {
    $song->tempo(@_);
}

=head2 section I<name>

Starts a song section. A section groups a number of bars with chords.
Each section can have a style associated although it is common to have
a single style for the whole song.

=cut

sub section($) {
    $section = Music::ChordBot::Opus::Section->new( name => shift );
    $song->add_section( $section );
}

=head2 style I<preset>

Associate the given style to the current section. For a list of
presets, see http://chordbot.com/style-lookup.php .

=cut

sub style($) {
    $section->set_style(@_);
}

=head2 chord I<key>, I<type>, I<duration>

Append a chord with given key, type and duration. Note that duration
is measured in number of beats. The three arguments may also be
specified in a single string argument, space separated.

You can specify a bass note for the chord by separating the key and
bass with a slash. E.g., C<"C/B"> denotes a C chord with B bass.

=cut

sub chord($) {
    $section->add_chord( @_ );
}

# Automatically export the song at the end of the program.
sub END {
    _export() if $song;
}

sub json { $song->json }

sub _export {
    binmode( STDOUT, ':utf8');
    print STDOUT $song->json, "\n";
}

=head1 QUICK ACCESS CHORDS

For convenience, subroutines are exported for quick access to chords.
So instead of

  chord "C", "Maj", 4;

you can also write:

  C 4;

If you omit the duration it will use the duration of the previous
 chord:

  C 4; C; F; G;		# same as C 4; C 4; F 4; G 4;

The subroutine name is the key of the chord, optionally followed by a
chord modifier. So C<C> is C major, C<Cm> is C minor, and so on.

Chord keys are A B C D E F G Ab Bb Db Eb Gb Ais Cis Dis Fis Gis.

Modifiers are m 7 m7 maj7 9 11 13 auf 7b5 m7b5 dim dim7.

=cut

my $_key = "C";
my $_mod = "Maj";
my $_dur = 4;

sub _chord {
    my ( $key, $mod, $dur ) = @_;
    $_dur = $dur if $dur;
    $section->add_chord( $key, $mod, $_dur  );
}

for my $key ( qw( A B C D E F G
		  Ab Bb Db Eb Gb
		  Ais Cis Dis Fis Gis
	    ) ) {
    my $k2 = $key =~ /^(.)is$/ ? "$1#" : $key;

    my %m = (
       ""      => "Maj",
       "m"     => "Min",
       "7"     => "7",
       "m7"    => "Min7",
       "maj7"  => "Maj7",
       "9"     => "9",
       "11"    => "11",
       "13"    => "13",
       "aug"   => "Aug",
       "7b5"   => "7(b5)",
       "m7b5"  => "Min7(b5)",
       "dim"   => "Dim",
       "dim7"  => "Dim7",
    );

    no strict 'refs';
    while ( my ($k, $t) = each( %m ) ) {
	*{ __PACKAGE__ . '::' . $key.$k } =
	  sub { &_chord( $k2, $t, $_[0] ) };
	push( @EXPORT, $key.$k );
    }
}

# Rest 'chord'.
sub NC { &_chord( "A", "Silence", $_[0] ) }
*S = \&NC;
push( @EXPORT, "S", "NC" );

=head1 DISCLAIMER

There is currently NO VALIDATION of argument values. Illegal values
will result in program crashes and songs that cannot be imported, or
played, by ChordBot.

=head1 AUTHOR, COPYRIGHT & LICENSE

See L<Music::ChordBot>.

=cut

1;