File Coverage

blib/lib/MIDI/Drummer/Tiny/Syncopate.pm
Criterion Covered Total %
statement 12 50 24.0
branch 0 12 0.0
condition 0 24 0.0
subroutine 4 9 44.4
pod 2 2 100.0
total 18 97 18.5


line stmt bran cond sub pod time code
1             package MIDI::Drummer::Tiny::Syncopate;
2             $MIDI::Drummer::Tiny::Syncopate::VERSION = '0.7013';
3             our $AUTHORITY = 'cpan:GENE';
4              
5             # ABSTRACT: Syncopation logic
6              
7 1     1   209564 use MIDI::Util qw(dura_size);
  1         23126  
  1         59  
8              
9 1     1   409 use Moo;
  1         6795  
  1         9  
10 1     1   1559 use strictures 2;
  1         1310  
  1         31  
11 1     1   685 use namespace::clean;
  1         13079  
  1         6  
12              
13             extends 'MIDI::Drummer::Tiny';
14              
15             #pod =head1 SYNOPSIS
16             #pod
17             #pod use MIDI::Drummer::Tiny::Syncopate;
18             #pod
19             #pod my $d = MIDI::Drummer::Tiny::Syncopate->new(
20             #pod file => 'syncopate.mid',
21             #pod reverb => 15,
22             #pod );
23             #pod
24             #pod $d->combinatorial( $d->snare, {
25             #pod repeat => 2,
26             #pod patterns => [qw(0101 1001)],
27             #pod });
28             #pod
29             #pod # Play parts simultaneously
30             #pod $d->sync( \&snare, \&kick, \&hhat );
31             #pod sub snare { $d->combinatorial( $d->snare, { count => 1 } ) }
32             #pod sub kick { $d->combinatorial( $d->kick, { negate => 1 } ) }
33             #pod sub hhat { $d->steady( $d->closed_hh ) }
34             #pod
35             #pod $d->write;
36             #pod
37             #pod =head1 DESCRIPTION
38             #pod
39             #pod C provides methods to use in the
40             #pod F lessons.
41             #pod
42             #pod =cut
43              
44             #pod =head1 ATTRIBUTES
45             #pod
46             #pod =head2 duration
47             #pod
48             #pod $duration = $d->duration;
49             #pod
50             #pod Default: C
51             #pod
52             #pod =cut
53              
54             has duration => (
55             is => 'ro',
56             default => sub { 'qn' },
57             );
58              
59             #pod =head2 repeat
60             #pod
61             #pod $repeat = $d->repeat;
62             #pod
63             #pod Default: C<4>
64             #pod
65             #pod =cut
66              
67             has repeat => (
68             is => 'ro',
69             default => sub { 4 },
70             );
71              
72             #pod =head1 METHODS
73             #pod
74             #pod =head2 new
75             #pod
76             #pod $d = MIDI::Drummer::Tiny::Syncopate->new(%arguments);
77             #pod
78             #pod Return a new C object.
79             #pod
80             #pod =head2 steady
81             #pod
82             #pod $d->steady;
83             #pod $d->steady( $d->kick );
84             #pod $d->steady( $d->kick, { duration => $d->eighth } );
85             #pod
86             #pod Play a steady beat with the given B and optional
87             #pod B, for the number of beats accumulated in the object's
88             #pod B attribute.
89             #pod
90             #pod Defaults:
91             #pod
92             #pod instrument: closed_hh
93             #pod Options:
94             #pod duration: given by constructor
95             #pod
96             #pod =cut
97              
98             sub steady {
99 0     0 1   my ( $self, $instrument, $opts ) = @_;
100              
101 0   0       $instrument ||= $self->closed_hh;
102              
103 0   0       $opts->{duration} ||= $self->duration;
104              
105             # XXX This is not right
106 0           for my $n ( 1 .. $self->counter ) {
107 0           $self->note( $opts->{duration}, $instrument );
108             }
109             }
110              
111             #pod =head2 combinatorial
112             #pod
113             #pod $d->combinatorial;
114             #pod $d->combinatorial( $d->kick );
115             #pod $d->combinatorial( $d->kick, \%options );
116             #pod
117             #pod Play a beat pattern with the given B, given by
118             #pod L.
119             #pod
120             #pod This method accumulates beats in the object's B attribute if
121             #pod the B option is set.
122             #pod
123             #pod The B option is a hashref of coderefs, keyed by single character
124             #pod tokens, like the digits, 0-9. The coderef durations should add up to
125             #pod the B option.
126             #pod
127             #pod Defaults:
128             #pod
129             #pod instrument: snare
130             #pod Options:
131             #pod duration: given by constructor
132             #pod count: 0
133             #pod negate: 0
134             #pod beats: given by constructor
135             #pod repeat: given by constructor
136             #pod vary:
137             #pod 0 => sub { $self->rest( $options->{duration} ) },
138             #pod 1 => sub { $self->note( $options->{duration}, $instrument ) },
139             #pod patterns: undef
140             #pod
141             #pod =cut
142              
143             sub combinatorial {
144 0     0 1   my ( $self, $instrument, $opts ) = @_;
145              
146 0   0       $instrument ||= $self->snare;
147              
148 0   0       $opts->{negate} ||= 0;
149 0   0       $opts->{count} ||= 0;
150 0   0       $opts->{beats} ||= $self->beats;
151 0   0       $opts->{repeat} ||= $self->repeat;
152 0   0       $opts->{duration} ||= $self->duration;
153             $opts->{vary} ||= {
154 0     0     0 => sub { $self->rest( $opts->{duration} ) },
155 0     0     1 => sub { $self->note( $opts->{duration}, $instrument ) },
156 0   0       };
157              
158 0           my $size = dura_size( $opts->{duration} );
159              
160             my @items = $opts->{patterns}
161 0           ? @{ $opts->{patterns} }
162 0           : sort map { join '', @$_ }
163 0 0         @{ _variations_with_repetition( [ keys %{ $opts->{vary} } ], $opts->{beats} ) };
  0            
  0            
164              
165 0           for my $pattern (@items) {
166 0 0         next if $pattern =~ /^0+$/;
167              
168 0 0         $pattern =~ tr/01/10/ if $opts->{negate};
169              
170 0           for ( 1 .. $opts->{repeat} ) {
171 0           for my $bit ( split //, $pattern ) {
172 0           $opts->{vary}{$bit}->($self);
173 0 0         $self->counter( $self->counter + $size ) if $opts->{count};
174             }
175             }
176             }
177             }
178              
179             sub _variations_with_repetition {
180 0     0     my ($elements, $k) = @_;
181             # Base case: if k is 0, return a single empty variation
182 0 0         return [""] if $k == 0;
183             # Base case: if k is 1, return each element as a variation
184 0 0         return [map { [$_] } @$elements] if $k == 1;
  0            
185             # Recursive step
186 0           my @result;
187 0           my $sub_variations = _variations_with_repetition($elements, $k - 1);
188 0           for my $element (@$elements) {
189 0           for my $sub (@$sub_variations) {
190 0           push @result, [ $element, @$sub ];
191             }
192             }
193 0           return \@result;
194             }
195              
196             1;
197              
198             __END__