#!/usr/bin/env perl
#
# Rhythm generator. Will require tweaks, look for NOTE notes.

use strict;
use warnings;
use MIDI::Simple;
use Music::Voss qw(bitchange);

# NOTE this list is tied to what the bitchange function outputs (and to
# the mappings in %duration_of); durations towards the middle will more
# commonly be selected than those at edges, mostly depending on the
# number of rollers, and less so to how many sides the "dice" used have.
my @possible_durations = qw(sn en sn qn);

# NOTE probably should not be changed, unless adding additional
# durations, or changing the keys. The keys are labels and could be
# changed to lilypond duration names or whatever.
my %duration_of = (
  'wn'  => 128,
  'hn'  => 64,
  'dhn' => 64 + 32,
  'qn'  => 32,
  'dqn' => 32 + 16,
  'en'  => 16,
  'den' => 16 + 8,
  'sn'  => 8,
  '32'  => 4,
  '64'  => 2,
  '128' => 1,         # anything smaller will require reworking things...
);

# NOTE this controls how many notes a complete rhythm must contain (as
# otherwise random results tend not fall on measure boundaries)
my $MEASURES = 8;
my $MAX_LEN  = 128 * $MEASURES;

my $ms = MIDI::Simple->new_score;
$ms->set_tempo(500000);
$ms->noop(qw/c1 f o5/);

for my $dur ( gen_rhythm() ) {
  $ms->n( $dur, "C" );
}

$ms->write_score('out.midi');

sub gen_rhythm {
  my @rhythm;
ATTEMPT: while (1) {
    # NOTE this should produce numbers equal to or a subset of
    # @possible_durations (min here is 0, max 3, depending on how the
    # coinflips turn out)
    my $durfn = bitchange(
      roll    => sub { int rand 2 },
      rollers => 3,
    );

    my $x   = 0;
    my $sum = 0;
    while (1) {
      my $roll = $durfn->( $x++ );
      push @rhythm, $possible_durations[$roll] // default_dur($roll);
      $sum += $duration_of{ $rhythm[-1] };
      if ( $sum == $MAX_LEN ) {
        last ATTEMPT;
      } elsif ( $sum > $MAX_LEN ) {
        @rhythm = ();
        next ATTEMPT;
      }
    }
  }
  return @rhythm;
}

sub default_dur {
  die "unable to lookup duration for ", $_[0], "\n";
}
