File Coverage

blib/lib/Music/RecRhythm.pm
Criterion Covered Total %
statement 77 77 100.0
branch 16 16 100.0
condition 9 12 75.0
subroutine 16 16 100.0
pod 6 7 85.7
total 124 128 96.8


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # rhythms within rhythms within rhythms
4             #
5             # Run perldoc(1) on this file for additional documentation.
6              
7             package Music::RecRhythm;
8              
9 3     3   283865 use 5.10.0;
  3         13  
10 3     3   21 use strict;
  3         7  
  3         79  
11 3     3   19 use warnings;
  3         11  
  3         124  
12              
13 3     3   25 use List::Util 1.26 qw(sum0);
  3         60  
  3         266  
14 3     3   2807 use Math::BigInt ();
  3         86727  
  3         126  
15 3     3   1406 use Moo;
  3         32640  
  3         18  
16 3     3   5464 use namespace::clean;
  3         32798  
  3         23  
17 3     3   1131 use Scalar::Util qw(looks_like_number);
  3         12  
  3         3125  
18              
19             our $VERSION = '0.04';
20              
21             with 'MooX::Rebuild'; # for ->rebuild a.k.a. clone
22              
23             has extra => ( is => 'rw' );
24             has _next => ( is => 'rw' );
25             has prev => ( is => 'rw', weak_ref => 1 );
26              
27             has set => (
28             is => 'rw',
29             coerce => sub {
30             my ($set) = @_;
31             die "need a set of positive integers"
32             if !Music::RecRhythm->validate_set($set);
33             for my $n (@$set) {
34             $n = int $n;
35             }
36             return $set;
37             },
38             trigger => sub {
39             my ( $self, $set ) = @_;
40             $self->_set_count( scalar @$set );
41             $self->_set_sum( sum0(@$set) );
42             },
43             );
44             has count => ( is => 'rwp' );
45             has sum => ( is => 'rwp' );
46              
47             # flag to skip the callback (though the rhythm will still be present in
48             # the recursion)
49             has is_silent => (
50             is => 'rw',
51             default => sub { 0 },
52             coerce => sub { $_[0] ? 1 : 0 },
53             );
54              
55             sub BUILD {
56 21     21 0 124 my ( $self, $param ) = @_;
57 21 100       142 die "need a set of positive integers" if !exists $param->{set};
58             }
59              
60             ########################################################################
61             #
62             # METHODS
63              
64             sub audible_levels {
65 5     5 1 14 my ($self) = @_;
66 5         11 my $count = 0;
67 5         16 while ($self) {
68 13 100       287 $count++ unless $self->is_silent;
69 13         111 $self = $self->next;
70             }
71 5         36 return $count;
72             }
73              
74             sub beatfactor {
75 11     11 1 63 my ($self) = @_;
76 11         20 my %factors;
77 11         22 my $prev_sum = 1;
78 11         32 while ($self) {
79 29         94 my $sum = $self->sum * $prev_sum;
80 29         51 @factors{ @{ $self->set }, $sum } = ();
  29         705  
81 29         295 $self = $self->next;
82 29         88 $prev_sum = $sum;
83             }
84 11         57 return Math::BigInt->bone()->blcm( keys %factors )->numify;
85             }
86              
87             sub levels {
88 5     5 1 25609 my ($self) = @_;
89 5         15 my $count = 0;
90 5         19 while ($self) {
91 13         24 $count++;
92 13         32 $self = $self->next;
93             }
94 5         42 return $count;
95             }
96              
97             # TODO could this instead be simplified with a trigger to set prev?
98             sub next {
99 145     145 1 373 my ( $self, $next ) = @_;
100 145 100       382 if ( defined $next ) {
101 11         38 $self->_next($next);
102 11         272 $next->prev($self);
103 11         161 return $self;
104             } else {
105 134         542 return $self->_next;
106             }
107             }
108              
109             sub recurse {
110 6     6 1 28 my ( $self, $callback, $extra ) = @_;
111 6         19 my $bf = $self->beatfactor;
112 6         4701 _recurse( $self, $callback, $extra, $bf, 0, 0 );
113             }
114              
115             sub _recurse {
116 78     78   294 my ( $rset, $callback, $extra, $totaltime, $level, $audible_level, @beats ) =
117             @_;
118 78         269 my %param = ( level => $level, audible_level => $audible_level );
119 78         184 for my $p (qw/next set/) {
120 156         2370 $param{$p} = $rset->$p;
121             }
122 78         2357 my $sil = $rset->is_silent;
123 78 100       724 $audible_level++ if !$sil;
124 78         277 my $unittime = $totaltime / $rset->sum;
125 78         160 for my $n ( 0 .. $#{ $param{set} } ) {
  78         298  
126 475         1243 $param{beat} = $param{set}[$n];
127 475         930 $param{index} = $n;
128 475         1036 $param{duration} = int( $unittime * $param{beat} );
129 475 100       1146 if ( !$sil ) {
130 472         1386 $callback->( $rset, \%param, $extra, @beats, $param{beat} );
131             }
132 475 100       4419 if ( defined $param{next} ) {
133             _recurse( $param{next}, $callback, $extra, $param{duration}, $level + 1,
134 72         261 $audible_level, @beats, $param{beat} );
135             }
136             }
137             }
138              
139             sub validate_set {
140 26     26 1 161 my ( $class, $set ) = @_;
141 26 100 100     241 return 0 if !defined $set or ref $set ne 'ARRAY' or !@$set;
      100        
142 22         54 for my $x (@$set) {
143 64 100 33     397 return 0 if !defined $x or !looks_like_number $x or $x < 1;
      66        
144             }
145 21         65 return 1;
146             }
147              
148             1;
149             __END__