#!/usr/bin/env perl
use strict;
use warnings;

# Use a circular list ("necklace") of chord transformations.
# Starting at position zero, move forward or backward along the necklace,
# transforming the current chord, and render the resulting progression as MIDI.

# Example:
# > perl transform-chain --r=1 --m=12 --bpm=90 --note=C --o=5 --t=O,P,T1,T-2,L,R --v
# > perl transform-chain --r=2 --m=8 --bpm=110 --note=A --o=5 --q=m --t=8 --v
# > perl transform-chain --r=1 --m=12 --bpm=100 --note=Bb --q=7 --t=8 --v
# > perl transform-chain --r=4 --t=7 --nobass --v
# > timidity %.mid

use lib map { "$ENV{HOME}/sandbox/$_/lib" } qw(MIDI-Util Music-Chord-Progression-Transform Music-MelodicDevice-Transposition); # local author libs

use Getopt::Long qw(GetOptions);
use MIDI::Util qw(setup_score);
use Music::Chord::Progression::Transform ();
use Music::MelodicDevice::Transposition ();

my %opt = (
    repeat     => 1,
    max        => 4,
    bpm        => 100,
    note       => 'G',
    octave     => 4,
    quality    => '',
    transforms => 'O,PRL,R,L,R,L,R', # mostly diatonic. integer = random
    bass       => 1,
    verbose    => 1,
);
GetOptions(\%opt,
    'repeat=i',
    'max=i',
    'bpm=i',
    'note=s',
    'octave=i',
    'quality=s',
    'transforms=s',
    'bass!',
    'verbose',
) or die("Error in command line arguments\n");

if ($opt{transforms} !~ /^\d+$/) {
    $opt{transforms} = [ split /,/, $opt{transforms} ];
}

my @bass; # collected by top()

my $score = setup_score(bpm => $opt{bpm});

$score->synch(
    \&top,
    \&bottom,
);

$score->write_score("$0.mid");

sub bottom {
    return unless $opt{bass};

    my $md = Music::MelodicDevice::Transposition->new;
    my $transposed = $md->transpose(-24, \@bass);

    for my $note (@$transposed) {
        $score->n('wn', $note);
    }
}

sub top {
    my $transform = Music::Chord::Progression::Transform->new(
        base_note     => $opt{note},
        base_octave   => $opt{octave},
        chord_quality => $opt{quality},
        transforms    => $opt{transforms},
        max           => $opt{max},
        verbose       => $opt{verbose},
    );
    my ($generated) = $transform->circular;

    # repeat though the chord progression
    for my $i (1 .. $opt{repeat}) {
        my $j = 0; # chord counter

        for my $chord (@$generated) {
            $j++;

            # add a midi formatted whole note to the score
            $score->n('wn', @$chord);

            # select the lowest note of the final chord for the bass
            if ($i >= $opt{repeat} && $j >= @$generated) {
                push @bass, $chord->[0];
            }
            # otherwise pick any chord note
            else {
                push @bass, $chord->[ int rand @$chord ];
            }
        }
    }
}
