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

# Render a song to MIDI.

use List::Util qw(shuffle);
use Music::Chord::Note ();
use Music::Scales qw(get_scale_notes);
#use lib map { "$ENV{HOME}/sandbox/$_/lib" } qw(MIDI-Util Music-BachChoralHarmony Music-Duration); # local author libs
use Music::BachChoralHarmony ();
use MIDI::Util qw(setup_score set_chan_patch midi_format);
use Music::Duration ();

my $id  = shift || '000106b_'; # -1 selects a random song
my $bpm = shift || 90;

Music::Duration::tuple( 'wn', 'W', 5 ); # 5 notes in place of one whole note

#my $data_file = 'share/jsbach_chorals_harmony.data'; # local author files
#my $key_title = 'share/jsbach_BWV_keys_titles.txt';  # "
my $bach = Music::BachChoralHarmony->new(
#    data_file => $data_file,
#    key_title => $key_title,
);
my $song = $bach->parse;

if ($id eq -1) {
    my @ids = keys %$song;
    $id = $ids[ int rand @ids ];
}

# Redefine the progression for the given id:
$song = $song->{$id};

my $bassline;
my $melodyline;
my $chordline;

my @scale = get_scale_notes( C => 'chromatic' );
my $channel = 0;

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

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

for my $event ( $song->{events}->@* ) {
    my ($note) = midi_format( $event->{bass} );
    push @$bassline, $note . 3;

    my $size = count_ones( $event->{notes} );
    my $duration =
        $size == 1 ? 'wn'  :
        $size == 2 ? 'hn'  :
        $size == 3 ? 'thn' :
        $size == 4 ? 'qn'  : 'Wwn';
    my @notes;
    my $i = 0;
    for my $bit ( split //, $event->{notes} ) {
        my ($pitch) = midi_format( $scale[$i] );
        push @notes, [ $duration, $pitch . 5 ]
            if $bit;
        $i++;
    }
    push @$melodyline, shuffle @notes;

    my $chord = $event->{chord};
    $chord =~ s/M//;
    $chord =~ s/_//;
    $chord =~ s/d/dim/;
    $chord =~ s/dim6/dim/; # What is a dim6 chord?
    $chord =~ s/4/add4/;
    my @tone = midi_format( $cn->chord($chord) );
    push @$chordline, map { $_ } \@tone;
}

$score->synch(
    \&bass,
    \&melody,
    \&chords,
);

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

sub count_ones {
    my ($bits) = @_;
    $bits =~ tr/0//d;
    return length $bits;
}

sub bass {
    set_chan_patch( $score, $channel++, 34 );
    $score->n( 'wn', $_ ) for @$bassline;
    $score->n( 'wn', $bassline->[-1] );
}

sub melody {
    set_chan_patch( $score, $channel++, 73 );
    $score->n( @$_ ) for @$melodyline;
}

sub chords {
    set_chan_patch( $score, $channel++, 4 );
    $score->n( 'wn', @$_ ) for @$chordline;
}
