#!/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 ~/Music/moonlight.mid --size 3 --max 16 --shuf --pause en
# perl ngram-play --files ~/Music/twinkle-twinkle.mid --size 2 --out '' --image --thresh 2
# perl ngram-play --dir ~/Music/MIDI --size 3 --max 16 --shuf --pause en

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
    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
    single  => 0,        # Allow phrases seen only once
    one     => 0,        # Analyze phrases into one list
    image   => 0,        # Output GraphViz network diagram
    thresh  => 10,       # Threshold to show net edges
    ranp    => 0,        # Random patch instead of all piano
    patches => join(' ', @patches),
);
GetOptions( \%opts, 
    'help|?',
    'man',
    'dir=s',
    'files=s',
    'size=i',
    'max=i',
    'bpm=i',
    'dura=s',
    'out=s',
    'pause=s',
    'analyze=s',
    'loop=i',
    'weight',
    'shuf',
    'single',
    'one',
    'image',
    'thresh=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},
    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},
    single_phrases  => $opts{single},
    one_channel     => $opts{one},
    patches         => $opts{patches},
);

$mng->process;

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

    $mng->write;
}
else {
    warn ddc($mng->dura);
    warn ddc($mng->dura_net);
    warn ddc($mng->notes);
    warn ddc($mng->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 } ) {
    next unless $mng->note_net->{$edge} >= $opts{thresh};

    # Split the edge in two
    my ($i, $j) = split '-', $edge;

    # Convert midinum to ISO in the edge
    my @notes;
    for my $n (split ' ', $i) {
        push @notes, $mng->note_convert($n);
    }
    $i = join ' ', @notes;
    @notes = ();
    for my $n (split ' ', $j) {
        push @notes, $mng->note_convert($n);
    }
    $j = join ' ', @notes;

    $g->add_edge(from => $i, to => $j, label => $mng->note_net->{$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 } ) {
    next unless $mng->dura_net->{$edge} >= $opts{thresh};

    # Split the edge in two
    my ($i, $j) = split '-', $edge;

    # Convert midinum to ISO in the edge
    my @notes;
    for my $n (split ' ', $i) {
        push @notes, $mng->dura_convert($n);
    }
    $i = join ' ', @notes;
    @notes = ();
    for my $n (split ' ', $j) {
        push @notes, $mng->dura_convert($n);
    }
    $j = join ' ', @notes;

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

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