#!/usr/bin/env perl

# Make a new MIDI file of the top repeated note phrases of a list of
# MIDI files or a directory, and make a transition note network diagram.
#
# Examples:
# perl ngram-play --files eg/twinkle-twinkle.mid --size 2 --out '' --image
# perl ngram-play --files ~/Music/MIDI/moonlight.mid --size 3 --min 3 --pause en
# perl ngram-play --files eg/twinkle-twinkle.mid,~/Music/MIDI/moonlight.mid
# perl ngram-play --dir ~/Music/MIDI/Bach --size 4 --min 8 --max 64 --shuf

use strict;
use warnings;

use Data::Dumper::Compact 'ddc';
use File::Find::Rule;
use Getopt::Long;
use GraphViz2;
use MIDI::Ngram;
use Music::Note;

# General MIDI patches that are audible and aren't horrible
my @patches = qw(
    0 1 2 4 5 7 8 9
    13 16 21 24 25 26
    32 34 35 40 42 60
    68 69 70 71 72 73
    74 79
);

my %opts = (
    dir     => undef,    # Directory of MIDI files
    files   => undef,    # MIDI files to process
    size    => 2,        # ngram size
    min     => 2,        # Minimum repetitions
    max     => 20,       # 0 for all records
    bpm     => 100,      # Beats per minute
    dura    => 'hn qn en', # Note durations
    out     => "$0.mid", # Output MIDI file
    pause   => '',       # Insert a rest after each phrase
    analyze => '',       # Analyze all channels
    loop    => 4,        # Times to choose a weighted phrase
    weight  => 0,        # Use weighted counts to play
    shuf    => 0,        # Shuffle phrases
    one     => 0,        # Analyze phrases into one list
    image   => 0,        # Output GraphViz network diagram
    chan    => 0,        # Network diagram channel to render
    ranp    => 0,        # Random patch instead of all piano
    patches => join(' ', @patches),
);
GetOptions( \%opts, 
    'help|?',
    'man',
    'dir=s',
    'files=s',
    'size=i',
    'min=i',
    'max=i',
    'bpm=i',
    'dura=s',
    'out=s',
    'pause=s',
    'analyze=s',
    'loop=i',
    'weight',
    'shuf',
    'one',
    'image',
    'chan=i',
    'ranp',
    'patches=s',
) or die 'Failed GetOptions';

my @files;
@files = File::Find::Rule->file()->name('*.mid')->in($opts{dir})
    if $opts{dir};

# Turn string lists into arrayrefs
$opts{files}   = @files ? \@files : [ split /(?:\s+|\s*,\s*)/, $opts{files} ];
$opts{dura}    = [ split /(?:\s+|\s*,\s*)/, $opts{dura} ];
$opts{analyze} = [ split /(?:\s+|\s*,\s*)/, $opts{analyze} ];
$opts{patches} = [ split /(?:\s+|\s*,\s*)/, $opts{patches} ];

my $mng = MIDI::Ngram->new(
    in_file         => $opts{files},
    ngram_size      => $opts{size},
    min_phrases     => $opts{min},
    max_phrases     => $opts{max},
    bpm             => $opts{bpm},
    durations       => $opts{dura},
    out_file        => $opts{out},
    pause_duration  => $opts{pause},
    analyze         => $opts{analyze},
    random_patch    => $opts{ranp},
    loop            => $opts{loop},
    weight          => $opts{weight},
    shuffle_phrases => $opts{shuf},
    one_channel     => $opts{one},
    patches         => $opts{patches},
);

$mng->process;

if ( $opts{out} ) {
    my $playback = $mng->populate;
    print $playback;

    $mng->write;
}
else {
    print join "\n",
        'Durations: '             . ddc($mng->dura),
        'Duration network: '      . ddc($mng->dura_net),
        'Notes: '                 . ddc($mng->notes),
        'Note network: '          . ddc($mng->note_net),
        'Durations*Notes: '       . ddc($mng->dura_notes),
        'Duration*Note network: ' . ddc($mng->dura_note_net);
}

exit unless $opts{image};

# First notes
my $g = GraphViz2->new(
    global => { directed => 1 },
    node   => { shape => 'oval' },
    edge   => { color => 'grey' },
);

my %edges;

for my $edge ( keys %{ $mng->note_net->{$opts{chan}} } ) {
    # Split the edge in two
    my ($i, $j) = split '-', $edge;

    $g->add_edge(from => $i, to => $j, label => $mng->note_net->{$opts{chan}}{$edge})
        unless $edges{$edge}++;
}

$g->run( format => 'png', output_file => $0 . '-notes.png' );

# Now durations
$g = GraphViz2->new(
    global => { directed => 1 },
    node   => { shape => 'oval' },
    edge   => { color => 'grey' },
);

%edges = ();

for my $edge ( keys %{ $mng->dura_net->{$opts{chan}} } ) {
    # Split the edge in two
    my ($i, $j) = split '-', $edge;

    $g->add_edge(from => $i, to => $j, label => $mng->dura_net->{$opts{chan}}{$edge})
        unless $edges{$edge}++;
}

$g->run( format => 'png', output_file => $0 . '-durations.png' );

# Now duration*note
$g = GraphViz2->new(
    global => { directed => 1 },
    node   => { shape => 'oval' },
    edge   => { color => 'grey' },
);

%edges = ();

for my $edge ( keys %{ $mng->dura_note_net->{$opts{chan}} } ) {
    # Split the edge in two
    my ($i, $j) = split '-', $edge;

    $g->add_edge(from => $i, to => $j, label => $mng->dura_note_net->{$opts{chan}}{$edge})
        unless $edges{$edge}++;
}

$g->run( format => 'png', output_file => $0 . '-duration_note.png' );
