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

# Use a circular list ("necklace") of Neo-Riemannian 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 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=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 midi_format);
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', midi_format($note));
    }
}

sub top {
    my $transform = Music::Chord::Progression::Transform->new(
        base_note   => $opt{note},
        base_octave => $opt{octave},
        base_scale  => $opt{scale},
        transforms  => $opt{transforms},
        max         => $opt{max},
        verbose     => $opt{verbose},
    );
    my $chords = $transform->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 ];
            }
        }
    }
}
