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