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.0810';
7              
8 5     5   3287 use strict;
  5         13  
  5         147  
9 5     5   26 use warnings;
  5         8  
  5         119  
10              
11 5     5   3460 use MIDI::Simple ();
  5         109761  
  5         173  
12 5     5   3034 use Music::Duration;
  5         1948  
  5         404  
13              
14             BEGIN {
15             # Define a division structure to use for durations.
16 5         1693 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   36 };
  5         12  
26              
27             # Add constants for each known duration.
28 5     5   51 for my $n (keys %MIDI::Simple::Length) {
29             # Get the duration part of the note name.
30 160 50       863 my $name = $n =~ /([whqesxyz])n$/ ? $1 : '';
31              
32 160 50       294 if ($name) {
33             # Create a meaningful prefix for the named constant.
34 160         239 my $prefix = '';
35 160 100       456 $prefix .= 'triplet' if $n =~ /t\w/;
36 160 100       411 $prefix .= 'double_dotted' if $n =~ /^dd/;
37 160 100       366 $prefix .= 'dotted' if $n =~ /^d[^d]/;
38 160 100       291 $prefix .= '_' if $prefix;
39              
40             # Add name-based duration.
41 160         375 my $key = uc($prefix . DIVISION->{$name}{name});
42 160         3350 constant->import($key => $n); # LeoNerd++ clue
43             # Add a _prefix for numeric duration constants.
44 160 100       520 $prefix .= '_' unless $prefix;
45             # Add number-based duration.
46 160         371 $key = uc($prefix . DIVISION->{$name}{ordinal});
47 160         22778 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 1648 my $class = shift;
57             # Our drummer is a set of attributes.
58 9         104 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         54 bless $self, $class;
85              
86             # Perform any pre-flight default setting.
87 9         43 $self->_setup;
88              
89 9         33 return $self;
90             }
91              
92             sub _setup { # Where's my roadies, Man?
93 9     9   21 my $self = shift;
94              
95             # Give unto us, a score with which to fondle.
96 9   33     91 $self->{-score} ||= MIDI::Simple->new_score;
97 9         986 $self->{-score}->noop('c'.$self->{-channel}, 'V'.$self->{-volume});
98 9         482 $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     227 $self->{-kit} ||= $self->_default_kit;
102 9   33     48 $self->{-patterns} ||= $self->_default_patterns;
103              
104             # Set the groove dimensions if a time signature is given.
105 9 100       35 if ($self->{-signature}) {
106 1         6 $self->signature($self->{-signature});
107             }
108             else {
109             # If no signature is provided, assume 4/4.
110 8   50     27 $self->{-beats} ||= 4;
111 8   50     31 $self->{-divisions} ||= 4;
112 8         30 $self->{-signature} = "$self->{-beats}/$self->{-divisions}";
113             }
114              
115             $self->{-score}->time_signature(
116             $self->{-beats},
117             sqrt( $self->{-divisions} ),
118 9 50       71 ( $self->{-divisions} == 8 ? 24 : 18 ),
119             8
120             );
121              
122             # Reset the backbeat if the signature is a 3 multiple.
123 9         190 my $x = $self->{-beats} / 3;
124 9 100       83 if ($x !~ /\./) {
125 1         4 $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         18 for my $note (keys %{+DIVISION}) {
  9         51  
130 72 100       173 if (DIVISION->{$note}{number} == $self->{-divisions}) {
131 9         51 $self->div_name(uc DIVISION->{$note}{name});
132             }
133             }
134              
135             # Set effects.
136 9         71 $self->reverb;
137 9         39 $self->chorus;
138 9         41 $self->pan_width;
139              
140 9         16 return $self;
141             }
142              
143             # Convenience functions:
144 7     7   11011 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 1579 my $self = shift;
151 3 100       11 $self->{-channel} = shift if @_;
152 3         14 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 19 my $self = shift;
162 9 50       23 $self->{-reverb} = shift if @_;
163 9         44 $self->{-score}->control_change($self->{-channel}, 91, $self->{-reverb});
164 9         198 return $self->{-reverb};
165             }
166             sub chorus { # [0 .. 127]
167 9     9 1 17 my $self = shift;
168 9 50       57 $self->{-chorus} = shift if @_;
169 9         46 $self->{-score}->control_change($self->{-channel}, 93, $self->{-chorus});
170 9         143 return $self->{-chorus};
171             }
172             sub pan { # [0 Left-Middle-Right 127]
173 8     8 1 16 my $self = shift;
174 8 50       17 $self->{-pan} = shift if @_;
175 8         31 $self->{-score}->control_change($self->{-channel}, 10, $self->{-pan});
176 8         160 return $self->{-pan};
177             }
178             sub pan_width { # [0 .. 64] from center
179 17     17 1 33 my $self = shift;
180 17 50       57 $self->{-pan_width} = shift if @_;
181 17         43 return $self->{-pan_width};
182             }
183             sub bpm { # Beats per minute
184 2     2 1 511 my $self = shift;
185 2 100       8 $self->{-bpm} = shift if @_;
186 2         5 return $self->{-bpm};
187             }
188             sub volume { # TURN IT DOWN IN THERE!
189 22     22 1 1056 my $self = shift;
190 22 100       46 $self->{-volume} = shift if @_;
191 22         48 return $self->{-volume};
192             }
193             sub phrases { # o/` How many more times? Treat me the way you wanna do?
194 6     6 1 1625 my $self = shift;
195 6 100       17 $self->{-phrases} = shift if @_;
196 6         33 return $self->{-phrases};
197             }
198             sub bars { # Number of measures
199 2     2 1 1040 my $self = shift;
200 2 100       8 $self->{-bars} = shift if @_;
201 2         7 return $self->{-bars};
202             }
203             sub beats { # Beats per measure
204 77     77 1 1560 my $self = shift;
205 77 100       151 $self->{-beats} = shift if @_;
206 77         215 return $self->{-beats};
207             }
208             sub divisions { # The division of the measure that is "the pulse."
209 3     3 1 1570 my $self = shift;
210 3 100       12 $self->{-divisions} = shift if @_;
211 3         9 return $self->{-divisions};
212             }
213             sub signature { # The ratio of discipline
214 3     3 1 1046 my $self = shift;
215 3 100       11 if (@_) {
216             # Set the argument to the signature string.
217 2         4 $self->{-signature} = shift;
218             # Set the rhythm metrics.
219 2         9 ($self->{-beats}, $self->{-divisions}) = split /\//, $self->{-signature}, 2;
220             }
221 3         10 return $self->{-signature};
222             }
223             sub div_name { # The name of the denominator of the time signature.
224 12     12 1 25 my $self = shift;
225 12 100       47 $self->{-div_name} = shift if @_;
226 12         29 return $self->{-div_name};
227             }
228             sub file { # The name of the MIDI file output
229 2     2 1 1075 my $self = shift;
230 2 100       9 $self->{-file} = shift if @_;
231 2         7 return $self->{-file};
232             }
233              
234             sub score { # The MIDI::Simple score with no-op-ability
235 21     21 1 535 my $self = shift;
236              
237             # If we are presented with a M::S object, assign it as the score.
238 21 50       45 $self->{-score} = shift if ref $_[0] eq 'MIDI::Simple';
239              
240             # Set any remaining arguments as score no-ops.
241 21         57 $self->{-score}->noop($_) for @_;
242              
243 21         466 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 904 my $self = shift;
257 11 100       27 $self->{-accent} = shift if @_;
258              
259             # Add a bit of volume.
260 11         21 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       31 if $accent > $MIDI::Simple::Volume{fff};
264              
265             # Hand back the new volume.
266 11         37 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 170     170 1 1487 my $self = shift;
285 170         336 return $self->_type('-kit', @_);
286             }
287             sub patterns { # Coderefs of patterns
288 65     65 1 6898 my $self = shift;
289 65         157 return $self->_type('-patterns', @_);
290             }
291              
292             sub _type { # Both kit and pattern access
293 235     235   317 my $self = shift;
294 235   50     471 my $type = shift || return;
295              
296 235 100 33     588 if (!@_) { # If there are no arguments, return all known types.
    100          
    50          
297 52         300 return $self->{$type};
298             }
299             elsif (@_ == 1) { # Return a single named type with either name=>value or just value.
300 172         260 my $i = shift;
301             return wantarray
302             ? ($i => $self->{$type}{$i})
303 172 50       612 : $self->{$type}{$i};
304             }
305             elsif (@_ > 1 && !(@_ % 2)) { # Add new types if given an even list.
306 11         34 my %args = @_;
307 11         22 my @t = ();
308              
309 11         51 while (my ($i, $v) = each %args) {
310 11         28 $self->{$type}{$i} = $v;
311 11         65 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       96 : $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 566 my $self = shift;
327 1   50     5 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         4  
331             }
332              
333             sub _set_get { # Internal kit access
334 115     115   189 my $self = shift;
335 115   50     235 my $key = shift || return;
336              
337             # Set the kit event.
338 115 100       223 $self->kit($key => [@_]) if @_;
339              
340 115         249 return $self->option_strike(@{$self->kit($key)});
  115         212  
341             }
342              
343             # API: Add other keys to your kit & patterns, in a subclass.
344 2     2 1 513 sub backbeat { return shift->_set_get('backbeat', @_) }
345 35     35 1 1152 sub snare { return shift->_set_get('snare', @_) }
346 22     22 1 866 sub kick { return shift->_set_get('kick', @_) }
347 36     36 1 632 sub tick { return shift->_set_get('tick', @_) }
348 1     1 1 595 sub hhat { return shift->_set_get('hhat', @_) }
349 1     1 1 536 sub crash { return shift->_set_get('crash', @_) }
350 1     1 1 562 sub ride { return shift->_set_get('ride', @_) }
351 1     1 1 538 sub tom { return shift->_set_get('tom', @_) }
352              
353             sub strike { # Return note values.
354 188     188 1 3691 my $self = shift;
355              
356             # Set the patches, default snare.
357 188 100       407 my @patches = @_ ? @_ : @{$self->kit('snare')};
  21         56  
358              
359             # Build MIDI::Simple note names from the patch numbers.
360 188         323 my @notes = map { 'n' . $MIDI::percussion2notenum{$_} } @patches;
  190         705  
361              
362 188 100       787 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 119     119 1 1791 my $self = shift;
367              
368             # Set the patches, default crashes.
369 119 100       278 my @patches = @_ ? @_ : @{$self->kit('crash')};
  1         4  
370              
371             # Choose a random patch!
372 119         366 return $self->strike($patches[int(rand @patches)]);
373             }
374              
375             sub rotate { # Rotate through a list of patches.
376 15     15 1 3196 my $self = shift;
377 15   100     37 my $beat = shift || 1; # Assume that we are on the first beat if none is given.
378 15   66     35 my $patches = shift || $self->kit('backbeat'); # Default backbeat.
379              
380             # Strike a note from the patches, based on the beat.
381 15         42 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 4231 my $self = shift;
386              
387             # Set the default parameters with an argument override.
388 8         20 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     34 : $self->strike(@{$args{-tick}});
  7         18  
401              
402             # Rotate the backbeat.
403 8         23 my $n = $self->rotate($args{-beat}, $args{-backbeat});
404              
405             # Return the cymbal and backbeat note.
406 8 50       35 return wantarray ? ($n, $c) : join(',', $n, $c);
407             }
408              
409             # Readable, MIDI score pass-throughs.
410             sub note {
411 162     162 1 819 my $self = shift;
412             #use Data::Dumper;warn Data::Dumper->new([@_])->Indent(1)->Terse(1)->Sortkeys(1)->Dump;
413 162         411 return $self->{-score}->n(@_)
414             }
415 16     16 1 154 sub rest { return shift->{-score}->r(@_) }
416              
417             sub count_in { # And-a one, and-a two...
418 3     3 1 383 my $self = shift;
419 3   100     12 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       19 my $strike = @_ ? $self->strike(@_) : $self->tick;
424              
425             # Play the number of bars with a single strike.
426 3         10 for my $i (1 .. $self->beats * $bars) {
427             # Accent if we are on the first beat.
428 32 100       58 $self->score('V'.$self->accent) if $i % $self->beats == 1;
429              
430             # Add a note to the score.
431 32         106 $self->note($self->$div, $strike);
432              
433             # Reset the note volume if we just played the first beat.
434 32 100       1848 $self->score('V'.$self->volume) if $i % $self->beats == 1;
435             }
436              
437             # Hand back the note we just used.
438 3         10 return $strike;
439             }
440             sub metronome { # Keep time with a single patch. Default: Pedal Hi-Hat
441 2     2 1 911 my $self = shift;
442             # A metronome is just a count-in over the number of phrases
443 2   50     8 return $self->count_in($self->phrases, shift || 'Pedal Hi-Hat');
444             }
445              
446             sub beat { # Pattern selector method
447 25     25 1 11132 my $self = shift;
448             # Receive or default arguments.
449 25         200 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       78 return undef unless ref($self->patterns) eq 'HASH';
460              
461             # Get the names of the known patterns.
462 25         49 my @k = keys %{$self->patterns};
  25         49  
463             # Bail out if we know nothing.
464 25 50       77 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     208 ? "$args{-name} $args{-type}" : $args{-name};
469              
470 25 50       65 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     142 while ($n eq 0 || $n eq $args{-last}) {
475             # TODO API: Allow custom decision method.
476 27         188 $n = $k[int(rand @k)];
477 27 100       112 if ($args{-type}) {
478 13         182 (my $t = $n) =~ s/^.+\s+($args{-type})$/$1/;
479             # Skip if this is not a type for which we are looking.
480 13 100       79 $n = 0 unless $t eq $args{-type};
481             }
482             }
483             }
484              
485             # Beat it - i.e. add the pattern to the score.
486 25         155 $self->{-patterns}{$n}->($self, %args);
487             # Return the beat note.
488 25         1695 return $n;
489             }
490             sub fill {
491 3     3 1 1107 my $self = shift;
492             # Add the beat pattern to the score.
493 3         15 return $self->beat(@_, -type => 'fill');
494             }
495              
496             sub sync_tracks {
497 2     2 1 21 my $self = shift;
498 2         9 $self->{-score}->synch(@_);
499             }
500              
501             sub write { # You gotta get it out there, you know. Make some buzz, Man.
502 7     7 1 2155 my $self = shift;
503              
504             # Set the file if provided or use the default.
505 7   66     29 my $file = shift || $self->{-file};
506              
507             # Write the score to the file!
508 7         35 $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       17403 return -e $file ? $file : 0;
513             }
514              
515             # API: Redefine these methods in a subclass.
516             sub _default_kit {
517 9     9   21 my $self = shift;
518             # Hand back a set of instruments as lists of GM named patches.
519             return {
520 9         145 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   9 my $self = shift;
549 5         16 return {};
550             }
551              
552             1;
553              
554             __END__