File Coverage

blib/lib/Music/Duration/Partition.pm
Criterion Covered Total %
statement 90 97 92.7
branch 18 24 75.0
condition 3 5 60.0
subroutine 18 19 94.7
pod 3 3 100.0
total 132 148 89.1


line stmt bran cond sub pod time code
1             package Music::Duration::Partition;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Partition a musical duration into rhythmic phrases
5              
6             our $VERSION = '0.0823';
7              
8 2     2   782817 use Moo;
  2         19344  
  2         13  
9 2     2   5271 use strictures 2;
  2         3953  
  2         111  
10 2     2   1157 use List::Util qw(min);
  2         5  
  2         238  
11 2     2   1197 use Math::Random::Discrete ();
  2         1463  
  2         77  
12 2     2   1282 use MIDI::Util qw(midi_dump reverse_dump);
  2         79394  
  2         231  
13 2     2   1305 use Music::Duration;
  2         1117  
  2         90  
14 2     2   1254 use namespace::clean;
  2         43484  
  2         20  
15              
16 2     2   908 use constant TICKS => 96;
  2         7  
  2         3706  
17              
18              
19             has size => (
20             is => 'ro',
21             default => sub { 4 },
22             );
23              
24              
25             has pool => (
26             is => 'ro',
27             isa => sub { die 'Empty pool not allowed' unless ref( $_[0] ) eq 'ARRAY' && @{ $_[0] } > 0 },
28             default => sub { [ keys %{ midi_dump('length') } ] },
29             );
30              
31             has _min_size => (
32             is => 'ro',
33             builder => 1,
34             lazy => 1,
35             );
36              
37             sub _build__min_size {
38 12     12   142 my ($self) = @_;
39 12         23 my @sizes = map { $self->_duration($_) } @{ $self->pool };
  15         27  
  12         33  
40 12         156 return min(@sizes);
41             }
42              
43             has _mrd => (
44             is => 'ro',
45             builder => 1,
46             lazy => 1,
47             );
48              
49             sub _build__mrd {
50 12     12   108 my ($self) = @_;
51             die 'Sizes of weights and pool not equal'
52 12 100       19 unless @{ $self->weights } == @{ $self->pool };
  12         429  
  12         68  
53 11         216 return Math::Random::Discrete->new($self->weights, $self->pool);
54             }
55              
56              
57             has pool_select => (
58             is => 'rw',
59             builder => 1,
60             lazy => 1,
61             );
62              
63             sub _build_pool_select {
64 12     12   111 my ($self) = @_;
65 12     61   106 return sub { $self->_mrd->rand };
  61         1513  
66             };
67              
68              
69             has weights => (
70             is => 'ro',
71             builder => 1,
72             lazy => 1,
73             );
74              
75             sub _build_weights {
76 9     9   77 my ($self) = @_;
77             # Equal probability for all pool members
78 9         16 return [ (1) x @{ $self->pool } ];
  9         44  
79             }
80              
81              
82             has groups => (
83             is => 'ro',
84             builder => 1,
85             lazy => 1,
86             );
87              
88             sub _build_groups {
89 12     12   120 my ($self) = @_;
90 12         24 return [ (0) x @{ $self->pool } ];
  12         95  
91             }
92              
93             has _pool_group => (
94             is => 'ro',
95             builder => 1,
96             lazy => 1,
97             );
98              
99             sub _build__pool_group {
100 12     12   100 my ($self) = @_;
101 12         22 my %pool_group;
102 12         24 for my $i (0 .. @{ $self->pool } - 1) {
  12         42  
103 15         301 $pool_group{ $self->pool->[$i] } = $self->groups->[$i];
104             }
105 12         83 return \%pool_group;
106             }
107              
108              
109             has remainder => (
110             is => 'ro',
111             default => sub { 1 },
112             );
113              
114              
115             has abbreviation => (
116             is => 'ro',
117             default => sub { 1 },
118             );
119              
120              
121             has verbose => (
122             is => 'ro',
123             default => sub { 0 },
124             );
125              
126             # hash reference of duration lengths (keyed by duration name)
127             has _durations => (
128             is => 'ro',
129             default => sub { midi_dump('length') },
130             );
131              
132              
133             sub motif {
134 15     15 1 9509 my ($self) = @_;
135              
136 15         33 my $motif = [];
137              
138 15         25 my $format = '%.4f';
139              
140 15         26 my $sum = 0;
141 15         50 my $group_num = 0;
142 15         21 my $group_name = '';
143              
144 15         265 while ( $sum < $self->size ) {
145 72         1692 my $name = $self->pool_select->($self); # Chooses a note duration
146              
147             # Compute grouping
148 71 50       2110 if ($group_num) {
149 0         0 $group_num--;
150 0         0 $name = $group_name;
151             }
152             else {
153 71 50       1418 if ($self->_pool_group->{$name}) {
154 0         0 $group_num = $self->_pool_group->{$name} - 1;
155 0         0 $group_name = $name;
156             }
157             else {
158 71         479 $group_num = 0;
159 71         125 $group_name = '';
160             }
161             }
162              
163 71         185 my $size = $self->_duration($name); # Get the duration of the note
164 71         206 my $diff = $self->size - $sum; # How much is left?
165              
166             # The difference is less than the min_size
167 71 100       1645 if (sprintf( $format, $diff ) < sprintf( $format, $self->_min_size )) {
168 4 50       43 warn "WARNING: Leftover duration: $diff\n"
169             if $self->verbose;
170 4         83 my $tick_diff = TICKS * $diff;
171 4         14 my $dura = 'd' . sprintf('%.0f', $tick_diff);
172 4 100       16 if ($self->abbreviation) {
173 3         17 my $length = reverse_dump('length');
174 3         1235 my %length = map { TICKS * $_ => $length->{$_} } keys %$length;
  96         360  
175             $dura = $length{$tick_diff}
176 3 100       70 if exists $length{$tick_diff};
177             }
178 4 100 66     46 push @$motif, $dura
179             if $self->remainder && sprintf($format, $tick_diff) > 0;
180 4         9 last;
181             }
182              
183             # The note duration is greater than the difference
184             next
185 67 50       948 if sprintf( $format, $size ) > sprintf( $format, $diff );
186              
187             # Increment the sum by the note duration
188 67         218 $sum += $size;
189              
190 67 50       168 warn(__PACKAGE__,' ',__LINE__," $name, $size, $sum\n")
191             if $self->verbose;
192              
193             # Add the note to the motif if the sum is less than the total duration size
194 67 50       333 push @$motif, $name
195             if $sum <= $self->size;
196             }
197              
198 14         119 return $motif;
199             }
200              
201              
202             sub motifs {
203 1     1 1 423 my ($self, $n) = @_;
204 1   50     6 $n ||= 1;
205 1         7 my @motifs = map { $self->motif } 1 .. $n;
  2         9  
206 1         8 return @motifs;
207             }
208              
209              
210             sub add_to_score {
211 0     0 1 0 my ($self, $score, $motif, $pitches) = @_;
212 0         0 for my $i (0 .. $#$motif) {
213 0         0 $score->n($motif->[$i], $pitches->[$i]);
214             }
215             }
216              
217             sub _duration {
218 86     86   192 my ( $self, $name ) = @_;
219              
220 86         139 my $dura;
221              
222 86 100       281 if ($name =~ /^d(\d+)$/) {
223 3         6 $dura = $1;
224             }
225             else {
226 83         187 $dura = $self->_durations->{$name};
227             }
228              
229 86         187 return $dura;
230             }
231              
232             1;
233              
234             __END__