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.0813';
7              
8 5     5   3185 use strict;
  5         11  
  5         142  
9 5     5   24 use warnings;
  5         9  
  5         114  
10              
11 5     5   3225 use MIDI::Simple ();
  5         102758  
  5         143  
12 5     5   2366 use Music::Duration ();
  5         1821  
  5         373  
13              
14             BEGIN {
15             # Define a division structure to use for durations.
16 5         1664 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   33 };
  5         14  
26              
27             # Add constants for each known duration.
28 5     5   47 for my $n (keys %MIDI::Simple::Length) {
29             # Get the duration part of the note name.
30 160 50       834 my $name = $n =~ /([whqesxyz])n$/ ? $1 : '';
31              
32 160 50       337 if ($name) {
33             # Create a meaningful prefix for the named constant.
34 160         230 my $prefix = '';
35 160 100       415 $prefix .= 'triplet' if $n =~ /t\w/;
36 160 100       347 $prefix .= 'double_dotted' if $n =~ /^dd/;
37 160 100       337 $prefix .= 'dotted' if $n =~ /^d[^d]/;
38 160 100       297 $prefix .= '_' if $prefix;
39              
40             # Add name-based duration.
41 160         388 my $key = uc($prefix . DIVISION->{$name}{name});
42 160         3170 constant->import($key => $n); # LeoNerd++ clue
43             # Add a _prefix for numeric duration constants.
44 160 100       456 $prefix .= '_' unless $prefix;
45             # Add number-based duration.
46 160         372 $key = uc($prefix . DIVISION->{$name}{ordinal});
47 160         20580 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 1621 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         23 bless $self, $class;
85              
86             # Perform any pre-flight default setting.
87 9         38 $self->_setup;
88              
89 9         31 return $self;
90             }
91              
92             sub _setup { # Where's my roadies, Man?
93 9     9   18 my $self = shift;
94              
95             # Give unto us, a score with which to fondle.
96 9   33     85 $self->{-score} ||= MIDI::Simple->new_score;
97 9         999 $self->{-score}->noop('c'.$self->{-channel}, 'V'.$self->{-volume});
98 9         478 $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     233 $self->{-kit} ||= $self->_default_kit;
102 9   33     53 $self->{-patterns} ||= $self->_default_patterns;
103              
104             # Set the groove dimensions if a time signature is given.
105 9 100       29 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     24 $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       73 ( $self->{-divisions} == 8 ? 24 : 18 ),
119             8
120             );
121              
122             # Reset the backbeat if the signature is a 3 multiple.
123 9         170 my $x = $self->{-beats} / 3;
124 9 100       98 if ($x !~ /\./) {
125 1         6 $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         26 for my $note (keys %{+DIVISION}) {
  9         56  
130 72 100       171 if (DIVISION->{$note}{number} == $self->{-divisions}) {
131 9         53 $self->div_name(uc DIVISION->{$note}{name});
132             }
133             }
134              
135             # Set effects.
136 9         52 $self->reverb;
137 9         50 $self->chorus;
138 9         61 $self->pan_width;
139              
140 9         15 return $self;
141             }
142              
143             # Convenience functions:
144 7     7   10644 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 1862 my $self = shift;
151 3 100       11 $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 17 my $self = shift;
162 9 50       23 $self->{-reverb} = shift if @_;
163 9         58 $self->{-score}->control_change($self->{-channel}, 91, $self->{-reverb});
164 9         167 return $self->{-reverb};
165             }
166             sub chorus { # [0 .. 127]
167 9     9 1 18 my $self = shift;
168 9 50       27 $self->{-chorus} = shift if @_;
169 9         31 $self->{-score}->control_change($self->{-channel}, 93, $self->{-chorus});
170 9         149 return $self->{-chorus};
171             }
172             sub pan { # [0 Left-Middle-Right 127]
173 8     8 1 12 my $self = shift;
174 8 50       17 $self->{-pan} = shift if @_;
175 8         25 $self->{-score}->control_change($self->{-channel}, 10, $self->{-pan});
176 8         129 return $self->{-pan};
177             }
178             sub pan_width { # [0 .. 64] from center
179 17     17 1 32 my $self = shift;
180 17 50       56 $self->{-pan_width} = shift if @_;
181 17         35 return $self->{-pan_width};
182             }
183             sub bpm { # Beats per minute
184 2     2 1 499 my $self = shift;
185 2 100       7 $self->{-bpm} = shift if @_;
186 2         8 return $self->{-bpm};
187             }
188             sub volume { # TURN IT DOWN IN THERE!
189 22     22 1 1057 my $self = shift;
190 22 100       46 $self->{-volume} = shift if @_;
191 22         54 return $self->{-volume};
192             }
193             sub phrases { # o/` How many more times? Treat me the way you wanna do?
194 6     6 1 1452 my $self = shift;
195 6 100       22 $self->{-phrases} = shift if @_;
196 6         29 return $self->{-phrases};
197             }
198             sub bars { # Number of measures
199 2     2 1 983 my $self = shift;
200 2 100       8 $self->{-bars} = shift if @_;
201 2         5 return $self->{-bars};
202             }
203             sub beats { # Beats per measure
204 78     78 1 1531 my $self = shift;
205 78 100       147 $self->{-beats} = shift if @_;
206 78         233 return $self->{-beats};
207             }
208             sub divisions { # The division of the measure that is "the pulse."
209 3     3 1 1548 my $self = shift;
210 3 100       13 $self->{-divisions} = shift if @_;
211 3         11 return $self->{-divisions};
212             }
213             sub signature { # The ratio of discipline
214 3     3 1 1111 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         10 ($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 21 my $self = shift;
225 12 100       38 $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 995 my $self = shift;
230 2 100       7 $self->{-file} = shift if @_;
231 2         6 return $self->{-file};
232             }
233              
234             sub score { # The MIDI::Simple score with no-op-ability
235 21     21 1 539 my $self = shift;
236              
237             # If we are presented with a M::S object, assign it as the score.
238 21 50       50 $self->{-score} = shift if ref $_[0] eq 'MIDI::Simple';
239              
240             # Set any remaining arguments as score no-ops.
241 21         59 $self->{-score}->noop($_) for @_;
242              
243 21         503 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 894 my $self = shift;
257 11 100       41 $self->{-accent} = shift if @_;
258              
259             # Add a bit of volume.
260 11         26 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       29 if $accent > $MIDI::Simple::Volume{fff};
264              
265             # Hand back the new volume.
266 11         45 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 218     218 1 1610 my $self = shift;
285 218         391 return $self->_type('-kit', @_);
286             }
287             sub patterns { # Coderefs of patterns
288 65     65 1 5946 my $self = shift;
289 65         156 return $self->_type('-patterns', @_);
290             }
291              
292             sub _type { # Both kit and pattern access
293 283     283   396 my $self = shift;
294 283   50     541 my $type = shift || return;
295              
296 283 100 33     706 if (!@_) { # If there are no arguments, return all known types.
    100          
    50          
297 52         209 return $self->{$type};
298             }
299             elsif (@_ == 1) { # Return a single named type with either name=>value or just value.
300 220         308 my $i = shift;
301             return wantarray
302             ? ($i => $self->{$type}{$i})
303 220 50       772 : $self->{$type}{$i};
304             }
305             elsif (@_ > 1 && !(@_ % 2)) { # Add new types if given an even list.
306 11         35 my %args = @_;
307 11         24 my @t = ();
308              
309 11         47 while (my ($i, $v) = each %args) {
310 11         28 $self->{$type}{$i} = $v;
311 11         53 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       95 : $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 563 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       3 : join ',', @{$self->kit($key)}; # CSV of names
  1         2  
331             }
332              
333             sub _set_get { # Internal kit access
334 163     163   235 my $self = shift;
335 163   50     332 my $key = shift || return;
336              
337             # Set the kit event.
338 163 100       313 $self->kit($key => [@_]) if @_;
339              
340 163         201 return $self->option_strike(@{$self->kit($key)});
  163         300  
341             }
342              
343             # API: Add other keys to your kit & patterns, in a subclass.
344 2     2 1 521 sub backbeat { return shift->_set_get('backbeat', @_) }
345 34     34 1 879 sub snare { return shift->_set_get('snare', @_) }
346 31     31 1 982 sub kick { return shift->_set_get('kick', @_) }
347 60     60 1 685 sub tick { return shift->_set_get('tick', @_) }
348 1     1 1 629 sub hhat { return shift->_set_get('hhat', @_) }
349 1     1 1 534 sub crash { return shift->_set_get('crash', @_) }
350 1     1 1 546 sub ride { return shift->_set_get('ride', @_) }
351 1     1 1 529 sub tom { return shift->_set_get('tom', @_) }
352              
353             sub strike { # Return note values.
354 224     224 1 2818 my $self = shift;
355              
356             # Set the patches, default snare.
357 224 100       453 my @patches = @_ ? @_ : @{$self->kit('snare')};
  21         58  
358              
359             # Build MIDI::Simple note names from the patch numbers.
360 224         358 my @notes = map { 'n' . $MIDI::percussion2notenum{$_} } @patches;
  226         681  
361              
362 224 100       876 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 167     167 1 1868 my $self = shift;
367              
368             # Set the patches, default crashes.
369 167 100       353 my @patches = @_ ? @_ : @{$self->kit('crash')};
  1         4  
370              
371             # Choose a random patch!
372 167         457 return $self->strike($patches[int(rand @patches)]);
373             }
374              
375             sub rotate { # Rotate through a list of patches.
376 15     15 1 3234 my $self = shift;
377 15   100     38 my $beat = shift || 1; # Assume that we are on the first beat if none is given.
378 15   66     36 my $patches = shift || $self->kit('backbeat'); # Default backbeat.
379              
380             # Strike a note from the patches, based on the beat.
381 15         40 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 4337 my $self = shift;
386              
387             # Set the default parameters with an argument override.
388 8         21 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     47 : $self->strike(@{$args{-tick}});
  7         20  
401              
402             # Rotate the backbeat.
403 8         21 my $n = $self->rotate($args{-beat}, $args{-backbeat});
404              
405             # Return the cymbal and backbeat note.
406 8 50       36 return wantarray ? ($n, $c) : join(',', $n, $c);
407             }
408              
409             # Readable, MIDI score pass-throughs.
410             sub note {
411 172     172 1 833 my $self = shift;
412             #use Data::Dumper;warn Data::Dumper->new([@_])->Indent(1)->Terse(1)->Sortkeys(1)->Dump;
413 172         446 return $self->{-score}->n(@_)
414             }
415 29     29 1 297 sub rest { return shift->{-score}->r(@_) }
416              
417             sub count_in { # And-a one, and-a two...
418 3     3 1 381 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       12 my $strike = @_ ? $self->strike(@_) : $self->tick;
424              
425             # Play the number of bars with a single strike.
426 3         14 for my $i (1 .. $self->beats * $bars) {
427             # Accent if we are on the first beat.
428 32 100       62 $self->score('V'.$self->accent) if $i % $self->beats == 1;
429              
430             # Add a note to the score.
431 32         105 $self->note($self->$div, $strike);
432              
433             # Reset the note volume if we just played the first beat.
434 32 100       1838 $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 912 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 9971 my $self = shift;
448             # Receive or default arguments.
449 25         173 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       70 return undef unless ref($self->patterns) eq 'HASH';
460              
461             # Get the names of the known patterns.
462 25         55 my @k = keys %{$self->patterns};
  25         47  
463             # Bail out if we know nothing.
464 25 50       71 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     168 ? "$args{-name} $args{-type}" : $args{-name};
469              
470 25 50       75 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     118 while ($n eq 0 || $n eq $args{-last}) {
475             # TODO API: Allow custom decision method.
476 28         134 $n = $k[int(rand @k)];
477 28 100       100 if ($args{-type}) {
478 14         154 (my $t = $n) =~ s/^.+\s+($args{-type})$/$1/;
479             # Skip if this is not a type for which we are looking.
480 14 100       74 $n = 0 unless $t eq $args{-type};
481             }
482             }
483             }
484              
485             # Beat it - i.e. add the pattern to the score.
486 25         130 $self->{-patterns}{$n}->($self, %args);
487             # Return the beat note.
488 25         1748 return $n;
489             }
490             sub fill {
491 3     3 1 1004 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 25 my $self = shift;
498 2         11 $self->{-score}->synch(@_);
499             }
500              
501             sub write { # You gotta get it out there, you know. Make some buzz, Man.
502 7     7 1 2062 my $self = shift;
503              
504             # Set the file if provided or use the default.
505 7   66     39 my $file = shift || $self->{-file};
506              
507             # Write the score to the file!
508 7         36 $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       19333 return -e $file ? $file : 0;
513             }
514              
515             # API: Redefine these methods in a subclass.
516             sub _default_kit {
517 9     9   19 my $self = shift;
518             # Hand back a set of instruments as lists of GM named patches.
519             return {
520 9         148 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   10 my $self = shift;
549 5         30 return {};
550             }
551              
552             1;
553              
554             __END__