#!/usr/bin/env perl

# Play a random rock progression!

use strict;
use warnings;

use Data::Dumper::Compact qw(ddc);
use Data::Dataset::ChordProgressions;
use List::MoreUtils qw(first_index);
use lib map { "$ENV{HOME}/sandbox/$_/lib" } qw(MIDI-Util);
use MIDI::Util qw(setup_score midi_format);
use Music::Chord::Note;
use Music::Scales qw(get_scale_notes);

my $reps  = shift || 2;
my $sects = shift || 'Avm-DcM-Evm-DcM'; # <note><verse|chorus><major|minor> v=verse, c=chorus, M=major, m=minor

my @sections = split /-/, $sects;

my $octave = 4;

my $score = setup_score();

my $cn = Music::Chord::Note->new;

my %data = Data::Dataset::ChordProgressions::as_hash();

# Lists of rock > scale > type chord progressions
my @major_verses   = @{ $data{rock}{major}{verse} };
my @minor_verses   = @{ $data{rock}{minor}{verse} };
my @major_choruses = @{ $data{rock}{major}{chorus} };
my @minor_choruses = @{ $data{rock}{minor}{chorus} };

for my $sect (@sections) {
    my ($note, $section, $scale, @pool);
    if ($sect =~ /^([A-G][#b]?)(v|c)(M|m)$/) {
        ($note, $section, $scale) = ($1, $2, $3);
        if ($scale eq 'M') {
            if ($section eq 'v') {
                @pool = @major_verses;
            }
            else {
                @pool = @major_choruses;
            }
            $scale = 'major';
        }
        else {
            if ($section eq 'v') {
                @pool = @minor_verses;
            }
            else {
                @pool = @minor_choruses;
            }
            $scale = 'minor';
        }
    }

    my %note_map;
    @note_map{ get_scale_notes('C', $scale) } = get_scale_notes($note, $scale);

    my $progression = $pool[int rand @pool];
    # Transpose the chords
    (my $named = $progression->[0]) =~ s/([A-G][#b]?)/$note_map{$1}/g;
    print "$named, $progression->[1]\n";
    my @chords = split /-/, $named;
    for my $j (1 .. $reps) {
        for my $chord (@chords) {
            # Find any "Chord/bass-note" chords
            my @slash = split /\//, $chord;
            # Get the notes of the chord
            my @notes = $cn->chord_with_octave($slash[0], $octave);
            # If there is a bass note...
            if ($slash[1]) {
                my $bass = $slash[1];
                my $o = $octave;
                # Does the bass note exist in the chord?
                my $i = first_index { /^$bass/ } @notes;
                # If so, set the octave and splice-out the note
                if ($i >= 0) {
                    ($o = $notes[$i]) =~ s/^[A-G][#b]?(\d+)$/$1/;
                    $o--;
                    splice @notes, $i, 1;
                }
                # Prepend the bass note and octave to the chord notes
                unshift @notes, $bass . $o;
            }
            @notes = midi_format(@notes);
#            print "\t", ddc(\@notes);
            $score->n('wn', @notes);
        }
    }
}

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