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

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

# Example:
# > perl nro-chain --r=1 --m=12 --bpm=90 --note=C --o=5 --t=I,T1,T-2,T3 --v
# > perl nro-chain --r=2 --m=8 --bpm=110 --note=A --o=5 --s=minor --t=8 --v
# > perl nro-chain --r=4 --t=7 --nobass --v
# > timidity %.mid

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

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

my %opt = (
    repeat    => 1,
    max       => 4,
    bpm       => 100,
    note      => 'G',
    octave    => 4,
    quality   => '',
    transform => 'I,T1,T2,T3', # integer = random
    semitones => 7,
    bass      => 1,
    verbose   => 0,
);
GetOptions(\%opt,
    'repeat=i',
    'max=i',
    'bpm=i',
    'note=s',
    'octave=i',
    'quality=s',
    'transform=s',
    'semitones=i',
    'bass!',
    'verbose',
) or die("Error in command line arguments\n");

if ($opt{transform} =~ /[IT]/) {
    $opt{transform} = [ split /,/, $opt{transform} ];
}

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', midi_format($note));
    }
}

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

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

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

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

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