File Coverage

blib/lib/MIDI/Drummer/Tiny/Grooves.pm
Criterion Covered Total %
statement 74 84 88.1
branch 21 28 75.0
condition 3 5 60.0
subroutine 23 25 92.0
pod 4 4 100.0
total 125 146 85.6


line stmt bran cond sub pod time code
1             package MIDI::Drummer::Tiny::Grooves;
2             $MIDI::Drummer::Tiny::Grooves::VERSION = '0.7013';
3             our $AUTHORITY = 'cpan:GENE';
4              
5 2     2   471544 use Moo;
  2         12536  
  2         8  
6 2     2   3181 use strictures 2;
  2         2650  
  2         63  
7             # use Data::Dumper::Compact qw(ddc);
8 2     2   1461 use File::ShareDir qw(dist_dir);
  2         44705  
  2         114  
9 2     2   1447 use Path::Tiny;
  2         20514  
  2         124  
10 2     2   1207 use MIDI::Drummer::Tiny ();
  2         12  
  2         82  
11 2     2   18 use namespace::clean;
  2         3  
  2         19  
12              
13             #pod =head1 SYNOPSIS
14             #pod
15             #pod use MIDI::Drummer::Tiny ();
16             #pod use MIDI::Drummer::Tiny::Grooves ();
17             #pod # TODO use MIDI::Drummer::Tiny::Grooves qw(:house :rock); # maybe
18             #pod
19             #pod my $drummer = MIDI::Drummer::Tiny->new(
20             #pod file => "grooves.mid",
21             #pod kick => 36,
22             #pod );
23             #pod
24             #pod my $grooves = MIDI::Drummer::Tiny::Grooves->new(
25             #pod drummer => $drummer
26             #pod );
27             #pod
28             #pod my $all = $grooves->all_grooves;
29             #pod
30             #pod my $groove = $grooves->get_groove; # random groove
31             #pod $groove = $grooves->get_groove(42); # numbered groove
32             #pod print "42. $groove->{cat}\n$groove->{name}";
33             #pod $grooves->groove($groove->{groove}) for 1 .. 4; # add to score
34             #pod
35             #pod my $set = $grooves->search({ cat => 'house' });
36             #pod my $pattern = $set->{27}{groove}; # { kick => '...', }
37             #pod $set = $grooves->search({ name => 'deep' }, $set); # refine search
38             #pod
39             #pod for my $i (sort keys %$set) {
40             #pod $groove = $set->{$i};
41             #pod print "$i. $groove->{cat}\n$groove->{name}]\n";
42             #pod $grooves->groove($groove->{groove}); # a bit redundant!
43             #pod }
44             #pod
45             #pod $grooves->drummer->write;
46             #pod # then:
47             #pod # > timidity grooves.mid
48             #pod
49             #pod =head1 DESCRIPTION
50             #pod
51             #pod Return the common grooves, as listed in the "Pocket Operations", that
52             #pod are L. There are a total of 269 known drum
53             #pod patterns.
54             #pod
55             #pod A groove is a numbered and named hash reference, with the following
56             #pod structure:
57             #pod
58             #pod 1 => {
59             #pod cat => "Basic Patterns",
60             #pod name => "ONE AND SEVEN & FIVE AND THIRTEEN",
61             #pod groove => {
62             #pod kick => { num => $self->kick, pat => ['1000001000000000'] },
63             #pod snare => { num => $self->snare, pat => ['0000100000001000'] },
64             #pod ...
65             #pod },
66             #pod },
67             #pod
68             #pod =cut
69              
70             #pod =head1 ACCESSORS
71             #pod
72             #pod =head2 drummer
73             #pod
74             #pod $drummer = $grooves->drummer;
75             #pod $grooves->drummer($drummer);
76             #pod
77             #pod The L object. If not given in the constructor, a
78             #pod new one is created when a method is called.
79             #pod
80             #pod =cut
81              
82             has drummer => (
83             is => 'rw',
84             isa => sub { die "Invalid drummer object" unless ref($_[0]) eq 'MIDI::Drummer::Tiny' },
85             default => sub { MIDI::Drummer::Tiny->new },
86             );
87              
88             #pod =head2 duration
89             #pod
90             #pod $duration = $grooves->duration;
91             #pod $grooves->duration($duration);
92             #pod
93             #pod The "resolution" duration that is given to the
94             #pod L method.
95             #pod
96             #pod This is initialized to the sixteenth duration of the drummer
97             #pod L object.
98             #pod
99             #pod =cut
100              
101             has duration => (
102             is => 'lazy',
103             );
104 0     0   0 sub _build_duration { shift->drummer->sixteenth }
105              
106             #pod =head2 kick, rimshot, snare, clap, conga, cowbell, shaker, closed, open, cymbal, hi_tom, mid_tom, low_tom
107             #pod
108             #pod $kick = $grooves->kick;
109             #pod $grooves->kick(36);
110             #pod
111             #pod The drum patches that are used by the grooves.
112             #pod
113             #pod Each is initialized to a corresponding patch of the drummer
114             #pod L object that is given to, or created by the
115             #pod constructor.
116             #pod
117             #pod =cut
118              
119             #pod =head2 return_patterns
120             #pod
121             #pod $return_patterns = $grooves->return_patterns;
122             #pod
123             #pod Either return the raw patterns of 16 beats or C'ed B
124             #pod object phrases from the B method.
125             #pod
126             #pod Default: C<0>
127             #pod
128             #pod =cut
129              
130             has return_patterns => (
131             is => 'rw',
132             isa => sub { die "Not a Boolean" unless $_[0] =~ /^[01]$/ },
133             default => sub { 0 },
134             );
135              
136             has _grooves => (
137             is => 'lazy',
138             builder => '_build__grooves',
139             );
140             sub _build__grooves {
141 3     3   37 my ($self) = @_;
142 3         51 my %mapping = (
143             BD => 'kick',
144             SN => 'snare',
145             RS => 'rimshot',
146             CH => 'closed',
147             OH => 'open',
148             CY => 'cymbal',
149             CB => 'cowbell',
150             CL => 'clap',
151             SH => 'shaker',
152             HT => 'hi_tom',
153             MT => 'mid_tom',
154             LT => 'low_tom',
155             HC => 'conga',
156             );
157 3         5 my $file = '/drum-pattern-bit-strings.txt';
158 3         20 my $path = dist_dir('MIDI-Drummer-Tiny') . $file;
159 3 50       508 $path = 'share' . $file unless -e $path;
160 3         20 my @contents = path($path)->lines;
161 3         3136 my (%grooves, $cat, $name, %patterns);
162 3         7 my $i = 0;
163 3         9 for my $line (@contents) {
164 4383         4518 chomp $line;
165 4383 100       7531 chop $line if $line =~ /\r$/;
166 4383 100       7661 if ($line =~ /^Instrument/) {
    100          
    100          
167 807         856 next;
168             }
169             elsif ($line =~ /^([A-Z][A-Z]),([01]+)$/) {
170 2649   100     4243 my $mapping = $mapping{$1} || next;
171 2439         25682 my $val = { num => $self->$mapping, pat => [$2] };
172 2439         13641 $patterns{$mapping} = $val;
173             }
174             elsif ($line =~ /^\* (.+)$/) {
175 66         105 $cat = $1;
176             }
177             else {
178 861 100       1119 if (keys %patterns) {
179             # print ddc \%patterns;
180 804         2964 $grooves{++$i} = {
181             cat => $cat,
182             name => $name,
183             groove => { %patterns },
184             };
185             }
186 861         1225 %patterns = ();
187 861         996 $name = $line;
188             }
189             }
190             # print ddc \%grooves;
191 3         366 return \%grooves;
192             }
193              
194             for my $patch (qw(
195             kick
196             rimshot
197             snare
198             clap
199             conga
200             cowbell
201             shaker
202             closed
203             open
204             cymbal
205             hi_tom
206             mid_tom
207             low_tom
208             )) {
209             has $patch => (
210             is => 'lazy',
211             builder => '_build_' . $patch,
212             );
213             }
214 3     3   62 sub _build_kick { shift->drummer->kick }
215 3     3   45 sub _build_rimshot { shift->drummer->side_stick }
216 3     3   47 sub _build_snare { shift->drummer->snare }
217 3     3   45 sub _build_clap { shift->drummer->clap }
218 3     3   46 sub _build_conga { shift->drummer->open_hi_conga }
219 3     3   47 sub _build_cowbell { shift->drummer->cowbell }
220 3     3   46 sub _build_shaker { shift->drummer->maracas }
221 3     3   47 sub _build_closed { shift->drummer->closed_hh }
222 3     3   44 sub _build_open { shift->drummer->open_hh }
223 3     3   47 sub _build_cymbal { shift->drummer->crash1 }
224 3     3   45 sub _build_hi_tom { shift->drummer->hi_mid_tom }
225 3     3   46 sub _build_mid_tom { shift->drummer->low_mid_tom }
226 3     3   43 sub _build_low_tom { shift->drummer->low_tom }
227              
228             #pod =head1 METHODS
229             #pod
230             #pod =head2 new
231             #pod
232             #pod $grooves = MIDI::Drummer::Tiny::Grooves->new;
233             #pod $grooves = MIDI::Drummer::Tiny::Grooves->new(%arguments);
234             #pod
235             #pod Return a new C object.
236             #pod
237             #pod =head2 get_groove
238             #pod
239             #pod $groove = $grooves->get_groove($groove_number);
240             #pod $groove = $grooves->get_groove; # random groove
241             #pod $groove = $grooves->get_groove(0, $set); # random groove of set
242             #pod $groove = $grooves->get_groove($groove_number, $set); # numbered groove of set
243             #pod
244             #pod Return a numbered or random groove from either the given B or
245             #pod all known grooves.
246             #pod
247             #pod =cut
248              
249             sub get_groove {
250 0     0 1 0 my ($self, $groove_number, $set) = @_;
251 0 0       0 unless (keys %$set) {
252 0         0 $set = $self->all_grooves;
253             }
254 0 0       0 unless ($groove_number) {
255 0         0 my @keys = keys %$set;
256 0         0 $groove_number = $keys[ int rand @keys ];
257             }
258 0         0 return $set->{$groove_number};
259             }
260              
261             #pod =head2 all_grooves
262             #pod
263             #pod $all = $grooves->all_grooves;
264             #pod
265             #pod Return all the known grooves as a hash reference.
266             #pod
267             #pod =cut
268              
269             sub all_grooves {
270 4     4 1 614 my ($self) = @_;
271 4         118 return $self->_grooves();
272             }
273              
274             #pod =head2 search
275             #pod
276             #pod $set = $grooves->search({ cat => $x, name => $y }); # search all grooves
277             #pod $set = $grooves->search({ cat => $x, name => $y }, $set); # search a subset
278             #pod
279             #pod Return the found grooves with names matching the B or B
280             #pod strings and given an optional set of grooves to search in.
281             #pod
282             #pod =cut
283              
284             sub search {
285 3     3 1 3925 my ($self, $args, $set) = @_;
286 3 50 33     12 unless ($set && keys %$set) {
287 3         13 $set = $self->all_grooves;
288             }
289 3         14 my $found = {};
290 3 100       10 if ($args->{cat}) {
291 2         8 my $string = lc $args->{cat};
292 2         74 for my $k (keys %$set) {
293 536 100       1062 if (lc($set->{$k}{cat}) =~ /$string/) {
294 13         21 $found->{$k} = $set->{$k};
295             }
296             }
297             }
298 3 100       22 if ($args->{name}) {
299 1         3 my $string = lc $args->{name};
300 1         32 for my $k (keys %$set) {
301 268 100       582 if (lc($set->{$k}{name}) =~ /$string/) {
302 3         6 $found->{$k} = $set->{$k};
303             }
304             }
305             }
306 3         21 return $found;
307             }
308              
309             #pod =head2 groove
310             #pod
311             #pod $self->groove(\%patterns);
312             #pod
313             #pod Add the patterns to the score. If the B attribute is
314             #pod on, the patterns are just returned.
315             #pod
316             #pod =cut
317              
318             sub groove {
319 1     1 1 1599 my ($self, $patterns) = @_;
320 1 50       42 if ($self->return_patterns) {
321 1         14 return map { $_ => [ split '', $patterns->{$_}{pat}[0] ] } keys %$patterns;
  3         42  
322             }
323             else {
324             $self->drummer->sync_patterns(
325 0           (map { $patterns->{$_}{num} => $patterns->{$_}{pat} } keys %$patterns),
  0            
326             duration => $self->duration,
327             );
328             }
329             }
330              
331             1;
332              
333             __END__