package Music::Dice;
our $AUTHORITY = 'cpan:GENE';

# ABSTRACT: Define and roll musical dice

our $VERSION = '0.0206';

use Moo;
use strictures 2;
use Carp qw(croak);
use Games::Dice::Advanced ();
use List::Util::WeightedChoice qw(choose_weighted);
use MIDI::Util qw(midi_dump);
use Music::Duration::Partition ();
use Music::Scales qw(get_scale_notes get_scale_nums);
use Music::ToRoman ();
use Types::Standard qw(ArrayRef Int Str);
use namespace::clean;


has semitones => (
    is      => 'ro',
    isa     => sub { croak "$_[0] is not an integer" unless $_[0] =~ /^\d+$/ },
    default => sub { 12 },
);


has scale_note => (
    is      => 'ro',
    isa     => sub { croak "$_[0] is not a valid note name" unless $_[0] =~ /^[A-G][b#]?$/ },
    default => sub { 'C' },
);


has scale_name => (
    is      => 'ro',
    isa     => sub { croak "$_[0] is not a valid scale name" unless $_[0] =~ /^[a-z]+$/ },
    default => sub { 'chromatic' },
);


has flats => (
    is      => 'ro',
    isa     => sub { croak "$_[0] is not a boolean" unless $_[0] =~ /^[01]$/ },
    default => sub { 1 },
);


has beats => (
    is      => 'ro',
    isa     => sub { croak "$_[0] is not a positive number" unless $_[0] =~ /^[1-9]\d*$/ },
    default => sub { 4 },
);


has phrase_pool => (
    is => 'rw',
);


has phrase_weights => (
    is => 'rw',
);


has phrase_groups => (
    is => 'rw',
);


has octaves => (
    is      => 'ro',
    isa     => ArrayRef[Int],
    default => sub { [ 2 .. 6 ] },
);


has notes => (
    is => 'lazy',
);

sub _build_notes {
    my ($self) = @_;
    my $keypref = $self->flats ? 'b' : '#';
    my @notes = get_scale_notes($self->scale_note, $self->scale_name, 0, $keypref);
    return \@notes;
}


has intervals => (
    is => 'lazy',
);

sub _build_intervals {
    my ($self) = @_;
    my @nums = get_scale_nums($self->scale_name);
    my @intervals = map { $nums[$_] - $nums[$_ - 1] } 1 .. $#nums;
    push @intervals, $self->semitones - $nums[-1];
    if ($self->identity) {
        unshift @intervals, 0;
        push @intervals, $self->semitones;
    }
    return \@intervals;
}


has identity => (
    is      => 'ro',
    isa     => sub { croak "$_[0] is not a boolean" unless $_[0] =~ /^[01]$/ },
    default => sub { 0 },
);


has chord_triads => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(
            major
            minor
            diminished
            augmented
            custom
        )],
    },
);


has chord_triad_weights => (
    is      => 'ro',
    isa     => ArrayRef[Int],
    default => sub { [qw(2 2 1 1 1)] },
);


has chord_qualities_major => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(
            add2 sus2
            add4 sus4
            -5
            -6 6
            M7 7
            add9
        )],
    },
);


has chord_qualities_major_7 => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        no warnings 'qw';
        [qw(
            7sus4 7b5 7#5
            69
            M79
            7b9 9 7#9
            7(b9,13) 7(9,13)
            9b5
            M11 11 7#11
            M13 13 7#13
        )],
    },
);


has chord_qualities_minor => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(
            madd4
            m6
            m7
        )],
    },
);


has chord_qualities_minor_7 => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        no warnings 'qw';
        [qw(
            m7b5 m7#5
            m9
            m7(9,11)
            m11
            m13
        )],
    },
);


has chord_qualities_diminished => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(
            dim6
            dim7
        )],
    },
);


has chord_qualities_augmented => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(
            augM7 aug7
        )],
    },
);


has chord_qualities_augmented_7 => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        no warnings 'qw';
        [qw(
            aug9
        )],
    },
);


has modes => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(
            ionian
            dorian
            phrygian
            lydian
            mixolydian
            aeolian
            locrian
        )],
    },
);


has ionian_mask => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(I ii iii IV V vi viio)],
    },
);


has dorian_mask => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(i ii III IV v vio VII)],
    },
);


has phrygian_mask => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(i II III iv vo VI vii)],
    },
);


has lydian_mask => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(I II iii ivo V vi vii)],
    },
);


has mixolydian_mask => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(I ii iiio IV v vi VII)],
    },
);


has aeolian_mask => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(i iio III iv v VI VII)],
    },
);


has locrian_mask => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub {
        [qw(io II iii iv V VI vii)],
    },
);


has tonnetzen => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub { [qw(P R L N S H)],
    },
);


has tonnetzen_7 => (
    is      => 'ro',
    isa     => ArrayRef[Str],
    default => sub { [qw(S23 S32 S34 S43 S56 S65 C32 C34 C65)],
    },
);


has rhythmic_phrase_constraints => (
    is      => 'ro',
    isa     => ArrayRef[Int],
    default => sub { [ 3, 4, 5 ] },
);


sub BUILD {
    my ($self, $args) = @_;
    if (exists $args->{phrase_pool} && !ref $args->{phrase_pool} && $args->{phrase_pool} eq 'all') {
        $self->phrase_pool([ sort keys %{ midi_dump('length') } ]);
    }
    else {
        $self->phrase_pool([qw(wn dhn hn dqn qn den en)]);
    }
    $self->phrase_weights([ (1) x @{ $self->phrase_pool } ]);
    $self->phrase_groups([ (1) x @{ $self->phrase_pool } ]);
}


sub octave {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->octaves, [ (1) x @{ $self->octaves } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub note {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->notes, [ (1) x @{ $self->notes } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub interval {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->intervals, [ (1) x @{ $self->intervals } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub note_chromatic {
    my ($self) = @_;
    my $d = sub {
        my $keypref = $self->flats ? 'b' : '#';
        my $choices = [ get_scale_notes($self->scale_note, 'chromatic', 0, $keypref) ];
        return choose_weighted($choices, [ (1) x @$choices ])
    };
    return Games::Dice::Advanced->new($d);
}


sub interval_chromatic {
    my ($self) = @_;
    my $d = sub {
        my $choices = [ (1) x $self->semitones ];
        return choose_weighted($choices, $choices);
    };
    return Games::Dice::Advanced->new($d);
}


sub note_major {
    my ($self) = @_;
    my $d = sub {
        my $keypref = $self->flats ? 'b' : '#';
        my $choices = [ get_scale_notes($self->scale_note, 'major', 0, $keypref) ];
        return choose_weighted($choices, [ (1) x @$choices ])
    };
    return Games::Dice::Advanced->new($d);
}


sub interval_major {
    my ($self) = @_;
    my $d = sub {
        my $choices = [qw(2 2 1 2 2 2 1)];
        return choose_weighted($choices, [ (1) x @$choices ]);
    };
    return Games::Dice::Advanced->new($d);
}


sub note_minor {
    my ($self) = @_;
    my $d = sub {
        my $keypref = $self->flats ? 'b' : '#';
        my $choices = [ get_scale_notes($self->scale_note, 'minor', 0, $keypref) ];
        return choose_weighted($choices, [ (1) x @$choices ])
    };
    return Games::Dice::Advanced->new($d);
}


sub interval_minor {
    my ($self) = @_;
    my $d = sub {
        my $choices = [qw(2 1 2 2 1 2 2)];
        return choose_weighted($choices, [ (1) x @$choices ]);
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_triad {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->chord_triads, $self->chord_triad_weights)
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_quality_major {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->chord_qualities_major, [ (1) x @{ $self->chord_qualities_major } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_quality_major_7 {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->chord_qualities_major_7, [ (1) x @{ $self->chord_qualities_major_7 } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_quality_minor {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->chord_qualities_minor, [ (1) x @{ $self->chord_qualities_minor } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_quality_minor_7 {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->chord_qualities_minor_7, [ (1) x @{ $self->chord_qualities_minor_7 } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_quality_diminished {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->chord_qualities_diminished, [ (1) x @{ $self->chord_qualities_diminished } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_quality_augmented {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->chord_qualities_augmented, [ (1) x @{ $self->chord_qualities_augmented } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_quality_augmented_7 {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->chord_qualities_augmented_7, [ (1) x @{ $self->chord_qualities_augmented_7 } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub chord_quality_triad_roll {
    my ($self, $note, $triad) = @_;
    my $quality = '';
    if ($triad eq 'custom') {
        my @custom;
        my $item = $self->unique_item([ $note ]);
        push @custom, $item;
        push @custom, $self->unique_item([ $note, $item ]);
        $quality = " @custom";
    }
    else {
        my $method = 'chord_quality_' . $triad;
        $quality = $self->$method->roll;
    }
    return $quality;
}


sub mode {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->modes, [ (1) x @{ $self->modes } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub ionian {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->ionian_mask, [ (1) x @{ $self->ionian_mask } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub dorian {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->dorian_mask, [ (1) x @{ $self->dorian_mask } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub phrygian {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->phrygian_mask, [ (1) x @{ $self->phrygian_mask } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub lydian {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->lydian_mask, [ (1) x @{ $self->lydian_mask } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub mixolydian {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->mixolydian_mask, [ (1) x @{ $self->mixolydian_mask } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub aeolian {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->aeolian_mask, [ (1) x @{ $self->aeolian_mask } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub locrian {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->locrian_mask, [ (1) x @{ $self->locrian_mask } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub mode_degree_triad_roll {
    my ($self, $mode) = @_;
    my $roman = $self->$mode->roll;
    my $mtr = Music::ToRoman->new(scale_name => $mode);
    my ($degree, $triad) = $mtr->get_scale_degree($roman);
    return $degree, $triad;
}


sub tonnetz {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->tonnetzen, [ (1) x @{ $self->tonnetzen } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub tonnetz_7 {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->tonnetzen_7, [ (1) x @{ $self->tonnetzen_7 } ])
    };
    return Games::Dice::Advanced->new($d);
}

## RHYTHMS ##


sub rhythmic_value {
    my ($self) = @_;
    my $d = sub {
        return choose_weighted($self->phrase_pool, [ (1) x @{ $self->phrase_weights } ])
    };
    return Games::Dice::Advanced->new($d);
}


sub rhythmic_phrase {
    my ($self) = @_;
    my $mdp = Music::Duration::Partition->new(
        size    => $self->beats,
        pool    => $self->phrase_pool,
        weights => $self->phrase_weights,
        groups  => $self->phrase_groups ,
    );
    my $d = sub {
        return $mdp->motif;
    };
    return Games::Dice::Advanced->new($d);
}


sub rhythmic_phrase_constrained {
    my ($self) = @_;
    my $mdp = Music::Duration::Partition->new(
        size    => $self->beats,
        pool    => $self->phrase_pool,
        weights => $self->phrase_weights,
        groups  => $self->phrase_groups ,
    );
    my $d = sub {
        my $motif;
        while (!$motif || !grep { $_ == @$motif } @{ $self->rhythmic_phrase_constraints }) {
            $motif = $mdp->motif;
        }
        return $motif;
    };
    return Games::Dice::Advanced->new($d);
}

## UTILITY ##


sub unique_item {
    my ($self, $excludes, $items) = @_;
    $items ||= $self->notes;
    my $item = '';
    while (!$item || grep { $_ eq $item } @$excludes) {
        $item = $items->[ int rand @$items ];
    }
    return $item;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Music::Dice - Define and roll musical dice

=head1 VERSION

version 0.0206

=head1 SYNOPSIS

  use Music::Dice ();
  my $d = Music::Dice->new;

  my $roll = $d->note->roll;        # given scale_name
  $roll = $d->interval->roll;
  $roll = $d->note_chromatic->roll; # particular scale rolls
  $roll = $d->interval_chromatic->roll;
  $roll = $d->note_major->roll;
  $roll = $d->interval_major->roll;
  $roll = $d->note_minor->roll;
  $roll = $d->interval_minor->roll;
  $roll = $d->chord_triad->roll;    # chord flavor rolls
  $roll = $d->chord_quality_major->roll;
  $roll = $d->chord_quality_major_7->roll;
  $roll = $d->chord_quality_minor->roll;
  $roll = $d->chord_quality_minor_7->roll;
  $roll = $d->chord_quality_diminished->roll;
  $roll = $d->chord_quality_augmented->roll;
  $roll = $d->chord_quality_augmented_7->roll;
  $roll = $d->chord_quality_triad_roll('C', 'major');
  $roll = $d->mode->roll;           # mode rolls given scale_note
  $roll = $d->ionian->roll;
  $roll = $d->dorian->roll;
  $roll = $d->phrygian->roll;
  $roll = $d->lydian->roll;
  $roll = $d->mixolydian->roll;
  $roll = $d->aeolian->roll;
  $roll = $d->locrian->roll;
  $roll = $d->tonnetz->roll;        # neo-riemannian tonnetz
  $roll = $d->tonnetz_7->roll;
  $roll = $d->rhythm->roll;         # rhythmic phrase rolls
  $roll = $d->rhythmic_phrase->roll;
  $roll = $d->rhythmic_phrase_constrained->roll;

  # for example:
  my $phrase = $d->rhythmic_phrase->roll;
  my @notes  = map { $d->note->roll } 1 .. @$phrase;
  my @triads = map { $d->chord_triad->roll } 1 .. @$phrase;
  my @named  = map { "$notes[$_] $triads[$_] | $phrase->[$_]" } 0 .. $#$phrase;
  print join("\n", @named), "\n";

=head1 DESCRIPTION

C<Music::Dice> defines and rolls musical dice.

=head1 ATTRIBUTES

=head2 semitones

  $semitones = $d->semitones;

The number of semitones in the notes of the chromatic scale.

Default: C<12>

=head2 scale_note

  $note = $d->scale_note;

The (uppercase) tonic of the scale.

Default: C<C>

=head2 scale_name

  $note = $d->scale_name;

The (lowercase) name of the scale.

Default: C<chromatic>

=head2 flats

  $flats = $d->flats;

Use either flats or sharps in the returned notes.

Default: C<1> (use flats not sharps)

=head2 beats

  $beats = $d->beats;

The number of quarter-note beats in a rhythmic phrase.

Default: C<4> (standard measure)

=head2 phrase_pool

  $pool = $d->phrase_pool;
  $d->phrase_pool(\@pool);

The pool of durations in a rhythmic phrase.

Default: C<[wn dhn hn dqn qn den en]>

The keyword C<all> may also be given, which will use the keys of the
C<MIDI::Simple::Length> hash (all the known MIDI-Perl durations).

=head2 phrase_weights

  $phrase_weights = $d->phrase_weights;
  $d->phrase_weights(\@weights);

The weights of the duration pool, in a rhythmic phrase.

Default: C<1> for each B<phrase_pool> member (equal probability)

=head2 phrase_groups

  $phrase_groups = $d->phrase_groups;
  $d->phrase_groups(\@groups);

The groups of the duration pool, in a rhythmic phrase.

Default: C<1> for each B<phrase_pool> member (equal probability)

=head2 octaves

  $octaves = $d->octaves;

The octaves to choose from.

Default: C<[2 3 4 5 6]>

=head2 notes

  $notes = $d->notes;

The user-definable named pitches from which to choose.

This list is computed, if the B<scale_note> and B<scale_name> are
given, and the B<notes> are I<not> given in the object constructor.

Default: C<[C Db D Eb E F Gb G Ab A Bb B]> (the chromatic scale)

Any scale may be given in the constructor. For accidentals, either
sharps (C<#>) or flats (C<b>) may be provided.

Additionally, midi pitch numbers may be used.

=head2 intervals

  $intervals = $d->intervals;

Return the note B<intervals>.

This list is computed, if the B<scale_name> is given, and the
B<intervals> are I<not> given in the object constructor.

Default: C<1> for each of the defined B<semitones>

=head2 identity

  $identity = $d->identity;

To include unison (C<0>) and the octave (the number of B<semitones>),
in the list of intervals.

Default: C<0>

=head2 chord_triads

  $chord_triads = $d->chord_triads;

The named chord triads, from which to choose. Rolling C<custom> means
that three individual notes, or two intervals must be chosen.

Default:

  major
  minor
  diminished
  augmented
  custom

=head2 chord_triad_weights

  $chord_triad_weights = $d->chord_triad_weights;

The chord triad weights.

Default: C<[2 2 1 1 1]> (major and minor are twice as likely)

=head2 chord_qualities_major

  $chord_qualities_major = $d->chord_qualities_major;

The named chord qualities that specify a single note addition or
transformation to major chords.

Please see L<Music::Chord::Note> for the known chords.

Default:

  add2 sus2
  add4 sus4
  -5
  -6 6
  M7 7
  add9

=head2 chord_qualities_major_7

  $chord_qualities_major_7 = $d->chord_qualities_major_7;

The named chord qualities that specify additions or transformations to
7th chords.

Please see L<Music::Chord::Note> for the known chords.

Default:

  7sus4 7b5 7#5
  69
  M79
  7b9 9 7#9
  7(b9,13) 7(9,13)
  9b5
  M11 11 7#11
  M13 13 7#13

=head2 chord_qualities_minor

  $chord_qualities_minor = $d->chord_qualities_minor;

The named chord qualities that specify a single note addition or
transformation to minor chords.

Please see L<Music::Chord::Note> for the known chords.

Default:

  madd4
  m6
  m7

=head2 chord_qualities_minor_7

  $chord_qualities_minor_7 = $d->chord_qualities_minor_7;

The named chord qualities that specify additions or transformations to
minor 7th chords.

Please see L<Music::Chord::Note> for the known chords.

Default:

  m7b5 m7#5
  m9
  m7(9,11)
  m11
  m13

=head2 chord_qualities_diminished

  $chord_qualities_diminished = $d->chord_qualities_diminished;

The named chord qualities that specify a single note addition or
transformation to diminished chords.

Please see L<Music::Chord::Note> for the known chords.

Default:

  dim6
  dim7

=head2 chord_qualities_augmented

  $chord_qualities_augmented = $d->chord_qualities_augmented;

The named chord qualities that specify a single note addition or
transformation to augmented chords.

Please see L<Music::Chord::Note> for the known chords.

Default:

  augM7 aug7

=head2 chord_qualities_augmented_7

  $chord_qualities_augmented_7 = $d->chord_qualities_augmented_7;

The named chord qualities that specify additions or transformations to
augmented 7th chords.

Please see L<Music::Chord::Note> for the known chords.

Default:

  aug9

=head2 modes

  $modes = $d->modes;

The named modes, from which to choose.

Default:

  ionian
  dorian
  phrygian
  lydian
  mixolydian
  aeolian
  locrian

=head2 ionian_mask

  $ionian_mask = $d->ionian_mask;

The mask of Ionian mode triad types.

Default: C<[I ii iii IV V vi viio]>

=head2 dorian_mask

  $dorian_mask = $d->dorian_mask;

The mask of Dorian mode triad types.

Default: C<[i ii III IV v vio VII]>

=head2 phrygian_mask

  $phrygian_mask = $d->phrygian_mask;

The mask of Phrygian mode triad types.

Default: C<[i II III iv vo VI vii]>

=head2 lydian_mask

  $lydian_mask = $d->lydian_mask;

The mask of Lydian mode triad types.

Default: C<[I II iii ivo V vi vii]>

=head2 mixolydian_mask

  $mixolydian_mask = $d->mixolydian_mask;

The mask of Mixolydian mode triad types.

Default: C<[I ii iiio IV v vi VII]>

=head2 aeolian_mask

  $aeolian_mask = $d->aeolian_mask;

The mask of Aeolian mode triad types.

Default: C<[i iio III iv v VI VII]>

=head2 locrian_mask

  $locrian_mask = $d->locrian_mask;

The mask of Locrian mode triad types.

Default: C<[io II iii iv V VI vii]>

=head2 tonnetzen

  $tonnetzen = $d->tonnetzen;

The named tonnetz values for triad transformations.

Default:

  P  # Parallel
  R  # Relative
  L  # Leittonwechsel
  N  # Nebenverwandt (RLP)
  S  # Slide (LPR)
  H  # "hexatonic pole exchange" (LPL)

=head2 tonnetzen_7

  $tonnetzen_7 = $d->tonnetzen_7;

The named tonnetz values for 7th chord transformations.

Default:

  S23
  S32
  S34
  S43
  S56
  S65
  C32
  C34
  C65

=head2 rhythmic_phrase_constraints

  $rhythmic_phrase_constraints = $d->rhythmic_phrase_constraints;

The number of rhythmic values in a phrase, given as an array reference.

Default: C<[3,4,5]>

=head1 METHODS

=head2 new

  $d = Music::Dice->new;
  $d = Music::Dice->new( # override defaults
    semitones                   => $semitones,
    scale_note                  => $note,
    scale_name                  => $name,
    flats                       => $flats,
    beats                       => $beats,
    phrase_pool                 => \@pool, # or 'all'
    phrase_weights              => \@weights,
    phrase_groups               => \@groups,
    notes                       => \@notes,
    identity                    => $identity,
    intervals                   => \@intervals,
    chord_triads                => \@triads,
    chord_triad_weights         => \@triad_weights,
    chord_qualities_major       => \@chord_qualities_major,
    chord_qualities_major_7     => \@chord_qualities_major_7,
    chord_qualities_minor       => \@chord_qualities_minor,
    chord_qualities_minor_7     => \@chord_qualities_minor_7,
    chord_qualities_diminished  => \@chord_qualities_diminished,
    chord_qualities_augmented   => \@chord_qualities_augmented,
    chord_qualities_augmented_7 => \@chord_qualities_augmented_7,
    modes                       => \@modes,
    tonnetzen                   => \@tonnetzen,
    tonnetzen_7                 => \@tonnetzen_7,
    rhythmic_phrase_constraints => \@constraints,
  );

Create a new C<Music::Dice> object.

=for Pod::Coverage BUILD

=head2 octave

  $result = $d->octave->roll;

Return an octave number.

=head2 note

  $result = $d->note->roll;

Return one of the B<notes>, with equal probability.

=head2 interval

  $result = $d->interval->roll;

Return one of the note B<intervals>, with equal probability.

=head2 note_chromatic

  $result = $d->note_chromatic->roll;

Return one of the chromatic scale notes, based on the given
B<scale_note>, with equal probability.

=head2 interval_chromatic

  $result = $d->interval_chromatic->roll;

Return one of the chromatic intervals, with equal probability.

=head2 note_major

  $result = $d->note_major->roll;

Return one of the major scale notes, based on the given
B<scale_note>, with equal probability.

=head2 interval_major

  $result = $d->interval_major->roll;

Return one of the major intervals, with equal probability.

=head2 note_minor

  $result = $d->note_minor->roll;

Return one of the natural minor scale notes, based on the given
B<scale_note>, with equal probability.

=head2 interval_minor

  $result = $d->interval_minor->roll;

Return one of the minor intervals, with equal probability.

=head2 chord_triad

  $result = $d->chord_triad->roll;

Return a chord triad. If C<custom> is rolled, then three C<notes>
must be rolled for, separately.

=head2 chord_quality_major

  $result = $d->chord_quality_major->roll;

Return a chord quality to modify a major chord triad.

=head2 chord_quality_major_7

  $result = $d->chord_quality_major_7->roll;

Return a chord quality to modify a 7th chord.

=head2 chord_quality_minor

  $result = $d->chord_quality_minor->roll;

Return a chord quality to modify a minor chord triad.

=head2 chord_quality_minor_7

  $result = $d->chord_quality_minor_7->roll;

Return a chord quality to modify a minor 7th chord.

=head2 chord_quality_diminished

  $result = $d->chord_quality_diminished->roll;

Return a chord quality to modify a diminished chord triad.

=head2 chord_quality_augmented

  $result = $d->chord_quality_augmented->roll;

Return a chord quality to modify an augmented chord triad.

=head2 chord_quality_augmented_7

  $result = $d->chord_quality_augmented_7->roll;

Return a chord quality to modify an augmented 7th chord.

=head2 chord_quality_triad_roll

  $result = $d->chord_quality_triad_roll($note, $triad);

Return a chord quality, given a B<note> and a known B<triad>.

=head2 mode

  $result = $d->mode->roll;

Return a mode.

=head2 ionian

  $result = $d->ionian->roll;

Return a value from the Ionian mode mask of chord types.

=head2 dorian

  $result = $d->dorian->roll;

Return a value from the Dorian mode mask of chord types.

=head2 phrygian

  $result = $d->phrygian->roll;

Return a value from the Phrygian mode mask of chord types.

=head2 lydian

  $result = $d->lydian->roll;

Return a value from the Lydian mode mask of chord types.

=head2 mixolydian

  $result = $d->mixolydian->roll;

Return a value from the Mixolydian mode mask of chord types.

=head2 aeolian

  $result = $d->aeolian->roll;

Return a value from the Aeolian mode mask of chord types.

=head2 locrian

  $result = $d->locrian->roll;

Return a value from the Ionian mode mask of chord types.

=head2 mode_degree_triad_roll

  ($degree, $triad) = $d->mode_degree_triad_roll($mode);

Return a modal degree and triad type (C<major>, C<minor>,
C<diminished>), given a B<mode> name.

=head2 tonnetz

  $result = $d->tonnetz->roll;

Return one of the B<tonnetzen>, with equal probability.

=head2 tonnetz_7

  $result = $d->tonnetz_7->roll;

Return one of the B<tonnetzen_7>, with equal probability.

=head2 rhythmic_value

  $result = $d->rhythmic_value->roll;

Return a single rhythmic value.

=head2 rhythmic_phrase

  $result = $d->rhythmic_phrase->roll;

Return a rhythmic phrase, given the number of B<beats>.

=head2 rhythmic_phrase_constrained

  $result = $d->rhythmic_phrase_constrained->roll;

Return a constrained rhythmic phrase, given the
B<rhythmic_phrase_constraints> (number of rhythmic values).

=head2 unique_item

  $item = $mb->unique_item(\@excludes);
  $item = $mb->unique_item(\@excludes, \@items);

Return an item from the B<items> list, that is not in the B<excludes>
list. If an item list is not given in the arguments, the object B<notes>
are used.

=head1 SEE ALSO

The F<t/01-methods.t> file

L<Games::Dice::Advanced>

L<List::Util::WeightedChoice>

L<MIDI::Util>

L<Moo>

L<Music::Duration::Partition>

L<Music::Scales>

L<usic::ToRoman>

L<Types::Standard>

=head1 AUTHOR

Gene Boggs <gene.boggs@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2024-2025 by Gene Boggs.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
