File Coverage

blib/lib/MIDI/Simple/Drummer.pm
Criterion Covered Total %
statement 208 230 90.4
branch 84 108 77.7
condition 26 43 60.4
subroutine 52 57 91.2
pod 43 43 100.0
total 413 481 85.8


line stmt bran cond sub pod time code
1             package MIDI::Simple::Drummer;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: An algorithmic MIDI drummer
5              
6             our $VERSION = '0.0811';
7              
8 5     5   2552 use strict;
  5         11  
  5         120  
9 5     5   20 use warnings;
  5         9  
  5         97  
10              
11 5     5   2531 use MIDI::Simple ();
  5         87080  
  5         120  
12 5     5   1982 use Music::Duration ();
  5         1559  
  5         300  
13              
14             BEGIN {
15             # Define a division structure to use for durations.
16 5         1320 use constant DIVISION => {
17             w => { number => 1, ordinal => '1st', name => 'whole' },
18             h => { number => 2, ordinal => '2nd', name => 'half' },
19             q => { number => 4, ordinal => '4th', name => 'quarter' },
20             e => { number => 8, ordinal => '8th', name => 'eighth' },
21             s => { number => 16, ordinal => '16th', name => 'sixteenth' },
22             x => { number => 32, ordinal => '32nd', name => 'thirtysecond' },
23             y => { number => 64, ordinal => '64th', name => 'sixtyfourth' },
24             z => { number => 128, ordinal => '128th', name => 'onetwentyeighth' },
25 5     5   28 };
  5         10  
26              
27             # Add constants for each known duration.
28 5     5   39 for my $n (keys %MIDI::Simple::Length) {
29             # Get the duration part of the note name.
30 160 50       699 my $name = $n =~ /([whqesxyz])n$/ ? $1 : '';
31              
32 160 50       237 if ($name) {
33             # Create a meaningful prefix for the named constant.
34 160         183 my $prefix = '';
35 160 100       316 $prefix .= 'triplet' if $n =~ /t\w/;
36 160 100       279 $prefix .= 'double_dotted' if $n =~ /^dd/;
37 160 100       271 $prefix .= 'dotted' if $n =~ /^d[^d]/;
38 160 100       248 $prefix .= '_' if $prefix;
39              
40             # Add name-based duration.
41 160         304 my $key = uc($prefix . DIVISION->{$name}{name});
42 160         2646 constant->import($key => $n); # LeoNerd++ clue
43             # Add a _prefix for numeric duration constants.
44 160 100       377 $prefix .= '_' unless $prefix;
45             # Add number-based duration.
46 160         304 $key = uc($prefix . DIVISION->{$name}{ordinal});
47 160         18000 constant->import($key => $n);
48             }
49             else {
50 0         0 warn "ERROR: Unknown note value '$n' - Skipping."
51             }
52             }
53             }
54              
55             sub new { # Is there a drummer in the house?
56 9     9 1 1377 my $class = shift;
57             # Our drummer is a set of attributes.
58 9         82 my $self = {
59             # MIDI
60             -channel => 9,
61             -volume => 100,
62             -pan => 64,
63             -pan_width => 0,
64             -patch => 0,
65             -reverb => 20,
66             -chorus => 0,
67             # Rhythm
68             -accent => 30,
69             -bpm => 120,
70             -phrases => 4,
71             -bars => 4,
72             -beats => 4,
73             -divisions => 4,
74             -signature => '',
75             # The Goods™
76             -score => undef,
77             -file => 'Drummer.mid',
78             -kit => undef,
79             -patterns => undef,
80             @_ # Capture any override or extra arguments.
81             };
82              
83             # Make our drummer a proper object.
84 9         17 bless $self, $class;
85              
86             # Perform any pre-flight default setting.
87 9         29 $self->_setup;
88              
89 9         26 return $self;
90             }
91              
92             sub _setup { # Where's my roadies, Man?
93 9     9   15 my $self = shift;
94              
95             # Give unto us, a score with which to fondle.
96 9   33     69 $self->{-score} ||= MIDI::Simple->new_score;
97 9         790 $self->{-score}->noop('c'.$self->{-channel}, 'V'.$self->{-volume});
98 9         451 $self->{-score}->set_tempo(int(60_000_000 / $self->{-bpm}));
99              
100             # Give unto us a drum, so that we might bang upon it all day, instead of working.
101 9   33     189 $self->{-kit} ||= $self->_default_kit;
102 9   33     39 $self->{-patterns} ||= $self->_default_patterns;
103              
104             # Set the groove dimensions if a time signature is given.
105 9 100       21 if ($self->{-signature}) {
106 1         4 $self->signature($self->{-signature});
107             }
108             else {
109             # If no signature is provided, assume 4/4.
110 8   50     22 $self->{-beats} ||= 4;
111 8   50     19 $self->{-divisions} ||= 4;
112 8         24 $self->{-signature} = "$self->{-beats}/$self->{-divisions}";
113             }
114              
115             $self->{-score}->time_signature(
116             $self->{-beats},
117             sqrt( $self->{-divisions} ),
118 9 50       47 ( $self->{-divisions} == 8 ? 24 : 18 ),
119             8
120             );
121              
122             # Reset the backbeat if the signature is a 3 multiple.
123 9         139 my $x = $self->{-beats} / 3;
124 9 100       71 if ($x !~ /\./) {
125 1         3 $self->backbeat('Acoustic Bass Drum', 'Acoustic Snare', 'Acoustic Bass Drum');
126             }
127              
128             # Set the method name for the division metric. Ex: QUARTER for 4.
129 9         16 for my $note (keys %{+DIVISION}) {
  9         49  
130 72 100       140 if (DIVISION->{$note}{number} == $self->{-divisions}) {
131 9         53 $self->div_name(uc DIVISION->{$note}{name});
132             }
133             }
134              
135             # Set effects.
136 9         44 $self->reverb;
137 9         42 $self->chorus;
138 9         31 $self->pan_width;
139              
140 9         11 return $self;
141             }
142              
143             # Convenience functions:
144 7     7   34729 sub _durations { return \%MIDI::Simple::Length }
145 0     0   0 sub _n2p { return \%MIDI::notenum2percussion }
146 0     0   0 sub _p2n { return \%MIDI::percussion2notenum }
147              
148             # Accessors:
149             sub channel { # The general MIDI drumkit is often channel 9.
150 3     3 1 1188 my $self = shift;
151 3 100       10 $self->{-channel} = shift if @_;
152 3         11 return $self->{-channel};
153             }
154             sub patch { # Drum kit
155 0     0 1 0 my $self = shift;
156 0 0       0 $self->{-patch} = shift if @_;
157 0         0 $self->{-score}->patch_change($self->{-channel}, $self->{-patch});
158 0         0 return $self->{-patch};
159             }
160             sub reverb { # [0 .. 127]
161 9     9 1 14 my $self = shift;
162 9 50       25 $self->{-reverb} = shift if @_;
163 9         48 $self->{-score}->control_change($self->{-channel}, 91, $self->{-reverb});
164 9         133 return $self->{-reverb};
165             }
166             sub chorus { # [0 .. 127]
167 9     9 1 16 my $self = shift;
168 9 50       20 $self->{-chorus} = shift if @_;
169 9         27 $self->{-score}->control_change($self->{-channel}, 93, $self->{-chorus});
170 9         118 return $self->{-chorus};
171             }
172             sub pan { # [0 Left-Middle-Right 127]
173 8     8 1 10 my $self = shift;
174 8 50       13 $self->{-pan} = shift if @_;
175 8         21 $self->{-score}->control_change($self->{-channel}, 10, $self->{-pan});
176 8         147 return $self->{-pan};
177             }
178             sub pan_width { # [0 .. 64] from center
179 17     17 1 29 my $self = shift;
180 17 50       40 $self->{-pan_width} = shift if @_;
181 17         36 return $self->{-pan_width};
182             }
183             sub bpm { # Beats per minute
184 2     2 1 390 my $self = shift;
185 2 100       7 $self->{-bpm} = shift if @_;
186 2         3 return $self->{-bpm};
187             }
188             sub volume { # TURN IT DOWN IN THERE!
189 22     22 1 818 my $self = shift;
190 22 100       41 $self->{-volume} = shift if @_;
191 22         43 return $self->{-volume};
192             }
193             sub phrases { # o/` How many more times? Treat me the way you wanna do?
194 6     6 1 1101 my $self = shift;
195 6 100       19 $self->{-phrases} = shift if @_;
196 6         25 return $self->{-phrases};
197             }
198             sub bars { # Number of measures
199 2     2 1 797 my $self = shift;
200 2 100       7 $self->{-bars} = shift if @_;
201 2         4 return $self->{-bars};
202             }
203             sub beats { # Beats per measure
204 78     78 1 1227 my $self = shift;
205 78 100       128 $self->{-beats} = shift if @_;
206 78         185 return $self->{-beats};
207             }
208             sub divisions { # The division of the measure that is "the pulse."
209 3     3 1 1260 my $self = shift;
210 3 100       14 $self->{-divisions} = shift if @_;
211 3         8 return $self->{-divisions};
212             }
213             sub signature { # The ratio of discipline
214 3     3 1 823 my $self = shift;
215 3 100       8 if (@_) {
216             # Set the argument to the signature string.
217 2         5 $self->{-signature} = shift;
218             # Set the rhythm metrics.
219 2         9 ($self->{-beats}, $self->{-divisions}) = split /\//, $self->{-signature}, 2;
220             }
221 3         8 return $self->{-signature};
222             }
223             sub div_name { # The name of the denominator of the time signature.
224 12     12 1 18 my $self = shift;
225 12 100       30 $self->{-div_name} = shift if @_;
226 12         25 return $self->{-div_name};
227             }
228             sub file { # The name of the MIDI file output
229 2     2 1 826 my $self = shift;
230 2 100       8 $self->{-file} = shift if @_;
231 2         5 return $self->{-file};
232             }
233              
234             sub score { # The MIDI::Simple score with no-op-ability
235 21     21 1 414 my $self = shift;
236              
237             # If we are presented with a M::S object, assign it as the score.
238 21 50       37 $self->{-score} = shift if ref $_[0] eq 'MIDI::Simple';
239              
240             # Set any remaining arguments as score no-ops.
241 21         48 $self->{-score}->noop($_) for @_;
242              
243 21         385 return $self->{-score};
244             }
245              
246             sub accent_note { # Accent a single note.
247 0     0 1 0 my $self = shift;
248 0         0 my $note = shift;
249 0         0 $self->score('V' . $self->accent); # Accent!
250 0         0 $self->note($note, $self->strike);
251 0         0 $self->score('V' . $self->volume); # Reset the note volume.
252             }
253              
254             # API: Subclass and redefine to emit nuance.
255             sub accent { # Pump up the Volume!
256 11     11 1 723 my $self = shift;
257 11 100       21 $self->{-accent} = shift if @_;
258              
259             # Add a bit of volume.
260 11         24 my $accent = $self->{-accent} + $self->volume;
261             # But don't try to go above the top.
262             $accent = $MIDI::Simple::Volume{fff}
263 11 100       26 if $accent > $MIDI::Simple::Volume{fff};
264              
265             # Hand back the new volume.
266 11         31 return $accent;
267             }
268             # API: Subclass and redefine to emit nuance.
269             sub duck { # Drop the volume.
270 0     0 1 0 my $self = shift;
271 0 0       0 $self->{-accent} = shift if @_;
272              
273             # Subtract a bit of volume.
274 0         0 my $duck = $self->volume - $self->{-accent};
275             # But don't try to go below the bottom.
276             $duck = $MIDI::Simple::Volume{ppp}
277 0 0       0 if $duck > $MIDI::Simple::Volume{ppp};
278              
279             # Hand back the new volume.
280 0         0 return $duck;
281             }
282              
283             sub kit { # Arrayrefs of patches
284 120     120 1 1242 my $self = shift;
285 120         189 return $self->_type('-kit', @_);
286             }
287             sub patterns { # Coderefs of patterns
288 65     65 1 4882 my $self = shift;
289 65         131 return $self->_type('-patterns', @_);
290             }
291              
292             sub _type { # Both kit and pattern access
293 185     185   209 my $self = shift;
294 185   50     304 my $type = shift || return;
295              
296 185 100 33     406 if (!@_) { # If there are no arguments, return all known types.
    100          
    50          
297 52         187 return $self->{$type};
298             }
299             elsif (@_ == 1) { # Return a single named type with either name=>value or just value.
300 122         136 my $i = shift;
301             return wantarray
302             ? ($i => $self->{$type}{$i})
303 122 50       377 : $self->{$type}{$i};
304             }
305             elsif (@_ > 1 && !(@_ % 2)) { # Add new types if given an even list.
306 11         29 my %args = @_;
307 11         18 my @t = ();
308              
309 11         38 while (my ($i, $v) = each %args) {
310 11         23 $self->{$type}{$i} = $v;
311 11         34 push @t, $i;
312             }
313             # Return the named types.
314             return wantarray
315 0         0 ? (map { $_ => $self->{$type}{$_} } @t) # Hash of named types.
316             : @t > 1 # More than one?
317 0         0 ? [map { $self->{$type}{$_} } @t] # Arrayref of types.
318 11 50       137 : $self->{$type}{$t[0]}; # Else single type.
    50          
319             }
320             else { # Unlikely to ever be triggered.
321 0         0 warn 'WARNING: Mystery arguments. Giving up.';
322             }
323             }
324              
325             sub name_of { # Return instrument name(s) given kit keys.
326 1     1 1 471 my $self = shift;
327 1   50     4 my $key = shift || return;
328             return wantarray
329 0         0 ? @{$self->kit($key)} # List of names
330 1 50       4 : join ',', @{$self->kit($key)}; # CSV of names
  1         3  
331             }
332              
333             sub _set_get { # Internal kit access
334 57     57   73 my $self = shift;
335 57   50     169 my $key = shift || return;
336              
337             # Set the kit event.
338 57 100       95 $self->kit($key => [@_]) if @_;
339              
340 57         68 return $self->option_strike(@{$self->kit($key)});
  57         102  
341             }
342              
343             # API: Add other keys to your kit & patterns, in a subclass.
344 2     2 1 452 sub backbeat { return shift->_set_get('backbeat', @_) }
345 39     39 1 1049 sub snare { return shift->_set_get('snare', @_) }
346 8     8 1 564 sub kick { return shift->_set_get('kick', @_) }
347 4     4 1 440 sub tick { return shift->_set_get('tick', @_) }
348 1     1 1 469 sub hhat { return shift->_set_get('hhat', @_) }
349 1     1 1 466 sub crash { return shift->_set_get('crash', @_) }
350 1     1 1 452 sub ride { return shift->_set_get('ride', @_) }
351 1     1 1 430 sub tom { return shift->_set_get('tom', @_) }
352              
353             sub strike { # Return note values.
354 139     139 1 2178 my $self = shift;
355              
356             # Set the patches, default snare.
357 139 100       302 my @patches = @_ ? @_ : @{$self->kit('snare')};
  21         54  
358              
359             # Build MIDI::Simple note names from the patch numbers.
360 139         200 my @notes = map { 'n' . $MIDI::percussion2notenum{$_} } @patches;
  141         402  
361              
362 139 100       450 return wantarray ? @notes : join(',', @notes);
363             }
364             # API: Redefine this method to use a different decision than rand().
365             sub option_strike { # When in doubt, crash.
366 61     61 1 1379 my $self = shift;
367              
368             # Set the patches, default crashes.
369 61 100       146 my @patches = @_ ? @_ : @{$self->kit('crash')};
  1         3  
370              
371             # Choose a random patch!
372 61         183 return $self->strike($patches[int(rand @patches)]);
373             }
374              
375             sub rotate { # Rotate through a list of patches.
376 23     23 1 2556 my $self = shift;
377 23   100     48 my $beat = shift || 1; # Assume that we are on the first beat if none is given.
378 23   66     53 my $patches = shift || $self->kit('backbeat'); # Default backbeat.
379              
380             # Strike a note from the patches, based on the beat.
381 23         59 return $self->strike($patches->[$beat % @$patches]);
382             }
383             sub backbeat_rhythm { # AC/DC forever
384             # Rotate the backbeat with tick & post-fill strike.
385 8     8 1 3424 my $self = shift;
386              
387             # Set the default parameters with an argument override.
388 8         15 my %args = (
389             -beat => 1,
390             -fill => 0,
391             -backbeat => scalar $self->kit('backbeat'),
392             -tick => scalar $self->kit('tick'),
393             -patches => scalar $self->kit('crash'),
394             @_ # Capture any override or extra arguments.
395             );
396              
397             # Strike a cymbal or use the provided patches.
398             my $c = $args{-beat} == 1 && $args{-fill}
399 1         3 ? $self->option_strike(@{$args{-patches}})
400 8 100 100     30 : $self->strike(@{$args{-tick}});
  7         15  
401              
402             # Rotate the backbeat.
403 8         17 my $n = $self->rotate($args{-beat}, $args{-backbeat});
404              
405             # Return the cymbal and backbeat note.
406 8 50       27 return wantarray ? ($n, $c) : join(',', $n, $c);
407             }
408              
409             # Readable, MIDI score pass-throughs.
410             sub note {
411 129     129 1 602 my $self = shift;
412             #use Data::Dumper;warn Data::Dumper->new([@_])->Indent(1)->Terse(1)->Sortkeys(1)->Dump;
413 129         286 return $self->{-score}->n(@_)
414             }
415 8     8 1 25 sub rest { return shift->{-score}->r(@_) }
416              
417             sub count_in { # And-a one, and-a two...
418 3     3 1 306 my $self = shift;
419 3   100     9 my $bars = shift || 1; # Assume that we are on the first bar if none is given.
420 3         7 my $div = $self->div_name;
421              
422             # Define the note to strike with the given patch. Default 'tick' patch.
423 3 100       10 my $strike = @_ ? $self->strike(@_) : $self->tick;
424              
425             # Play the number of bars with a single strike.
426 3         12 for my $i (1 .. $self->beats * $bars) {
427             # Accent if we are on the first beat.
428 32 100       47 $self->score('V'.$self->accent) if $i % $self->beats == 1;
429              
430             # Add a note to the score.
431 32         86 $self->note($self->$div, $strike);
432              
433             # Reset the note volume if we just played the first beat.
434 32 100       1469 $self->score('V'.$self->volume) if $i % $self->beats == 1;
435             }
436              
437             # Hand back the note we just used.
438 3         6 return $strike;
439             }
440             sub metronome { # Keep time with a single patch. Default: Pedal Hi-Hat
441 2     2 1 737 my $self = shift;
442             # A metronome is just a count-in over the number of phrases
443 2   50     7 return $self->count_in($self->phrases, shift || 'Pedal Hi-Hat');
444             }
445              
446             sub beat { # Pattern selector method
447 25     25 1 8645 my $self = shift;
448             # Receive or default arguments.
449 25         132 my %args = (
450             -name => 0, # Provide a pattern name
451             -fill => 0, # Provide a fill pattern name
452             -last => 0, # Is this the last beat?
453             -type => '', # Is this a fill if not named in -fill?
454             -time => $self->QUARTER, # Default duration is a quarter note.
455             @_
456             );
457              
458             # Bail out unless we have a proper repertoire.
459 25 50       57 return undef unless ref($self->patterns) eq 'HASH';
460              
461             # Get the names of the known patterns.
462 25         33 my @k = keys %{$self->patterns};
  25         34  
463             # Bail out if we know nothing.
464 25 50       49 return undef unless @k;
465              
466             # Do we want a certain type that isn't already in the given name?
467             my $n = $args{-name} && $args{-type} && $args{-name} !~ /^.+\s+$args{-type}$/
468 25 100 66     130 ? "$args{-name} $args{-type}" : $args{-name};
469              
470 25 50       48 if (@k == 1) { # Return the pattern if there is only one.
471 0         0 $n = $k[0];
472             }
473             else { # Otherwise choose a different pattern.
474 25   100     105 while ($n eq 0 || $n eq $args{-last}) {
475             # TODO API: Allow custom decision method.
476 28         108 $n = $k[int(rand @k)];
477 28 100       82 if ($args{-type}) {
478 15         173 (my $t = $n) =~ s/^.+\s+($args{-type})$/$1/;
479             # Skip if this is not a type for which we are looking.
480 15 100       67 $n = 0 unless $t eq $args{-type};
481             }
482             }
483             }
484              
485             # Beat it - i.e. add the pattern to the score.
486 25         106 $self->{-patterns}{$n}->($self, %args);
487             # Return the beat note.
488 25         1622 return $n;
489             }
490             sub fill {
491 3     3 1 736 my $self = shift;
492             # Add the beat pattern to the score.
493 3         12 return $self->beat(@_, -type => 'fill');
494             }
495              
496             sub sync_tracks {
497 2     2 1 18 my $self = shift;
498 2         7 $self->{-score}->synch(@_);
499             }
500              
501             sub write { # You gotta get it out there, you know. Make some buzz, Man.
502 7     7 1 1604 my $self = shift;
503              
504             # Set the file if provided or use the default.
505 7   66     23 my $file = shift || $self->{-file};
506              
507             # Write the score to the file!
508 7         28 $self->{-score}->write_score($file);
509              
510             # Return the filename if it was created or zero if not.
511             # XXX Check file-size not existance.
512 7 50       11148 return -e $file ? $file : 0;
513             }
514              
515             # API: Redefine these methods in a subclass.
516             sub _default_kit {
517 9     9   16 my $self = shift;
518             # Hand back a set of instruments as lists of GM named patches.
519             return {
520 9         123 backbeat => ['Acoustic Snare', 'Acoustic Bass Drum'],
521             snare => ['Acoustic Snare'], # 38
522             kick => ['Acoustic Bass Drum'], # 35
523             tick => ['Closed Hi-Hat'],
524             hhat => ['Closed Hi-Hat', # 42
525             'Open Hi-Hat', # 46
526             'Pedal Hi-Hat', # 44
527             ],
528             crash => ['Chinese Cymbal', # 52
529             'Crash Cymbal 1', # 49
530             'Crash Cymbal 2', # 57
531             'Splash Cymbal', # 55
532             ],
533             ride => ['Ride Bell', # 53
534             'Ride Cymbal 1', # 51
535             'Ride Cymbal 2', # 59
536             ],
537             tom => ['High Tom', # 50
538             'Hi-Mid Tom', # 48
539             'Low-Mid Tom', # 47
540             'Low Tom', # 45
541             'High Floor Tom', # 43
542             'Low Floor Tom', # 41
543             ],
544             };
545             }
546             # There are no known patterns. We are a wannabe at this point.
547             sub _default_patterns {
548 5     5   8 my $self = shift;
549 5         19 return {};
550             }
551              
552             1;
553              
554             __END__