File Coverage

blib/lib/Music/RecRhythm.pm
Criterion Covered Total %
statement 71 71 100.0
branch 14 14 100.0
condition 9 12 75.0
subroutine 15 15 100.0
pod 5 6 83.3
total 114 118 96.6


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   213685 use 5.10.0;
  3         10  
10 3     3   14 use strict;
  3         5  
  3         50  
11 3     3   11 use warnings;
  3         8  
  3         86  
12              
13 3     3   18 use List::Util 1.26 qw(sum0);
  3         45  
  3         204  
14 3     3   2187 use Math::BigInt ();
  3         53408  
  3         98  
15 3     3   1162 use Moo;
  3         22681  
  3         12  
16 3     3   3832 use namespace::clean;
  3         22856  
  3         15  
17 3     3   695 use Scalar::Util qw(looks_like_number);
  3         6  
  3         2099  
18              
19             our $VERSION = '0.05';
20              
21             with 'MooX::Rebuild'; # for ->rebuild a.k.a. clone
22              
23             has extra => ( is => 'rw' );
24              
25             has next => (
26             is => 'rw',
27             trigger => sub {
28             my ( $self, $next ) = @_;
29             $next->prev($self);
30             },
31             );
32             has prev => ( is => 'rw', weak_ref => 1 );
33              
34             has set => (
35             is => 'rw',
36             coerce => sub {
37             my ($set) = @_;
38             die "need a set of positive integers"
39             if !Music::RecRhythm->validate_set($set);
40             for my $n (@$set) {
41             $n = int $n;
42             }
43             return $set;
44             },
45             trigger => sub {
46             my ( $self, $set ) = @_;
47             $self->_set_count( scalar @$set );
48             $self->_set_sum( sum0(@$set) );
49             },
50             );
51             has count => ( is => 'rwp' );
52             has sum => ( is => 'rwp' );
53              
54             # flag to skip the callback (though the rhythm will still be present in
55             # the recursion)
56             has is_silent => (
57             is => 'rw',
58             default => sub { 0 },
59             coerce => sub { $_[0] ? 1 : 0 },
60             );
61              
62             sub BUILD {
63 21     21 0 105 my ( $self, $param ) = @_;
64 21 100       127 die "need a set of positive integers" if !exists $param->{set};
65             }
66              
67             ########################################################################
68             #
69             # METHODS
70              
71             sub audible_levels {
72 5     5 1 10 my ($self) = @_;
73 5         9 my $count = 0;
74 5         11 while ($self) {
75 13 100       225 $count++ unless $self->is_silent;
76 13         226 $self = $self->next;
77             }
78 5         52 return $count;
79             }
80              
81             sub beatfactor {
82 11     11 1 95 my ($self) = @_;
83 11         17 my %factors;
84 11         14 my $prev_sum = 1;
85 11         25 while ($self) {
86 29         62 my $sum = $self->sum * $prev_sum;
87 29         34 @factors{ @{ $self->set }, $sum } = ();
  29         425  
88 29         533 $self = $self->next;
89 29         151 $prev_sum = $sum;
90             }
91 11         56 return Math::BigInt->bone()->blcm( keys %factors )->numify;
92             }
93              
94             sub levels {
95 5     5 1 22493 my ($self) = @_;
96 5         8 my $count = 0;
97 5         13 while ($self) {
98 13         44 $count++;
99 13         197 $self = $self->next;
100             }
101 5         38 return $count;
102             }
103              
104             sub recurse {
105 6     6 1 38 my ( $self, $callback, $extra ) = @_;
106 6         14 my $bf = $self->beatfactor;
107 6         3048 _recurse( $self, $callback, $extra, $bf, 0, 0 );
108             }
109              
110             sub _recurse {
111 78     78   183 my ( $rset, $callback, $extra, $totaltime, $level, $audible_level, @beats ) =
112             @_;
113 78         149 my %param = ( level => $level, audible_level => $audible_level );
114 78         110 for my $p (qw/next set/) {
115 156         2477 $param{$p} = $rset->$p;
116             }
117 78         1218 my $sil = $rset->is_silent;
118 78 100       371 $audible_level++ if !$sil;
119 78         140 my $unittime = $totaltime / $rset->sum;
120 78         94 for my $n ( 0 .. $#{ $param{set} } ) {
  78         155  
121 475         663 $param{beat} = $param{set}[$n];
122 475         564 $param{index} = $n;
123 475         587 $param{duration} = int( $unittime * $param{beat} );
124 475 100       636 if ( !$sil ) {
125 472         768 $callback->( $rset, \%param, $extra, @beats, $param{beat} );
126             }
127 475 100       2378 if ( defined $param{next} ) {
128             _recurse( $param{next}, $callback, $extra, $param{duration}, $level + 1,
129 72         152 $audible_level, @beats, $param{beat} );
130             }
131             }
132             }
133              
134             sub validate_set {
135 26     26 1 125 my ( $class, $set ) = @_;
136 26 100 100     179 return 0 if !defined $set or ref $set ne 'ARRAY' or !@$set;
      100        
137 22         45 for my $x (@$set) {
138 64 100 33     300 return 0 if !defined $x or !looks_like_number $x or $x < 1;
      66        
139             }
140 21         46 return 1;
141             }
142              
143             1;
144             __END__