File Coverage

blib/lib/MIDI/Drummer/Tiny.pm
Criterion Covered Total %
statement 198 415 47.7
branch 31 90 34.4
condition 13 30 43.3
subroutine 34 46 73.9
pod 20 21 95.2
total 296 602 49.1


line stmt bran cond sub pod time code
1             package MIDI::Drummer::Tiny;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Glorified metronome
5              
6             our $VERSION = '0.7002';
7              
8 6     6   1132459 use 5.024;
  6         26  
9 6     6   1840 use strictures 2;
  6         6019  
  6         269  
10 6     6   2825 use Carp;
  6         13  
  6         595  
11 6     6   40 use List::Util 1.26 qw(sum0);
  6         253  
  6         451  
12 6     6   3034 use Moo;
  6         48859  
  6         37  
13 6     6   8947 use experimental qw(signatures);
  6         12259  
  6         39  
14 6     6   4045 use Math::Bezier ();
  6         4931  
  6         194  
15 6         576 use MIDI::Util qw(
16             dura_size
17             reverse_dump
18             set_time_signature
19             ticks
20 6     6   2657 );
  6         195615  
21 6     6   3261 use Music::Duration ();
  6         3015  
  6         196  
22 6     6   3116 use Music::RhythmSet::Util qw(upsize);
  6         25244  
  6         521  
23              
24 6     6   3464 use MIDI::Drummer::Tiny::Types qw(:all);
  6         392  
  6         70  
25 6     6   193929 use Types::Standard qw(InstanceOf);
  6         17  
  6         76  
26              
27 6     6   21303 use Data::Dumper::Compact qw(ddc);
  6         87166  
  6         54  
28 6     6   4309 use namespace::clean;
  6         102490  
  6         90  
29              
30 6     6   16705 use constant STRAIGHT => 50; # Swing percent
  6         20  
  6         6184  
31              
32             #pod =head1 SYNOPSIS
33             #pod
34             #pod use MIDI::Drummer::Tiny;
35             #pod
36             #pod my $d = MIDI::Drummer::Tiny->new(
37             #pod file => 'drums.mid',
38             #pod bpm => 100,
39             #pod volume => 100,
40             #pod signature => '5/4',
41             #pod bars => 8,
42             #pod reverb => 0,
43             #pod soundfont => '/you/soundfonts/TR808.sf2', # option
44             #pod #kick => 36, # Override default patch
45             #pod #snare => 40, # "
46             #pod );
47             #pod
48             #pod $d->metronome5;
49             #pod
50             #pod $d->set_time_sig('4/4');
51             #pod $d->count_in(1); # Closed hi-hat for 1 bar
52             #pod
53             #pod $d->metronome4($d->bars, $d->closed_hh, $d->eighth, 60); # 60% swing!
54             #pod
55             #pod $d->rest($d->whole);
56             #pod
57             #pod $d->flam($d->quarter, $d->snare);
58             #pod $d->crescendo_roll([50, 127, 1], $d->eighth, $d->thirtysecond);
59             #pod $d->note($d->sixteenth, $d->crash1);
60             #pod $d->accent_note(127, $d->sixteenth, $d->crash2);
61             #pod
62             #pod # Alternate kick and snare
63             #pod $d->note($d->quarter, $d->open_hh, $_ % 2 ? $d->kick : $d->snare)
64             #pod for 1 .. $d->beats * $d->bars;
65             #pod
66             #pod # Same but with beat-strings:
67             #pod $d->sync_patterns(
68             #pod $d->open_hh => [ '1111' ],
69             #pod $d->snare => [ '0101' ],
70             #pod $d->kick => [ '1010' ],
71             #pod ) for 1 .. $d->bars;
72             #pod
73             #pod my $patterns = [
74             #pod your_function(5, 16), # e.g. a euclidean function
75             #pod your_function(7, 16), # ...
76             #pod ];
77             #pod $d->pattern( instrument => $d->kick, patterns => $patterns ); # see doc...
78             #pod
79             #pod $d->add_fill('...'); # see doc...
80             #pod
81             #pod print 'Count: ', $d->counter, "\n";
82             #pod
83             #pod # As a convenience, and sometimes necessity:
84             #pod $d->set_bpm(200); # handy for tempo changes
85             #pod $d->set_channel; # reset back to 9 if ever changed
86             #pod
87             #pod $d->write;
88             #pod
89             #pod =head1 DESCRIPTION
90             #pod
91             #pod This module provides handy defaults and tools to produce a MIDI score
92             #pod with drum parts. It is full of tools to construct a score with drum
93             #pod parts. It is not a traditional "drum machine." Rather, it contains
94             #pod methods to construct a drum machine, or play "as a drummer might."
95             #pod
96             #pod Below, the term "spec" refers to a note length duration, like an
97             #pod eighth or quarter note, for instance.
98             #pod
99             #pod =for Pod::Coverage BUILD
100             #pod
101             #pod =cut
102              
103 6     6 0 232 sub BUILD ( $self, $args_ref ) {
  6         15  
  6         14  
  6         12  
104 6 50       76 return unless $self->setup;
105              
106 6         161 $self->score->noop( 'c' . $self->channel, 'V' . $self->volume );
107              
108 6         1581 $self->score->set_tempo( int( 60_000_000 / $self->bpm ) );
109              
110 6         475 $self->score->control_change( $self->channel, 91, $self->reverb );
111              
112             # Add a TS to the score but don't reset the beats if given
113 6         305 $self->set_time_sig( $self->signature, !$args_ref->{beats} );
114 6         382 return;
115             }
116              
117             #pod =attr verbose
118             #pod
119             #pod Default: C<0>
120             #pod
121             #pod =attr file
122             #pod
123             #pod This the MIDI file name to write. It can be a string, a
124             #pod L, or a L.
125             #pod
126             #pod Default: C
127             #pod
128             #pod =cut
129              
130             has file => (
131             is => 'rw',
132             isa => MIDI_File,
133             coerce => 1,
134             default => 'MIDI-Drummer.mid',
135             );
136              
137             #pod =attr soundfont
138             #pod
139             #pod $soundfont = $d->soundfont;
140             #pod
141             #pod This is the location of the soundfont file. It can be a string or a
142             #pod L.
143             #pod
144             #pod =cut
145              
146             has soundfont => (
147             is => 'rw',
148             isa => Soundfont_File,
149             coerce => 1,
150             );
151              
152             #pod =attr score
153             #pod
154             #pod Default: Cnew_score>
155             #pod
156             #pod =method sync
157             #pod
158             #pod $d->sync(@code_refs);
159             #pod
160             #pod This is a simple pass-through to the B C method.
161             #pod
162             #pod This allows simultaneous playing of multiple "tracks" defined by code
163             #pod references.
164             #pod
165             #pod =cut
166              
167             has score => (
168             is => 'lazy',
169             isa => InstanceOf ['MIDI::Simple'],
170             handles => { sync => 'synch' },
171             );
172              
173             sub _build_score {
174 6     6   82 my ($self) = @_;
175 6         84 return MIDI::Simple->new_score;
176             }
177              
178             #pod =attr reverb
179             #pod
180             #pod Default: C<15>
181             #pod
182             #pod =attr channel
183             #pod
184             #pod Default: C<9>
185             #pod
186             #pod =method set_channel
187             #pod
188             #pod $d->set_channel;
189             #pod $d->set_channel($channel);
190             #pod
191             #pod Reset the channel to C<9> by default, or the given argument if
192             #pod different.
193             #pod
194             #pod =cut
195              
196             has channel => (
197             is => 'rwp',
198             isa => Channel,
199             default => 9,
200             );
201              
202 2     2 1 3393 sub set_channel ( $self, $channel = 9 ) {
  2         4  
  2         6  
  2         3  
203 2         76 $self->score->noop("c$channel");
204 2         174 $self->_set_channel($channel);
205 2         80 return;
206             }
207              
208             #pod =attr volume
209             #pod
210             #pod Default: C<100>
211             #pod
212             #pod =method set_volume
213             #pod
214             #pod $d->set_volume;
215             #pod $d->set_volume($volume);
216             #pod
217             #pod Set the volume (L) to the given argument.
218             #pod
219             #pod If not given a B argument, this method mutes (sets to C<0>).
220             #pod
221             #pod =cut
222              
223             has volume => (
224             is => 'rwp',
225             isa => Velocity,
226             default => 100,
227              
228             );
229              
230 2     2 1 2156 sub set_volume ( $self, $volume = 0 ) {
  2         6  
  2         5  
  2         4  
231 2         75 $self->score->noop("V$volume");
232 2         187 $self->_set_volume($volume);
233 2         79 return;
234             }
235              
236             #pod =attr bpm
237             #pod
238             #pod Default: C<120>
239             #pod
240             #pod =method set_bpm
241             #pod
242             #pod $d->set_bpm($bpm);
243             #pod
244             #pod Return or set the beats per minute.
245             #pod
246             #pod =cut
247              
248             has bpm => (
249             is => 'rw',
250             isa => PosInt,
251             default => 120,
252             writer => 'set_bpm',
253             trigger => 1,
254             );
255              
256 1     1   5562 sub _trigger_bpm ( $self, $bpm = 120 ) { ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
  1         3  
  1         3  
  1         20  
257 1         34 $self->score->set_tempo( int( 60_000_000 / $bpm ) );
258 1         41 return;
259             }
260              
261             #pod =attr bars
262             #pod
263             #pod Default: C<4>
264             #pod
265             #pod =attr signature
266             #pod
267             #pod Default: C<4/4>
268             #pod
269             #pod B / B
270             #pod
271             #pod =attr beats
272             #pod
273             #pod Computed from the B, if not given in the constructor.
274             #pod
275             #pod Default: C<4>
276             #pod
277             #pod =attr divisions
278             #pod
279             #pod Computed from the B.
280             #pod
281             #pod Default: C<4>
282             #pod
283             #pod =attr setup
284             #pod
285             #pod Run the commands in the C method that set-up new midi score
286             #pod events like tempo, time signature, etc.
287             #pod
288             #pod Default: C<1>
289             #pod
290             #pod =attr counter
291             #pod
292             #pod $d->counter( $d->counter + $duration );
293             #pod $count = $d->counter;
294             #pod
295             #pod Beat counter of durations, where a quarter-note is equal to 1. An
296             #pod eighth-note is 0.5, etc.
297             #pod
298             #pod This is automatically accumulated each time a C or C is
299             #pod added to the score.
300             #pod
301             #pod =cut
302              
303             my %attr_defaults = (
304             ro => {
305             verbose => 0,
306             reverb => 15,
307             bars => 4,
308             },
309             rw => {
310             signature => '4/4',
311             beats => 4,
312             divisions => 4,
313             setup => 1,
314             counter => 0,
315             },
316             );
317             for my $is ( keys %attr_defaults ) {
318             for my $attr ( keys %{ $attr_defaults{$is} } ) {
319             has $attr => (
320             is => $is,
321             default => $attr_defaults{$is}{$attr},
322             );
323             }
324             }
325              
326             #pod =kit metronome
327             #pod
328             #pod =over
329             #pod
330             #pod =item click, bell
331             #pod
332             #pod =back
333             #pod
334             #pod =kit hi-hats
335             #pod
336             #pod =over
337             #pod
338             #pod =item open_hh, closed_hh, pedal_hh
339             #pod
340             #pod =back
341             #pod
342             #pod =kit crash and splash cymbals
343             #pod
344             #pod =over
345             #pod
346             #pod =item crash1, crash2, splash, china
347             #pod
348             #pod =back
349             #pod
350             #pod =kit ride cymbals
351             #pod
352             #pod =over
353             #pod
354             #pod =item ride1, ride2, ride_bell
355             #pod
356             #pod =back
357             #pod
358             #pod =kit snare drums and handclaps
359             #pod
360             #pod =over
361             #pod
362             #pod =item snare, acoustic_snare, electric_snare, side_stick, clap
363             #pod
364             #pod Where the B is by default the same as the B but
365             #pod can be overridden with the B (C<40>).
366             #pod
367             #pod =back
368             #pod
369             #pod =kit tom-toms
370             #pod
371             #pod =over
372             #pod
373             #pod =item hi_tom, hi_mid_tom, low_mid_tom, low_tom, hi_floor_tom, low_floor_tom
374             #pod
375             #pod =back
376             #pod
377             #pod =kit bass drums
378             #pod
379             #pod =over
380             #pod
381             #pod =item kick, acoustic_bass, electric_bass
382             #pod
383             #pod Where the B is by default the same as the B but
384             #pod can be overridden with the B (C<36>).
385             #pod
386             #pod =back
387             #pod
388             #pod =kit auxiliary percussion
389             #pod
390             #pod =over
391             #pod
392             #pod =item tambourine, cowbell, vibraslap
393             #pod
394             #pod =back
395             #pod
396             #pod =kit Latin and African drums
397             #pod
398             #pod =over
399             #pod
400             #pod =item hi_bongo, low_bongo, mute_hi_conga, open_hi_conga, low_conga, high_timbale, low_timbale
401             #pod
402             #pod =back
403             #pod
404             #pod =kit Latin and African auxiliary percussion
405             #pod
406             #pod =over
407             #pod
408             #pod =item high_agogo, low_agogo, cabasa, maracas, short_whistle, long_whistle, short_guiro, long_guiro, claves, hi_wood_block, low_wood_block, mute_cuica, open_cuica
409             #pod
410             #pod =back
411             #pod
412             #pod =kit triangles
413             #pod
414             #pod =over
415             #pod
416             #pod =item mute_triangle, open_triangle
417             #pod
418             #pod =back
419             #pod
420             #pod =cut
421              
422 6     6   82 use constant PERCUSSION_START => 33;
  6         14  
  6         48230  
423             for my $sound ( qw(
424             click
425             bell
426             acoustic_bass
427             electric_bass
428             side_stick
429             acoustic_snare
430             clap
431             electric_snare
432             low_floor_tom
433             closed_hh
434             hi_floor_tom
435             pedal_hh
436             low_tom
437             open_hh
438             low_mid_tom
439             hi_mid_tom
440             crash1
441             hi_tom
442             ride1
443             china
444             ride_bell
445             tambourine
446             splash
447             cowbell
448             crash2
449             vibraslap
450             ride2
451             hi_bongo
452             low_bongo
453             mute_hi_conga
454             open_hi_conga
455             low_conga
456             high_timbale
457             low_timbale
458             high_agogo
459             low_agogo
460             cabasa
461             maracas
462             short_whistle
463             long_whistle
464             short_guiro
465             long_guiro
466             claves
467             hi_wood_block
468             low_wood_block
469             mute_cuica
470             open_cuica
471             mute_triangle
472             open_triangle
473             ) )
474             {
475             state $percussion_note;
476             has $sound => (
477             is => 'ro',
478             isa => PercussionNote,
479             default => PERCUSSION_START + $percussion_note++,
480             );
481             }
482             has kick => (
483             is => 'ro',
484             isa => PercussionNote,
485             default => 35, # Alt: 36
486             );
487             has snare => (
488             is => 'ro',
489             isa => PercussionNote,
490             default => 38, # Alt: 40
491             );
492              
493             #pod =duration whole notes
494             #pod
495             #pod =over
496             #pod
497             #pod =item whole, triplet_whole, dotted_whole, double_dotted_whole
498             #pod
499             #pod =back
500             #pod
501             #pod =duration half notes
502             #pod
503             #pod =over
504             #pod
505             #pod =item half, triplet_half, dotted_half, double_dotted_half
506             #pod
507             #pod =back
508             #pod
509             #pod =duration quarter notes
510             #pod
511             #pod =over
512             #pod
513             #pod =item quarter, triplet_quarter, dotted_quarter, double_dotted_quarter
514             #pod
515             #pod =back
516             #pod
517             #pod =duration eighth notes
518             #pod
519             #pod =over
520             #pod
521             #pod =item eighth, triplet_eighth, dotted_eighth, double_dotted_eighth
522             #pod
523             #pod =back
524             #pod
525             #pod =duration sixteenth notes
526             #pod
527             #pod =over
528             #pod
529             #pod =item sixteenth, triplet_sixteenth, dotted_sixteenth, double_dotted_sixteenth
530             #pod
531             #pod =back
532             #pod
533             #pod =duration thirty-secondth notes
534             #pod
535             #pod =over
536             #pod
537             #pod =item thirtysecond, triplet_thirtysecond, dotted_thirtysecond, double_dotted_thirtysecond
538             #pod
539             #pod =back
540             #pod
541             #pod =duration sixty-fourth notes
542             #pod
543             #pod =over
544             #pod
545             #pod =item sixtyfourth, triplet_sixtyfourth, dotted_sixtyfourth, double_dotted_sixtyfourth
546             #pod
547             #pod =back
548             #pod
549             #pod =duration one-twenty-eighth notes
550             #pod
551             #pod =over
552             #pod
553             #pod =item onetwentyeighth, triplet_onetwentyeighth, dotted_onetwentyeighth, double_dotted_onetwentyeighth
554             #pod
555             #pod =back
556             #pod
557             #pod =cut
558              
559             my %basic_note_durations = (
560             whole => 'w',
561             half => 'h',
562             quarter => 'q',
563             eighth => 'e',
564             sixteenth => 's',
565             thirtysecond => 'x',
566             sixtyfourth => 'y',
567             onetwentyeighth => 'z',
568             );
569              
570             my %duration_prefixes = (
571             triplet => 't',
572             dotted => 'd',
573             double_dotted => 'dd',
574             );
575              
576             for my $basic_duration ( keys %basic_note_durations ) {
577             my $duration = $basic_note_durations{$basic_duration};
578              
579             has $basic_duration => (
580             is => 'ro',
581             isa => Duration,
582             default => "${duration}n",
583             );
584              
585             for my $prefix ( keys %duration_prefixes ) {
586             has "${prefix}_${basic_duration}" => (
587             is => 'ro',
588             isa => Duration,
589             default => "$duration_prefixes{$prefix}${duration}n",
590             );
591             }
592             }
593              
594             #pod =method new
595             #pod
596             #pod $d = MIDI::Drummer::Tiny->new(%arguments);
597             #pod
598             #pod Return a new C object and add a time signature
599             #pod event to the score.
600             #pod
601             #pod =method note
602             #pod
603             #pod $d->note( $d->quarter, $d->closed_hh, $d->kick );
604             #pod $d->note( 'qn', 42, 35 ); # Same thing
605             #pod
606             #pod Add notes to the score.
607             #pod
608             #pod This method takes the same arguments as L.
609             #pod
610             #pod It also keeps track of the beat count with the C attribute.
611             #pod
612             #pod =cut
613              
614 122     122 1 8553 sub note ( $self, @spec ) {
  122         220  
  122         285  
  122         185  
615 122 50       570 my $size
616             = $spec[0] =~ /^d(\d+)$/
617             ? $1 / ticks( $self->score )
618             : dura_size( $spec[0] );
619              
620             # carp __PACKAGE__,' L',__LINE__,' ',,"$spec[0]\n";
621             # carp __PACKAGE__,' L',__LINE__,' ',,"$size\n";
622 122         1605 $self->counter( $self->counter + $size );
623 122         3677 return $self->score->n(@spec);
624             }
625              
626             #pod =method accent_note
627             #pod
628             #pod $d->accent_note($accent_value, $d->sixteenth, $d->snare);
629             #pod
630             #pod Play an accented note.
631             #pod
632             #pod For instance, this can be a "ghosted note", where the B is a
633             #pod smaller number (< 50). Or a note that is greater than the normal
634             #pod score volume.
635             #pod
636             #pod =cut
637              
638 0     0 1 0 sub accent_note ( $self, $accent, @spec ) {
  0         0  
  0         0  
  0         0  
  0         0  
639 0         0 my $resume = $self->score->Volume;
640 0         0 $self->score->Volume($accent);
641 0         0 $self->note(@spec);
642 0         0 return $self->score->Volume($resume);
643             }
644              
645             #pod =method rest
646             #pod
647             #pod $d->rest( $d->quarter );
648             #pod
649             #pod Add a rest to the score.
650             #pod
651             #pod This method takes the same arguments as L.
652             #pod
653             #pod It also keeps track of the beat count with the C attribute.
654             #pod
655             #pod =cut
656              
657 556     556 1 1021 sub rest ( $self, @spec ) {
  556         982  
  556         2025  
  556         809  
658 556 50       2486 my $size
659             = $spec[0] =~ /^d(\d+)$/
660             ? $1 / ticks( $self->score )
661             : dura_size( $spec[0] );
662              
663             # carp __PACKAGE__,' L',__LINE__,' ',,"$spec[0] => $size\n";
664 556         7253 $self->counter( $self->counter + $size );
665 556         15497 return $self->score->r(@spec);
666             }
667              
668             #pod =method count_in
669             #pod
670             #pod $d->count_in;
671             #pod $d->count_in($bars);
672             #pod $d->count_in({ bars => $bars, patch => $patch });
673             #pod
674             #pod Play a patch for the number of beats times the number of bars.
675             #pod
676             #pod If no bars are given, the object setting is used. If no patch is
677             #pod given, the closed hihat is used.
678             #pod
679             #pod =cut
680              
681 0     0 1 0 sub count_in ( $self, $args_ref ) {
  0         0  
  0         0  
  0         0  
682              
683 0         0 my $bars = $self->bars;
684 0         0 my $patch = $self->pedal_hh;
685 0         0 my $accent = $self->closed_hh;
686              
687 0 0 0     0 if ( $args_ref && ref $args_ref ) {
    0          
688 0 0       0 $bars = $args_ref->{bars} if defined $args_ref->{bars};
689 0 0       0 $patch = $args_ref->{patch} if defined $args_ref->{patch};
690 0 0       0 $accent = $args_ref->{accent} if defined $args_ref->{accent};
691             }
692             elsif ($args_ref) {
693 0         0 $bars = $args_ref; # given a simple integer
694             }
695              
696 0         0 my $j = 1;
697 0         0 for my $i ( 1 .. $self->beats * $bars ) {
698 0 0       0 if ( $i == $self->beats * $j - $self->beats + 1 ) {
699 0         0 $self->accent_note( 127, $self->quarter, $accent );
700 0         0 $j++;
701             }
702             else {
703 0         0 $self->note( $self->quarter, $patch );
704             }
705             }
706 0         0 return;
707             }
708              
709             #pod =method metronome3
710             #pod
711             #pod $d->metronome3;
712             #pod $d->metronome3($bars);
713             #pod $d->metronome3($bars, $cymbal);
714             #pod $d->metronome3($bars, $cymbal, $tempo);
715             #pod $d->metronome3($bars, $cymbal, $tempo, $swing);
716             #pod
717             #pod Add a steady 3/x beat to the score.
718             #pod
719             #pod Defaults for all metronome methods:
720             #pod
721             #pod bars: The object B
722             #pod cymbal: B
723             #pod tempo: B
724             #pod swing: 50 percent = straight-time
725             #pod
726             #pod =cut
727              
728             sub metronome3 (
729 0         0 $self,
730 0         0 $bars = $self->bars,
731 0         0 $cymbal = $self->closed_hh,
732 0         0 $tempo = $self->quarter,
733 0         0 $swing = 50 # percent
734             )
735 0     0 1 0 {
  0         0  
736 0         0 my $x = dura_size($tempo) * ticks( $self->score );
737 0         0 my $y = sprintf '%0.f', ( $swing / 100 ) * $x;
738 0         0 my $z = $x - $y;
739 0         0 for ( 1 .. $bars ) {
740 0         0 $self->note( "d$x", $cymbal, $self->kick );
741 0 0       0 if ( $swing > STRAIGHT ) {
742 0         0 $self->note( "d$y", $cymbal );
743 0         0 $self->note( "d$z", $cymbal );
744             }
745             else {
746 0         0 $self->note( "d$x", $cymbal );
747             }
748 0         0 $self->note( "d$x", $cymbal, $self->snare );
749             }
750 0         0 return;
751             }
752              
753             #pod =method metronome4
754             #pod
755             #pod $d->metronome4;
756             #pod $d->metronome4($bars);
757             #pod $d->metronome4($bars, $cymbal);
758             #pod $d->metronome4($bars, $cymbal, $tempo);
759             #pod $d->metronome4($bars, $cymbal, $tempo, $swing);
760             #pod
761             #pod Add a steady 4/x beat to the score.
762             #pod
763             #pod =cut
764              
765             sub metronome4 (
766 0         0 $self,
767 0         0 $bars = $self->bars,
768 0         0 $cymbal = $self->closed_hh,
769 0         0 $tempo = $self->quarter,
770 0         0 $swing = 50 # percent
771             )
772 0     0 1 0 {
  0         0  
773 0         0 my $x = dura_size($tempo) * ticks( $self->score );
774 0         0 my $y = sprintf '%0.f', ( $swing / 100 ) * $x;
775 0         0 my $z = $x - $y;
776 0         0 for my $n ( 1 .. $bars ) {
777 0         0 $self->note( "d$x", $cymbal, $self->kick );
778 0 0       0 if ( $swing > STRAIGHT ) {
779 0         0 $self->note( "d$y", $cymbal );
780 0         0 $self->note( "d$z", $cymbal );
781             }
782             else {
783 0         0 $self->note( "d$x", $cymbal );
784             }
785 0         0 $self->note( "d$x", $cymbal, $self->snare );
786 0 0       0 if ( $swing > STRAIGHT ) {
787 0         0 $self->note( "d$y", $cymbal );
788 0         0 $self->note( "d$z", $cymbal );
789             }
790             else {
791 0         0 $self->note( "d$x", $cymbal );
792             }
793             }
794 0         0 return;
795             }
796              
797             #pod =method metronome5
798             #pod
799             #pod $d->metronome5;
800             #pod $d->metronome5($bars);
801             #pod $d->metronome5($bars, $cymbal);
802             #pod $d->metronome5($bars, $cymbal, $tempo);
803             #pod $d->metronome5($bars, $cymbal, $tempo, $swing);
804             #pod
805             #pod Add a 5/x beat to the score.
806             #pod
807             #pod =cut
808              
809             sub metronome5 (
810 0         0 $self,
811 0         0 $bars = $self->bars,
812 0         0 $cymbal = $self->closed_hh,
813 0         0 $tempo = $self->quarter,
814 0         0 $swing = 50 # percent
815             )
816 0     0 1 0 {
  0         0  
817 0         0 my $x = dura_size($tempo) * ticks( $self->score );
818 0         0 my $half = $x / 2;
819 0         0 my $y = sprintf '%0.f', ( $swing / 100 ) * $x;
820 0         0 my $z = $x - $y;
821              
822 0         0 for my $n ( 1 .. $bars ) {
823 0         0 $self->note( "d$x", $cymbal, $self->kick );
824 0 0       0 if ( $swing > STRAIGHT ) {
825 0         0 $self->note( "d$y", $cymbal );
826 0         0 $self->note( "d$z", $cymbal );
827             }
828             else {
829 0         0 $self->note( "d$x", $cymbal );
830             }
831 0         0 $self->note( "d$x", $cymbal, $self->snare );
832 0 0       0 if ( $swing > STRAIGHT ) {
833 0         0 $self->note( "d$y", $cymbal );
834 0         0 $self->note( "d$z", $cymbal );
835             }
836             else {
837 0         0 $self->note( "d$x", $cymbal );
838             }
839 0 0       0 if ( $n % 2 ) {
840 0         0 $self->note( "d$x", $cymbal );
841             }
842             else {
843 0         0 $self->note( "d$half", $cymbal );
844 0         0 $self->note( "d$half", $self->kick );
845             }
846             }
847 0         0 return;
848             }
849              
850             #pod =method metronome6
851             #pod
852             #pod $d->metronome6;
853             #pod $d->metronome6($bars);
854             #pod $d->metronome6($bars, $cymbal);
855             #pod $d->metronome6($bars, $cymbal, $tempo);
856             #pod $d->metronome6($bars, $cymbal, $tempo, $swing);
857             #pod
858             #pod Add a 6/x beat to the score.
859             #pod
860             #pod =cut
861              
862             sub metronome6 (
863 0         0 $self,
864 0         0 $bars = $self->bars,
865 0         0 $cymbal = $self->closed_hh,
866 0         0 $tempo = $self->quarter,
867 0         0 $swing = 50 # percent
868             )
869 0     0 1 0 {
  0         0  
870 0         0 my $x = dura_size($tempo) * ticks( $self->score );
871 0         0 my $y = sprintf '%0.f', ( $swing / 100 ) * $x;
872 0         0 my $z = $x - $y;
873              
874 0         0 for my $n ( 1 .. $bars ) {
875 0         0 $self->note( "d$x", $cymbal, $self->kick );
876 0 0       0 if ( $swing > STRAIGHT ) {
877 0         0 $self->note( "d$y", $cymbal );
878 0         0 $self->note( "d$z", $cymbal );
879             }
880             else {
881 0         0 $self->note( "d$x", $cymbal );
882             }
883 0         0 $self->note( "d$x", $cymbal );
884 0         0 $self->note( "d$x", $cymbal, $self->snare );
885 0 0       0 if ( $swing > STRAIGHT ) {
886 0         0 $self->note( "d$y", $cymbal );
887 0         0 $self->note( "d$z", $cymbal );
888             }
889             else {
890 0         0 $self->note( "d$x", $cymbal );
891             }
892 0         0 $self->note( "d$x", $cymbal );
893             }
894 0         0 return;
895             }
896              
897             #pod =method metronome7
898             #pod
899             #pod $d->metronome7;
900             #pod $d->metronome7($bars);
901             #pod $d->metronome7($bars, $cymbal);
902             #pod $d->metronome7($bars, $cymbal, $tempo);
903             #pod $d->metronome7($bars, $cymbal, $tempo, $swing);
904             #pod
905             #pod Add a 7/x beat to the score.
906             #pod
907             #pod =cut
908              
909             sub metronome7 (
910 0         0 $self,
911 0         0 $bars = $self->bars,
912 0         0 $cymbal = $self->closed_hh,
913 0         0 $tempo = $self->quarter,
914 0         0 $swing = 50 # percent
915             )
916 0     0 1 0 {
  0         0  
917 0         0 my $x = dura_size($tempo) * ticks( $self->score );
918 0         0 my $y = sprintf '%0.f', ( $swing / 100 ) * $x;
919 0         0 my $z = $x - $y;
920              
921 0         0 for my $n ( 1 .. $bars ) {
922 0         0 $self->note( "d$x", $cymbal, $self->kick );
923 0 0       0 if ( $swing > STRAIGHT ) {
924 0         0 $self->note( "d$y", $cymbal );
925 0         0 $self->note( "d$z", $cymbal );
926             }
927             else {
928 0         0 $self->note( "d$x", $cymbal );
929             }
930 0         0 $self->note( "d$x", $cymbal );
931 0 0       0 if ( $swing > STRAIGHT ) {
932 0         0 $self->note( "d$y", $cymbal, $self->kick );
933 0         0 $self->note( "d$z", $cymbal );
934             }
935             else {
936 0         0 $self->note( "d$x", $cymbal, $self->kick );
937             }
938 0         0 $self->note( "d$x", $cymbal, $self->snare );
939 0 0       0 if ( $swing > STRAIGHT ) {
940 0         0 $self->note( "d$y", $cymbal );
941 0         0 $self->note( "d$z", $cymbal );
942             }
943             else {
944 0         0 $self->note( "d$x", $cymbal );
945             }
946 0         0 $self->note( "d$x", $cymbal );
947             }
948 0         0 return;
949             }
950              
951             #pod =method metronome44
952             #pod
953             #pod $d->metronome44;
954             #pod $d->metronome44($bars);
955             #pod $d->metronome44($bars, $flag);
956             #pod $d->metronome44($bars, $flag, $cymbal);
957             #pod
958             #pod Add a steady quarter-note based 4/4 beat to the score.
959             #pod
960             #pod If a B is provided the beat is modified to include alternating
961             #pod eighth-note kicks.
962             #pod
963             #pod =cut
964              
965             sub metronome44 (
966 0         0 $self, $bars = $self->bars,
  0         0  
967 0         0 $flag = 0, $cymbal = $self->closed_hh,
  0         0  
968 0     0 1 0 )
  0         0  
969             {
970 0         0 my $i = 0;
971 0         0 for my $n ( 1 .. $self->beats * $bars ) {
972 0 0       0 if ( $n % 2 == 0 ) {
973 0         0 $self->note( $self->quarter, $cymbal, $self->snare );
974             }
975             else {
976 0 0       0 if ( $flag == 0 ) {
977 0         0 $self->note( $self->quarter, $cymbal, $self->kick );
978             }
979             else {
980 0 0       0 if ( $i % 2 == 0 ) {
981 0         0 $self->note( $self->quarter, $cymbal, $self->kick );
982             }
983             else {
984 0         0 $self->note( $self->eighth, $cymbal, $self->kick );
985 0         0 $self->note( $self->eighth, $self->kick );
986             }
987             }
988 0         0 $i++;
989             }
990             }
991 0         0 return;
992             }
993              
994             #pod =method flam
995             #pod
996             #pod $d->flam($spec);
997             #pod $d->flam( $spec, $grace_note );
998             #pod $d->flam( $spec, $grace_note, $patch );
999             #pod $d->flam( $spec, $grace_note, $patch, $accent );
1000             #pod
1001             #pod Add a "flam" to the score, where a ghosted 64th gracenote is played
1002             #pod before the primary note.
1003             #pod
1004             #pod If not provided the B is used for the B and B
1005             #pod patches. Also, 1/2 of the score volume is used for the B
1006             #pod if that is not given.
1007             #pod
1008             #pod If the B note is given as a literal C<'r'>, rest instead of
1009             #pod adding a note to the score.
1010             #pod
1011             #pod =cut
1012              
1013             sub flam (
1014 0         0 $self, $spec,
  0         0  
1015 0         0 $grace = $self->snare,
1016 0         0 $patch = $self->snare,
1017 0         0 $accent = sprintf '%0.f',
1018             $self->score->Volume / 2
1019             )
1020 0     0 1 0 {
  0         0  
1021             my ( $x, $y )
1022 0         0 = @MIDI::Simple::Length{ $spec, $self->sixtyfourth }; ## no critic (Variables::ProhibitPackageVars)
1023 0         0 my $z = sprintf '%0.f', ( $x - $y ) * ticks( $self->score );
1024 0 0       0 if ( $grace eq 'r' ) {
1025 0         0 $self->rest( $self->sixtyfourth );
1026             }
1027             else {
1028 0         0 $self->accent_note( $accent, $self->sixtyfourth, $grace );
1029             }
1030 0         0 return $self->note( 'd' . $z, $patch );
1031             }
1032              
1033             #pod =method roll
1034             #pod
1035             #pod $d->roll( $length, $spec );
1036             #pod $d->roll( $length, $spec, $patch );
1037             #pod
1038             #pod Add a drum roll to the score, where the B is played for
1039             #pod duration B in B increments.
1040             #pod
1041             #pod If not provided the B is used for the B.
1042             #pod
1043             #pod =cut
1044              
1045 0     0 1 0 sub roll ( $self, $length, $spec, $patch = $self->snare ) {
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1046 0         0 my ( $x, $y ) = @MIDI::Simple::Length{ $length, $spec }; ## no critic (Variables::ProhibitPackageVars)
1047 0         0 my $z = sprintf '%0.f', $x / $y;
1048 0         0 $self->note( $spec, $patch ) for 1 .. $z;
1049 0         0 return;
1050             }
1051              
1052             #pod =method crescendo_roll
1053             #pod
1054             #pod $d->crescendo_roll( [$start, $end, $bezier], $length, $spec );
1055             #pod $d->crescendo_roll( [$start, $end, $bezier], $length, $spec, $patch );
1056             #pod
1057             #pod Add a drum roll to the score, where the B is played for
1058             #pod duration B in B notes, at increasing or decreasing
1059             #pod volumes from B to B.
1060             #pod
1061             #pod If not provided the B is used for the B.
1062             #pod
1063             #pod If true, the B flag will render the crescendo with a curve,
1064             #pod rather than as a straight line.
1065             #pod
1066             #pod | *
1067             #pod | *
1068             #pod vol | *
1069             #pod | *
1070             #pod |*
1071             #pod ---------------
1072             #pod time
1073             #pod
1074             #pod =cut
1075              
1076 0         0 sub crescendo_roll ( $self, $span_ref, $length, $spec,
  0         0  
  0         0  
  0         0  
1077 0         0 $patch = $self->snare )
1078 0     0 1 0 {
  0         0  
1079 0         0 my ( $i, $j, $k ) = $span_ref->@*;
1080 0         0 my ( $x, $y ) = @MIDI::Simple::Length{ $length, $spec }; ## no critic (Variables::ProhibitPackageVars)
1081 0         0 my $z = sprintf '%0.f', $x / $y;
1082 0 0       0 if ($k) {
1083 0         0 my $bezier = Math::Bezier->new( 1, $i, $z, $i, $z, $j, );
1084 0         0 for ( my $n = 0 ; $n <= 1 ; $n += ( 1 / ( $z - 1 ) ) ) {
1085 0         0 my ( undef, $v ) = $bezier->point($n);
1086 0         0 $v = sprintf '%0.f', $v;
1087              
1088             # carp (__PACKAGE__,' ',__LINE__," $n INC: $v\n");
1089 0         0 $self->accent_note( $v, $spec, $patch );
1090             }
1091             }
1092             else {
1093 0         0 my $v = sprintf '%0.f', ( $j - $i ) / ( $z - 1 );
1094              
1095             # carp (__PACKAGE__,' ',__LINE__," VALUE: $v\n");
1096 0         0 for my $n ( 1 .. $z ) {
1097 0 0       0 if ( $n == $z ) {
1098 0 0       0 if ( $i < $j ) {
    0          
1099 0         0 $i += $j - $i;
1100             }
1101             elsif ( $i > $j ) {
1102 0         0 $i -= $i - $j;
1103             }
1104             }
1105              
1106             # carp (__PACKAGE__,' ',__LINE__," $n INC: $i\n");
1107 0         0 $self->accent_note( $i, $spec, $patch );
1108 0         0 $i += $v;
1109             }
1110             }
1111 0         0 return;
1112             }
1113              
1114             #pod =method pattern
1115             #pod
1116             #pod $d->pattern( patterns => \@patterns );
1117             #pod $d->pattern( patterns => \@patterns, instrument => $d->kick );
1118             #pod $d->pattern( patterns => \@patterns, instrument => $d->kick, %options );
1119             #pod
1120             #pod Play a given set of beat B with the given B.
1121             #pod
1122             #pod The B are an arrayref of "beat-strings". By default these
1123             #pod are made of contiguous ones and zeros, meaning "strike" or "rest".
1124             #pod For example:
1125             #pod
1126             #pod patterns => [qw( 0101 0101 0110 0110 )],
1127             #pod
1128             #pod This method accumulates the number of beats in the object's B
1129             #pod attribute.
1130             #pod
1131             #pod The B option is a hashref of coderefs, keyed by single character
1132             #pod tokens, like the digits 0-9. Each coderef duration should add up to
1133             #pod the given B option. The single argument that is given to a
1134             #pod coderef is the object itself. It is used like: C.
1135             #pod
1136             #pod These patterns can be generated with any custom function, as in the
1137             #pod L. For instance, you could use the L
1138             #pod module to generate Euclidean patterns.
1139             #pod
1140             #pod Defaults:
1141             #pod
1142             #pod instrument: snare
1143             #pod patterns: [] (i.e. empty!)
1144             #pod Options:
1145             #pod duration: quarter-note
1146             #pod beats: given by constructor
1147             #pod repeat: 1
1148             #pod negate: 0 (flip the bit values)
1149             #pod vary:
1150             #pod 0 => sub { $self->rest( $args{duration} ) },
1151             #pod 1 => sub { $self->note( $args{duration}, $args{instrument} ) },
1152             #pod
1153             #pod =cut
1154              
1155 34     34 1 822 sub pattern ( $self, %args ) {
  34         83  
  34         146  
  34         59  
1156 34   33     127 $args{instrument} ||= $self->snare;
1157 34   50     152 $args{patterns} ||= [];
1158 34   33     308 $args{beats} ||= $self->beats;
1159 34   50     201 $args{negate} ||= 0;
1160 34   50     202 $args{repeat} ||= 1;
1161              
1162 34 50       110 return unless $args{patterns}->@*;
1163              
1164             # set size and duration
1165 34         63 my $size;
1166 34 100       88 if ( $args{duration} ) {
1167 33   50     140 $size = dura_size( $args{duration} ) || 1;
1168             }
1169             else {
1170 1         4 $size = 4 / length( $args{patterns}->[0] );
1171 1         6 my $dump = reverse_dump('length');
1172 1   33     503 $args{duration} = $dump->{$size} || $self->quarter;
1173             }
1174              
1175             # set the default beat-string variations
1176             $args{vary} ||= {
1177 556     556   1512 0 => sub { $self->rest( $args{duration} ) },
1178 121     121   430 1 => sub { $self->note( $args{duration}, $args{instrument} ) },
1179 34   50     1010 };
1180              
1181 34         154 for my $pattern ( $args{patterns}->@* ) {
1182 34 50       159 $pattern =~ tr/01/10/ if $args{negate};
1183              
1184 34 50       134 next if $pattern =~ /^0+$/;
1185              
1186 34         122 for ( 1 .. $args{repeat} ) {
1187 34         236 for my $bit ( split //, $pattern ) {
1188 677         37703 $args{vary}{$bit}->( $self, %args );
1189             }
1190             }
1191             }
1192 34         2125 return;
1193             }
1194              
1195             #pod =method sync_patterns
1196             #pod
1197             #pod $d->sync_patterns( $instrument1 => $patterns1, $inst2 => $pats2, ... );
1198             #pod $d->sync_patterns(
1199             #pod $d->open_hh => [ '11111111') ],
1200             #pod $d->snare => [ '0101' ],
1201             #pod $d->kick => [ '1010' ],
1202             #pod duration => $d->eighth, # render all notes at this level of granularity
1203             #pod ) for 1 .. $d->bars;
1204             #pod
1205             #pod Execute the C method for multiple voices.
1206             #pod
1207             #pod If a C is provided, this will be used for each pattern
1208             #pod (primarily for the B method).
1209             #pod
1210             #pod =cut
1211              
1212 12     12 1 23 sub sync_patterns ( $self, %patterns ) {
  12         22  
  12         48  
  12         23  
1213 12         34 my $master_duration = delete $patterns{duration};
1214              
1215 12         23 my @subs;
1216 12         38 for my $instrument ( keys %patterns ) {
1217             push @subs, sub {
1218             $self->pattern(
1219             instrument => $instrument,
1220 33 50   33   1311 patterns => $patterns{$instrument},
1221             $master_duration
1222             ? ( duration => $master_duration )
1223             : (),
1224             );
1225 33         147 };
1226             }
1227              
1228 12         485 return $self->sync(@subs);
1229             }
1230              
1231             #pod =method add_fill
1232             #pod
1233             #pod $d->add_fill( $fill, $instrument1 => $patterns1, $inst2 => $pats2, ... );
1234             #pod $d->add_fill(
1235             #pod sub {
1236             #pod my $self = shift;
1237             #pod return {
1238             #pod duration => 16, # sixteenth note fill
1239             #pod $self->open_hh => '00000000',
1240             #pod $self->snare => '11111111',
1241             #pod $self->kick => '00000000',
1242             #pod };
1243             #pod },
1244             #pod $d->open_hh => [ '11111111' ], # example phrase
1245             #pod $d->snare => [ '0101' ], # "
1246             #pod $d->kick => [ '1010' ], # "
1247             #pod );
1248             #pod
1249             #pod Add a fill to the beat pattern. That is, replace the end of the given
1250             #pod beat-string phrase with a fill. The fill is given as the first
1251             #pod argument and should be a coderef that returns a hashref. The default
1252             #pod is a three-note, eighth-note snare fill.
1253             #pod
1254             #pod =cut
1255              
1256 12     12 1 21125 sub add_fill ( $self, $fill = undef, %patterns ) {
  12         34  
  12         30  
  12         72  
  12         20  
1257             $fill //= sub { {
1258 11     11   114 duration => 8,
1259             $self->open_hh => '000',
1260             $self->snare => '111',
1261             $self->kick => '000',
1262 12   66     137 } };
1263 12         39 my $fill_patterns = $fill->($self);
1264 12 100       84 carp 'Fill: ', ddc($fill_patterns) if $self->verbose;
1265 12   50     921 my $fill_duration = delete $fill_patterns->{duration} || 8;
1266 12         47 my $fill_length = length( ( values %$fill_patterns )[0] );
1267              
1268 12         23 my %lengths;
1269 12         45 for my $instrument ( keys %patterns ) {
1270             $lengths{$instrument}
1271 33         79 = sum0 map { length $_ } $patterns{$instrument}->@*;
  33         154  
1272             }
1273              
1274 12         59 my $lcm = _multilcm( $fill_duration, values %lengths );
1275 12 100       179 carp "LCM: $lcm\n" if $self->verbose;
1276              
1277 12         35 my $size = 4 / $lcm;
1278 12         69 my $dump = reverse_dump('length');
1279             my $master_duration
1280 12   66     5618 = $dump->{$size} || $self->eighth; # XXX this || is not right
1281 12 100       216 carp "Size: $size, Duration: $master_duration\n" if $self->verbose;
1282              
1283 12 100       61 my $fill_chop
1284             = $fill_duration == $lcm
1285             ? $fill_length
1286             : int( $lcm / $fill_length ) + 1;
1287 12 100       127 carp "Chop: $fill_chop\n" if $self->verbose;
1288              
1289 12         25 my %fresh_patterns;
1290 12         48 for my $instrument ( keys %patterns ) {
1291              
1292             # get a single "flattened" pattern as an arrayref
1293             my $pattern
1294 33         935 = [ map { split //, $_ } $patterns{$instrument}->@* ];
  33         178  
1295              
1296             # the fresh pattern is possibly upsized with the LCM
1297 33 100       187 $fresh_patterns{$instrument}
1298             = $pattern->@* < $lcm
1299             ? [ join '', upsize( $pattern, $lcm )->@* ]
1300             : [ join '', $pattern->@* ];
1301             }
1302 12 100       413 carp 'Patterns: ', ddc( \%fresh_patterns ) if $self->verbose;
1303              
1304 12         185 my %replacement;
1305 12         44 for my $instrument ( keys $fill_patterns->%* ) {
1306              
1307             # get a single "flattened" pattern as a zero-pre-padded arrayref
1308             my $pattern = [
1309             split //, sprintf '%0*s',
1310 36         276 $fill_duration, $fill_patterns->{$instrument} ];
1311              
1312             # the fresh pattern string is possibly upsized with the LCM
1313 36 100       176 my $fresh
1314             = $pattern->@* < $lcm
1315             ? join '', upsize( $pattern, $lcm )->@*
1316             : join '', $pattern->@*;
1317              
1318             # the replacement string is the tail of the fresh pattern string
1319 36         804 $replacement{$instrument} = substr $fresh, -$fill_chop;
1320             }
1321 12 100       53 carp 'Replacements: ', ddc( \%replacement ) if $self->verbose;
1322              
1323 12         513 my %replaced;
1324 12         39 for my $instrument ( keys %fresh_patterns ) {
1325              
1326             # get the string to replace
1327 33         92 my $string = join '', $fresh_patterns{$instrument}->@*;
1328              
1329             # replace the tail of the string
1330 33         66 my $pos = length $replacement{$instrument};
1331 33         95 substr $string, -$pos, $pos, $replacement{$instrument};
1332 33 50       109 carp "$instrument: $string\n" if $self->verbose;
1333              
1334             # prepare the replaced pattern for syncing
1335 33         125 $replaced{$instrument} = [$string];
1336             }
1337              
1338 12         71 $self->sync_patterns( %replaced, duration => $master_duration, );
1339              
1340 12         651 return \%replaced;
1341             }
1342              
1343             #pod =method set_time_sig
1344             #pod
1345             #pod $d->set_time_sig;
1346             #pod $d->set_time_sig('5/4');
1347             #pod $d->set_time_sig( '5/4', 0 );
1348             #pod
1349             #pod Add a time signature event to the score, and reset the B and
1350             #pod B object attributes.
1351             #pod
1352             #pod If a ratio argument is given, set the B object attribute to
1353             #pod it. If the 2nd argument flag is C<0>, the B and B
1354             #pod are B reset.
1355             #pod
1356             #pod =cut
1357              
1358 7     7 1 1091 sub set_time_sig ( $self, $time_signature, $set = 1 ) {
  7         17  
  7         14  
  7         19  
  7         16  
1359 7 50       46 $self->signature($time_signature) if $time_signature;
1360 7 100       25 if ($set) {
1361 6         36 my ( $beats, $divisions ) = split /\//, $self->signature;
1362 6         27 $self->beats($beats);
1363 6         22 $self->divisions($divisions);
1364             }
1365 7         186 return set_time_signature( $self->score, $self->signature );
1366             }
1367              
1368             #pod =method write
1369             #pod
1370             #pod Output the score as a MIDI file with the module L attribute as
1371             #pod the file name.
1372             #pod
1373             #pod =cut
1374              
1375 0     0 1 0 sub write ($self) { ## no critic (Subroutines::ProhibitBuiltinHomonyms)
  0         0  
  0         0  
1376 0         0 return $self->score->write_score( $self->file );
1377             }
1378              
1379             # lifted from https://www.perlmonks.org/?node_id=56906
1380 33     33   51 sub _gcf ( $x, $y ) {
  33         76  
  33         60  
  33         50  
1381 33         162 ( $x, $y ) = ( $y, $x % $y ) while $y;
1382 33         129 return $x;
1383             }
1384              
1385 33     33   60 sub _lcm ( $x, $y ) { return $x * $y / _gcf( $x, $y ) }
  33         54  
  33         55  
  33         48  
  33         119  
1386              
1387             sub _multilcm { ## no critic (Subroutines::RequireArgUnpacking)
1388 12     12   28 my $x = shift;
1389 12         65 $x = _lcm( $x, shift ) while @_;
1390 12         30 return $x;
1391             }
1392              
1393             1;
1394              
1395             __END__