File Coverage

blib/lib/MIDI/Drummer/Tiny.pm
Criterion Covered Total %
statement 151 306 49.3
branch 25 74 33.7
condition 19 81 23.4
subroutine 29 45 64.4
pod 27 28 96.4
total 251 534 47.0


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.4303';
7              
8 1     1   1411 use Moo;
  1         11757  
  1         4  
9 1     1   1953 use strictures 2;
  1         1697  
  1         40  
10 1     1   213 use Data::Dumper::Compact qw(ddc);
  1         2  
  1         18  
11 1     1   106 use List::Util qw(sum0);
  1         2  
  1         82  
12 1     1   486 use Math::Bezier ();
  1         939  
  1         30  
13 1     1   496 use MIDI::Util qw(dura_size reverse_dump set_time_signature timidity_conf play_timidity);
  1         39773  
  1         69  
14 1     1   464 use Music::Duration ();
  1         410  
  1         29  
15 1     1   537 use Music::RhythmSet::Util qw(upsize);
  1         4421  
  1         63  
16 1     1   437 use namespace::clean;
  1         11131  
  1         9  
17              
18 1     1   355 use constant TICKS => 96; # Per quarter note
  1         2  
  1         5889  
19              
20              
21             sub BUILD {
22 5     5 0 29 my ( $self, $args ) = @_;
23              
24 5         1068 $self->score->noop( 'c' . $self->channel, 'V' . $self->volume );
25              
26             # if ($self->kit) {
27             # $self->score->control_change($self->channel, 0, 120);
28             # $self->score->patch_change($self->channel, $self->kit)
29             # }
30              
31 5         248 $self->score->set_tempo( int( 60_000_000 / $self->bpm ) );
32              
33 5         121 $self->score->control_change($self->channel, 91, $self->reverb);
34              
35             # Add a TS to the score but don't reset the beats if given
36 5         114 $self->set_time_sig( $self->signature, !$args->{beats} );
37             }
38              
39              
40             has soundfont => ( is => 'rw');
41             has verbose => ( is => 'ro', default => sub { 0 } );
42             has reverb => ( is => 'ro', default => sub { 15 } );
43             has channel => ( is => 'rw', default => sub { 9 } );
44             has volume => ( is => 'rw', default => sub { 100 } );
45             has bpm => ( is => 'rw', default => sub { 120 } );
46             has file => ( is => 'ro', default => sub { 'MIDI-Drummer.mid' } );
47             has bars => ( is => 'ro', default => sub { 4 } );
48             has score => ( is => 'ro', default => sub { MIDI::Simple->new_score } );
49             has signature => ( is => 'rw', default => sub { '4/4' });
50             has beats => ( is => 'rw', default => sub { 4 } );
51             has divisions => ( is => 'rw', default => sub { 4 } );
52             has counter => ( is => 'rw', default => sub { 0 } );
53              
54              
55             has click => ( is => 'ro', default => sub { 33 } );
56             has bell => ( is => 'ro', default => sub { 34 } );
57             has kick => ( is => 'ro', default => sub { 35 } ); # Alt: 36
58             has acoustic_bass => ( is => 'ro', default => sub { 35 } );
59             has electric_bass => ( is => 'ro', default => sub { 36 } );
60             has side_stick => ( is => 'ro', default => sub { 37 } );
61             has snare => ( is => 'ro', default => sub { 38 } ); # Alt: 40
62             has acoustic_snare => ( is => 'ro', default => sub { 38 } );
63             has electric_snare => ( is => 'ro', default => sub { 40 } );
64             has clap => ( is => 'ro', default => sub { 39 } );
65             has open_hh => ( is => 'ro', default => sub { 46 } );
66             has closed_hh => ( is => 'ro', default => sub { 42 } );
67             has pedal_hh => ( is => 'ro', default => sub { 44 } );
68             has crash1 => ( is => 'ro', default => sub { 49 } );
69             has crash2 => ( is => 'ro', default => sub { 57 } );
70             has splash => ( is => 'ro', default => sub { 55 } );
71             has china => ( is => 'ro', default => sub { 52 } );
72             has ride1 => ( is => 'ro', default => sub { 51 } );
73             has ride2 => ( is => 'ro', default => sub { 59 } );
74             has ride_bell => ( is => 'ro', default => sub { 53 } );
75             has hi_tom => ( is => 'ro', default => sub { 50 } );
76             has hi_mid_tom => ( is => 'ro', default => sub { 48 } );
77             has low_mid_tom => ( is => 'ro', default => sub { 47 } );
78             has low_tom => ( is => 'ro', default => sub { 45 } );
79             has hi_floor_tom => ( is => 'ro', default => sub { 43 } );
80             has low_floor_tom => ( is => 'ro', default => sub { 41 } );
81             has tambourine => ( is => 'ro', default => sub { 54 } );
82             has cowbell => ( is => 'ro', default => sub { 56 } );
83             has vibraslap => ( is => 'ro', default => sub { 58 } );
84             has hi_bongo => ( is => 'ro', default => sub { 60 } );
85             has low_bongo => ( is => 'ro', default => sub { 61 } );
86             has mute_hi_conga => ( is => 'ro', default => sub { 62 } );
87             has open_hi_conga => ( is => 'ro', default => sub { 63 } );
88             has low_conga => ( is => 'ro', default => sub { 64 } );
89             has high_timbale => ( is => 'ro', default => sub { 65 } );
90             has low_timbale => ( is => 'ro', default => sub { 66 } );
91             has high_agogo => ( is => 'ro', default => sub { 67 } );
92             has low_agogo => ( is => 'ro', default => sub { 68 } );
93             has cabasa => ( is => 'ro', default => sub { 69 } );
94             has maracas => ( is => 'ro', default => sub { 70 } );
95             has short_whistle => ( is => 'ro', default => sub { 71 } );
96             has long_whistle => ( is => 'ro', default => sub { 72 } );
97             has short_guiro => ( is => 'ro', default => sub { 73 } );
98             has long_guiro => ( is => 'ro', default => sub { 74 } );
99             has claves => ( is => 'ro', default => sub { 75 } );
100             has hi_wood_block => ( is => 'ro', default => sub { 76 } );
101             has low_wood_block => ( is => 'ro', default => sub { 77 } );
102             has mute_cuica => ( is => 'ro', default => sub { 78 } );
103             has open_cuica => ( is => 'ro', default => sub { 79 } );
104             has mute_triangle => ( is => 'ro', default => sub { 80 } );
105             has open_triangle => ( is => 'ro', default => sub { 81 } );
106              
107              
108             has whole => (is => 'ro', default => sub { 'wn' });
109             has triplet_whole => (is => 'ro', default => sub { 'twn' });
110             has dotted_whole => (is => 'ro', default => sub { 'dwn' });
111             has double_dotted_whole => (is => 'ro', default => sub { 'ddwn' });
112             has half => (is => 'ro', default => sub { 'hn' });
113             has triplet_half => (is => 'ro', default => sub { 'thn' });
114             has dotted_half => (is => 'ro', default => sub { 'dhn' });
115             has double_dotted_half => (is => 'ro', default => sub { 'ddhn' });
116             has quarter => (is => 'ro', default => sub { 'qn' });
117             has triplet_quarter => (is => 'ro', default => sub { 'tqn' });
118             has dotted_quarter => (is => 'ro', default => sub { 'dqn' });
119             has double_dotted_quarter => (is => 'ro', default => sub { 'ddqn' });
120             has eighth => (is => 'ro', default => sub { 'en' });
121             has triplet_eighth => (is => 'ro', default => sub { 'ten' });
122             has dotted_eighth => (is => 'ro', default => sub { 'den' });
123             has double_dotted_eighth => (is => 'ro', default => sub { 'dden' });
124             has sixteenth => (is => 'ro', default => sub { 'sn' });
125             has triplet_sixteenth => (is => 'ro', default => sub { 'tsn' });
126             has dotted_sixteenth => (is => 'ro', default => sub { 'dsn' });
127             has double_dotted_sixteenth => (is => 'ro', default => sub { 'ddsn' });
128             has thirtysecond => (is => 'ro', default => sub { 'xn' });
129             has triplet_thirtysecond => (is => 'ro', default => sub { 'txn' });
130             has dotted_thirtysecond => (is => 'ro', default => sub { 'dxn' });
131             has double_dotted_thirtysecond => (is => 'ro', default => sub { 'ddxn' });
132             has sixtyfourth => (is => 'ro', default => sub { 'yn' });
133             has triplet_sixtyfourth => (is => 'ro', default => sub { 'tyn' });
134             has dotted_sixtyfourth => (is => 'ro', default => sub { 'dyn' });
135             has double_dotted_sixtyfourth => (is => 'ro', default => sub { 'ddyn' });
136             has onetwentyeighth => (is => 'ro', default => sub { 'zn' });
137             has triplet_onetwentyeighth => (is => 'ro', default => sub { 'tzn' });
138             has dotted_onetwentyeighth => (is => 'ro', default => sub { 'dzn' });
139             has double_dotted_onetwentyeighth => (is => 'ro', default => sub { 'ddzn' });
140              
141              
142             sub note {
143 122     122 1 5869 my ($self, @spec) = @_;
144 122 50       352 my $size = $spec[0] =~ /^d(\d+)$/ ? $1 / TICKS : dura_size($spec[0]);
145             #warn __PACKAGE__,' L',__LINE__,' ',,"$spec[0] => $size\n";
146 122         1167 $self->counter( $self->counter + $size );
147 122         310 return $self->score->n(@spec);
148             }
149              
150              
151             sub accent_note {
152 0     0 1 0 my $self = shift;
153 0         0 my $accent = shift;
154 0         0 my $resume = $self->score->Volume;
155 0         0 $self->score->Volume($accent);
156 0         0 $self->note(@_);
157 0         0 $self->score->Volume($resume);
158             }
159              
160              
161             sub rest {
162 556     556 1 1029 my ($self, @spec) = @_;
163 556 50       1466 my $size = $spec[0] =~ /^d(\d+)$/ ? $1 / TICKS : dura_size($spec[0]);
164             #warn __PACKAGE__,' L',__LINE__,' ',,"$spec[0] => $size\n";
165 556         5288 $self->counter( $self->counter + $size );
166 556         1279 return $self->score->r(@spec);
167             }
168              
169              
170             sub count_in {
171 0     0 1 0 my ($self, $args) = @_;
172              
173 0         0 my $bars = $self->bars;
174 0         0 my $patch = $self->pedal_hh;
175 0         0 my $accent = $self->closed_hh;
176              
177 0 0 0     0 if ($args && ref $args) {
    0          
178 0 0       0 $bars = $args->{bars} if defined $args->{bars};
179 0 0       0 $patch = $args->{patch} if defined $args->{patch};
180 0 0       0 $accent = $args->{accent} if defined $args->{accent};
181             }
182             elsif ($args) {
183 0         0 $bars = $args; # given a simple integer
184             }
185              
186 0         0 my $j = 1;
187 0         0 for my $i ( 1 .. $self->beats * $bars ) {
188 0 0       0 if ($i == $self->beats * $j - $self->beats + 1) {
189 0         0 $self->accent_note( 127, $self->quarter, $accent );
190 0         0 $j++;
191             }
192             else {
193 0         0 $self->note( $self->quarter, $patch );
194             }
195             }
196             }
197              
198              
199             sub metronome38 {
200 0     0 1 0 my $self = shift;
201 0   0     0 my $bars = shift || $self->bars;
202              
203 0         0 for ( 1 .. $bars ) {
204 0         0 $self->note( $self->eighth, $self->closed_hh, $self->kick );
205 0         0 $self->note( $self->eighth, $self->closed_hh);
206 0         0 $self->note( $self->eighth, $self->closed_hh, $self->snare );
207             }
208             }
209              
210              
211             sub metronome34 {
212 0     0 1 0 my $self = shift;
213 0   0     0 my $bars = shift || $self->bars;
214              
215 0         0 for ( 1 .. $bars ) {
216 0         0 $self->note( $self->quarter, $self->ride1, $self->kick );
217 0         0 $self->note( $self->quarter, $self->ride1 );
218 0         0 $self->note( $self->quarter, $self->ride1, $self->snare );
219             }
220             }
221              
222              
223             sub metronome44 {
224 0     0 1 0 my $self = shift;
225 0   0     0 my $bars = shift || $self->bars;
226 0   0     0 my $flag = shift // 0;
227              
228 0         0 my $i = 0;
229              
230 0         0 for my $n ( 1 .. $self->beats * $bars ) {
231 0 0       0 if ( $n % 2 == 0 )
232             {
233 0         0 $self->note( $self->quarter, $self->closed_hh, $self->snare );
234             }
235             else {
236 0 0       0 if ( $flag == 0 )
237             {
238 0         0 $self->note( $self->quarter, $self->closed_hh, $self->kick );
239             }
240             else
241             {
242 0 0       0 if ( $i % 2 == 0 )
243             {
244 0         0 $self->note( $self->quarter, $self->closed_hh, $self->kick );
245             }
246             else
247             {
248 0         0 $self->note( $self->eighth, $self->closed_hh, $self->kick );
249 0         0 $self->note( $self->eighth, $self->kick );
250             }
251             }
252              
253 0         0 $i++;
254             }
255             }
256             }
257              
258              
259             sub metronome44swing {
260 0     0 1 0 my $self = shift;
261 0   0     0 my $bars = shift || $self->bars;
262              
263 0         0 for my $n ( 1 .. $bars ) {
264 0         0 $self->note( $self->quarter, $self->ride1, $self->kick );
265 0         0 $self->note( $self->triplet_eighth, $self->ride1 );
266 0         0 $self->rest( $self->triplet_eighth );
267 0         0 $self->note( $self->triplet_eighth, $self->ride1, $self->kick );
268 0         0 $self->note( $self->quarter, $self->ride1, $self->snare );
269 0         0 $self->note( $self->triplet_eighth, $self->ride1, $self->kick );
270 0         0 $self->rest( $self->triplet_eighth );
271 0         0 $self->note( $self->triplet_eighth, $self->ride1 );
272             }
273             }
274              
275              
276             sub metronome54 {
277 0     0 1 0 my $self = shift;
278 0   0     0 my $bars = shift || $self->bars;
279 0         0 for my $n (1 .. $bars) {
280 0         0 $self->note($self->quarter, $self->closed_hh, $self->kick);
281 0         0 $self->note($self->quarter, $self->closed_hh);
282 0         0 $self->note($self->quarter, $self->closed_hh, $self->snare);
283 0         0 $self->note($self->quarter, $self->closed_hh);
284 0 0       0 if ($n % 2) {
285 0         0 $self->note($self->quarter, $self->closed_hh);
286             }
287             else {
288 0         0 $self->note($self->eighth, $self->closed_hh);
289 0         0 $self->note($self->eighth, $self->kick);
290             }
291             }
292             }
293              
294              
295             sub metronome58 {
296 0     0 1 0 my $self = shift;
297 0   0     0 my $bars = shift || $self->bars;
298 0         0 for my $n (1 .. $bars) {
299 0         0 $self->note($self->eighth, $self->closed_hh, $self->kick);
300 0         0 $self->note($self->eighth, $self->closed_hh);
301 0         0 $self->note($self->eighth, $self->closed_hh, $self->snare);
302 0         0 $self->note($self->eighth, $self->closed_hh);
303 0         0 $self->note($self->eighth, $self->closed_hh);
304             }
305             }
306              
307              
308             sub metronome68 {
309 0     0 1 0 my $self = shift;
310 0   0     0 my $bars = shift || $self->bars;
311 0         0 for my $n (1 .. $bars) {
312 0         0 $self->note($self->eighth, $self->closed_hh, $self->kick);
313 0         0 $self->note($self->eighth, $self->closed_hh);
314 0         0 $self->note($self->eighth, $self->closed_hh);
315 0         0 $self->note($self->eighth, $self->closed_hh, $self->snare);
316 0         0 $self->note($self->eighth, $self->closed_hh);
317 0         0 $self->note($self->eighth, $self->closed_hh);
318             }
319             }
320              
321              
322             sub metronome74 {
323 0     0 1 0 my $self = shift;
324 0   0     0 my $bars = shift || $self->bars;
325 0         0 for my $n (1 .. $bars) {
326 0         0 $self->note($self->quarter, $self->closed_hh, $self->kick);
327 0         0 $self->note($self->quarter, $self->closed_hh);
328 0         0 $self->note($self->quarter, $self->closed_hh, $self->snare);
329 0         0 $self->note($self->eighth, $self->closed_hh);
330 0         0 $self->note($self->eighth, $self->kick);
331 0         0 $self->note($self->quarter, $self->closed_hh, $self->kick);
332 0         0 $self->note($self->quarter, $self->closed_hh, $self->snare);
333 0         0 $self->note($self->quarter, $self->closed_hh);
334             }
335             }
336              
337              
338             sub metronome78 {
339 0     0 1 0 my $self = shift;
340 0   0     0 my $bars = shift || $self->bars;
341 0         0 for my $n (1 .. $bars) {
342 0         0 $self->note($self->eighth, $self->closed_hh, $self->kick);
343 0         0 $self->note($self->eighth, $self->closed_hh);
344 0         0 $self->note($self->eighth, $self->closed_hh);
345 0         0 $self->note($self->eighth, $self->closed_hh, $self->kick);
346 0         0 $self->note($self->eighth, $self->closed_hh, $self->snare);
347 0         0 $self->note($self->eighth, $self->closed_hh);
348 0         0 $self->note($self->eighth, $self->closed_hh);
349             }
350             }
351              
352              
353             sub flam {
354 0     0 1 0 my ($self, $spec, $grace, $patch, $accent) = @_;
355 0   0     0 $grace ||= $self->snare;
356 0   0     0 $patch ||= $self->snare;
357 0         0 my $x = $MIDI::Simple::Length{$spec};
358 0         0 my $y = $MIDI::Simple::Length{ $self->sixtyfourth };
359 0         0 my $z = sprintf '%0.f', ($x - $y) * TICKS;
360 0   0     0 $accent ||= sprintf '%0.f', $self->score->Volume / 2;
361 0 0       0 if ($grace eq 'r') {
362 0         0 $self->rest($self->sixtyfourth);
363             }
364             else {
365 0         0 $self->accent_note($accent, $self->sixtyfourth, $grace);
366             }
367 0         0 $self->note('d' . $z, $patch);
368             }
369              
370              
371             sub roll {
372 0     0 1 0 my ($self, $length, $spec, $patch) = @_;
373 0   0     0 $patch ||= $self->snare;
374 0         0 my $x = $MIDI::Simple::Length{$length};
375 0         0 my $y = $MIDI::Simple::Length{$spec};
376 0         0 my $z = sprintf '%0.f', $x / $y;
377 0         0 $self->note($spec, $patch) for 1 .. $z;
378             }
379              
380              
381             sub crescendo_roll {
382 0     0 1 0 my ($self, $span, $length, $spec, $patch) = @_;
383 0   0     0 $patch ||= $self->snare;
384 0         0 my ($i, $j, $k) = @$span;
385 0         0 my $x = $MIDI::Simple::Length{$length};
386 0         0 my $y = $MIDI::Simple::Length{$spec};
387 0         0 my $z = sprintf '%0.f', $x / $y;
388 0 0       0 if ($k) {
389 0         0 my $bezier = Math::Bezier->new(
390             1, $i,
391             $z, $i,
392             $z, $j,
393             );
394 0         0 for (my $n = 0; $n <= 1; $n += (1 / ($z - 1))) {
395 0         0 my (undef, $v) = $bezier->point($n);
396 0         0 $v = sprintf '%0.f', $v;
397             # warn(__PACKAGE__,' ',__LINE__," $n INC: $v\n");
398 0         0 $self->accent_note($v, $spec, $patch);
399             }
400             }
401             else {
402 0         0 my $v = sprintf '%0.f', ($j - $i) / ($z - 1);
403             # warn(__PACKAGE__,' ',__LINE__," VALUE: $v\n");
404 0         0 for my $n (1 .. $z) {
405 0 0       0 if ($n == $z) {
406 0 0       0 if ($i < $j) {
    0          
407 0         0 $i += $j - $i;
408             }
409             elsif ($i > $j) {
410 0         0 $i -= $i - $j;
411             }
412             }
413             # warn(__PACKAGE__,' ',__LINE__," $n INC: $i\n");
414 0         0 $self->accent_note($i, $spec, $patch);
415 0         0 $i += $v;
416             }
417             }
418             }
419              
420              
421             sub pattern {
422 34     34 1 639 my ( $self, %args ) = @_;
423              
424 34   33     94 $args{instrument} ||= $self->snare;
425 34   50     78 $args{patterns} ||= [];
426 34   33     178 $args{beats} ||= $self->beats;
427 34   50     128 $args{negate} ||= 0;
428 34   50     130 $args{repeat} ||= 1;
429              
430 34 50       46 return unless @{ $args{patterns} };
  34         81  
431              
432             # set size and duration
433 34         54 my $size;
434 34 100       55 if ( $args{duration} ) {
435 33   50     96 $size = dura_size( $args{duration} ) || 1;
436             }
437             else {
438 1         4 $size = 4 / length( $args{patterns}->[0] );
439 1         7 my $dump = reverse_dump('length');
440 1   33     403 $args{duration} = $dump->{$size} || $self->quarter;
441             }
442              
443             # set the default beat-string variations
444             $args{vary} ||= {
445 556     556   1145 0 => sub { $self->rest( $args{duration} ) },
446 121     121   275 1 => sub { $self->note( $args{duration}, $args{instrument} ) },
447 34   50     624 };
448              
449 34         56 for my $pattern (@{ $args{patterns} }) {
  34         72  
450 34 50       73 $pattern =~ tr/01/10/ if $args{negate};
451              
452 34 50       96 next if $pattern =~ /^0+$/;
453              
454 34         82 for ( 1 .. $args{repeat} ) {
455 34         162 for my $bit ( split //, $pattern ) {
456 677         22043 $args{vary}{$bit}->($self, %args);
457             }
458             }
459             }
460             }
461              
462              
463             sub sync_patterns {
464 11     11 1 37 my ($self, %patterns) = @_;
465              
466 11         24 my $master_duration = delete $patterns{duration};
467              
468 11         18 my @subs;
469 11         23 for my $instrument (keys %patterns) {
470             push @subs, sub {
471             $self->pattern(
472             instrument => $instrument,
473 33 50   33   1256 patterns => $patterns{$instrument},
474             $master_duration ? (duration => $master_duration) : (),
475             );
476             },
477 33         135 }
478              
479 11         33 $self->sync(@subs);
480             }
481              
482              
483             sub add_fill {
484 11     11 1 12218 my ($self, $fill, %patterns) = @_;
485              
486             $fill ||= sub {
487             return {
488 10     10   41 duration => 8,
489             $self->open_hh => '000',
490             $self->snare => '111',
491             $self->kick => '000',
492             };
493 11   100     81 };
494 11         32 my $fill_patterns = $fill->($self);
495 11 50       40 print 'Fill: ', ddc($fill_patterns) if $self->verbose;
496 11   50     33 my $fill_duration = delete $fill_patterns->{duration} || 8;
497 11         34 my $fill_length = length((values %$fill_patterns)[0]);
498              
499 11         17 my %lengths;
500 11         28 for my $instrument (keys %patterns) {
501 33         47 $lengths{$instrument} = sum0 map { length $_ } @{ $patterns{$instrument} };
  33         102  
  33         57  
502             }
503              
504 11         34 my $lcm = _multilcm($fill_duration, values %lengths);
505 11 50       40 print "LCM: $lcm\n" if $self->verbose;
506              
507 11         24 my $size = 4 / $lcm;
508 11         31 my $dump = reverse_dump('length');
509 11   66     3638 my $master_duration = $dump->{$size} || $self->eighth; # XXX this || is not right
510 11 50       36 print "Size: $size, Duration: $master_duration\n" if $self->verbose;
511              
512 11 100       41 my $fill_chop = $fill_duration == $lcm
513             ? $fill_length
514             : int($lcm / $fill_length) + 1;
515 11 50       29 print "Chop: $fill_chop\n" if $self->verbose;
516              
517 11         14 my %fresh_patterns;
518 11         26 for my $instrument (keys %patterns) {
519             # get a single "flattened" pattern as an arrayref
520 33         702 my $pattern = [ map { split //, $_ } @{ $patterns{$instrument} } ];
  33         134  
  33         59  
521             # the fresh pattern is possibly upsized with the LCM
522             $fresh_patterns{$instrument} = @$pattern < $lcm
523 33 100       95 ? [ join '', @{ upsize($pattern, $lcm) } ]
  29         69  
524             : [ join '', @$pattern ];
525             }
526 11 50       326 print 'Patterns: ', ddc(\%fresh_patterns) if $self->verbose;
527              
528 11         20 my %replacement;
529 11         26 for my $instrument (keys %$fill_patterns) {
530             # get a single "flattened" pattern as a zero-pre-padded arrayref
531 33         169 my $pattern = [ split //, sprintf '%0*s', $fill_duration, $fill_patterns->{$instrument} ];
532             # the fresh pattern string is possibly upsized with the LCM
533             my $fresh = @$pattern < $lcm
534 33 100       102 ? join '', @{ upsize($pattern, $lcm) }
  15         30  
535             : join '', @$pattern;
536             # the replacement string is the tail of the fresh pattern string
537 33         571 $replacement{$instrument} = substr $fresh, -$fill_chop;
538             }
539 11 50       32 print 'Replacements: ', ddc(\%replacement) if $self->verbose;
540              
541 11         17 my %replaced;
542 11         27 for my $instrument (keys %fresh_patterns) {
543             # get the string to replace
544 33         44 my $string = join '', @{ $fresh_patterns{$instrument} };
  33         66  
545             # replace the tail of the string
546 33         53 my $pos = length $replacement{$instrument};
547 33         60 substr $string, -$pos, $pos, $replacement{$instrument};
548 33 50       72 print "$instrument: $string\n" if $self->verbose;
549             # prepare the replaced pattern for syncing
550 33         110 $replaced{$instrument} = [ $string ];
551             }
552              
553             $self->sync_patterns(
554 11         56 %replaced,
555             duration => $master_duration,
556             );
557              
558 11         698 return \%replaced;
559             }
560              
561              
562             sub set_time_sig {
563 6     6 1 795 my ($self, $signature, $set) = @_;
564 6 50       24 $self->signature($signature) if $signature;
565 6   100     19 $set //= 1;
566 6 100       13 if ($set) {
567 5         19 my ($beats, $divisions) = split /\//, $self->signature;
568 5         14 $self->beats($beats);
569 5         11 $self->divisions($divisions);
570             }
571 6         24 set_time_signature($self->score, $self->signature);
572             }
573              
574              
575             sub set_bpm {
576 1     1 1 3599 my ($self, $bpm) = @_;
577 1         7 $self->bpm($bpm);
578 1         14 $self->score->set_tempo( int( 60_000_000 / $self->bpm ) );
579             }
580              
581              
582             sub set_channel {
583 2     2 1 2314 my ($self, $channel) = @_;
584 2   100     19 $channel //= 9;
585 2         10 $self->channel($channel);
586 2         11 $self->score->noop( 'c' . $channel );
587             }
588              
589              
590             sub set_volume {
591 2     2 1 1611 my ($self, $volume) = @_;
592 2   100     9 $volume ||= 0;
593 2         9 $self->volume($volume);
594 2         14 $self->score->noop( 'V' . $volume );
595             }
596              
597              
598             sub sync {
599 11     11 1 18 my $self = shift;
600 11         46 $self->score->synch(@_);
601             }
602              
603              
604             sub write {
605 0     0 1 0 my $self = shift;
606 0         0 $self->score->write_score( $self->file );
607             }
608              
609              
610             sub timidity_cfg {
611 2     2 1 526 my ($self, $config_file) = @_;
612 2 50       10 die 'No soundfont defined' unless $self->soundfont;
613 2         10 my $cfg = timidity_conf($self->soundfont, $config_file);
614 2         315 return $cfg;
615             }
616              
617              
618             sub play_with_timidity {
619 0     0 1 0 my ($self, $config) = @_;
620 0         0 $self->write;
621 0         0 my @cmd;
622 0 0       0 if ($self->soundfont) {
623 0   0     0 $config ||= 'timidity-midi-util.cfg';
624 0         0 timidity_conf($self->soundfont, $config);
625 0         0 @cmd = ('timidity', '-c', $config, $self->file);
626             }
627             else {
628 0         0 @cmd = ('timidity', $self->file);
629             }
630 0 0       0 system(@cmd) == 0 or die "system(@cmd) failed: $?";
631             }
632              
633             # lifted from https://www.perlmonks.org/?node_id=56906
634             sub _gcf {
635 33     33   58 my ($x, $y) = @_;
636 33         98 ($x, $y) = ($y, $x % $y) while $y;
637 33         95 return $x;
638             }
639             sub _lcm {
640 33     33   71 return($_[0] * $_[1] / _gcf($_[0], $_[1]));
641             }
642             sub _multilcm {
643 11     11   17 my $x = shift;
644 11         35 $x = _lcm($x, shift) while @_;
645 11         16 return $x;
646             }
647              
648             1;
649              
650             __END__