#!/usr/bin/env perl

# Evolve barycentic chords

use AI::Genetic::Pro ();
use List::MoreUtils qw(uniq);
use MIDI::Util qw(setup_score);
use Music::Interval::Barycentric qw(evenness_index);

my $max   = shift || 16;
my $bpm   = shift || 100;
my $patch = shift || 0;

my $top = 8; # Integer above the highest evenness_index
my $threshold = 5; # Allowed top - evenness_index
my $base = 59; # Base of middle C MIDI values

my $notes = [ ([0 .. 11]) x 3 ]; # vectors of chromatc scales

my $score = setup_score(bpm => $bpm, patch => $patch);

my $ga = AI::Genetic::Pro->new(        
    -fitness         => \&fitness,     # fitness function
    -terminate       => \&terminate,   # terminate function
    -type            => 'listvector',  # type of chromosome
    -population      => 100,           # population
    -crossover       => 0.9,           # probab. of crossover
    -mutation        => 0.5,           # probab. of mutation
    -parents         => 2,             # number  of parents
    -selection       => ['Roulette'],  # selection strategy
    -strategy        => ['PMX'],       # crossover strategy
    -cache           => 1,             # cache results
    -history         => 1,             # remember best results
    -preserve        => 1,             # remember the bests
    -variable_length => 0,             # turn variable length OFF
);

$ga->init($notes);
$ga->evolve(100);

for my $fit ($ga->getFittest($max)) {
    $score->n('qn', map { $base + $_ } @{ get_chromosome($ga, $fit) });
    $score->r('qn');
}
$score->write_score("$0.mid");

# "The higher the value, the [greater] the chance of an individual passing its genes on in future generations through mating (crossover)."
sub fitness {
    my ($ga, $chromosome) = @_;

    $chromosome = get_chromosome($ga, $chromosome);

    return 0
        if @$chromosome > uniq(@$chromosome); # Duplicates not allowed

    # Chords with adjacent half-steps are not allowed
    for my $i (@$chromosome) {
        for my $j (@$chromosome) {
            return 0
                if ($j == $i + 1) || ($j == $i - 1);
        }
    }

    return $top - evenness_index($chromosome); # "Even" chords are deemed fittest
}

# Stop evolving if above the threshold
sub terminate {
    my ($ga) = @_;
    return $ga->as_value($ga->getFittest) > $threshold ? 1 : 0;
}

sub get_chromosome {
    my ($ga, $chromosome) = @_;
    $chromosome ||= $ga->getFittest;
    return [ split /_+/, $ga->as_string($chromosome) ];
}
