File Coverage

blib/lib/MIDI/Tweaks.pm
Criterion Covered Total %
statement 390 413 94.4
branch 116 168 69.0
condition 44 71 61.9
subroutine 56 58 96.5
pod 5 18 27.7
total 611 728 83.9


line stmt bran cond sub pod time code
1             #! perl
2              
3             package MIDI::Tweaks;
4              
5 22     22   327778 use warnings;
  22         34  
  22         678  
6 22     22   75 use strict;
  22         25  
  22         696  
7              
8             =head1 NAME
9              
10             MIDI::Tweaks - Enhancements to MIDI.pm.
11              
12             =cut
13              
14             our $VERSION = '1.00';
15              
16 22     22   9020 use MIDI;
  22         233316  
  22         781  
17 22     22   129 use Carp;
  22         23  
  22         1083  
18              
19             # These are valid for all events.
20 22     22   81 use constant EV_TYPE => 0;
  22         20  
  22         1504  
21 22     22   79 use constant EV_TIME => 1;
  22         24  
  22         816  
22             # These are for events that apply to a channel only.
23 22     22   77 use constant EV_CHAN => 2;
  22         31  
  22         768  
24             # These are for note events.
25 22     22   73 use constant EV_NOTE_PITCH => 3;
  22         23  
  22         749  
26 22     22   74 use constant EV_NOTE_VELO => 4;
  22         21  
  22         782  
27             # These if for track_name events.
28 22     22   209 use constant EV_MARKER_NAME => 2;
  22         22  
  22         803  
29              
30             # Drum channel
31 22     22   65 use constant MIDI_CHAN_PERCUSSION => 10;
  22         20  
  22         748  
32              
33 22     22   73 use base qw(Exporter);
  22         17  
  22         2696  
34              
35             our @EXPORT;
36             our @EXPORT_OK;
37              
38             BEGIN {
39 22     22   52 @EXPORT = qw(EV_TYPE EV_TIME EV_CHAN EV_NOTE_PITCH EV_NOTE_VELO EV_MARKER_NAME);
40 22         5506 @EXPORT_OK = qw(is_note_event is_note_on is_note_off is_channel_event);
41             }
42              
43             =head1 SYNOPSIS
44              
45             This module implements a number of MIDI tweaks using the Sean Burke's
46             MIDI module.
47              
48             # Read midi data.
49             my $op = new MIDI::Tweaks::Opus ({ from_file => "orig.mid" });
50              
51             # Reset all volume controls.
52             $_->change_volume({ value => 100 }) foreach $op->tracks;
53              
54             # Slowdown a bit.
55             $op->change_tempo({ ratio => 0.9 });
56              
57             # Prepare the individual tracks.
58             my $track0 = $op->tracks_r->[0];
59             my $acc = $op->tracks_r->[1]->change_velocity({ value => 30 });
60             my $solo = $op->tracks_r->[2]->change_velocity({ value => 110 });
61             my $high = $op->tracks_r->[3]->change_velocity({ value => 100 });
62             my $low = $op->tracks_r->[4]->change_velocity({ value => 100 });
63              
64             # $low contains the middle + lower parts. Split.
65             (my $mid, $low) = $low->split_hilo;
66              
67             # Produce a midi for low voice only.
68             $op->tracks($track0, $acc, $low);
69             $op->write_to_file("low.mid");
70              
71              
72             Warning: This module is still under development. The interface to the
73             methods may change when new features are added.
74              
75             Two scripts are provided when installing this module:
76              
77             midi-tweak: applies some tweaks to MIDI files
78              
79             midi-dump: dumps contents of a MIDI file in an understandable format
80              
81             =head1 CONSTANTS
82              
83             The following constants will be exported by default.
84              
85             =head2 EV_TYPE
86              
87             The offset in an event (array ref) of the type information.
88              
89             =head2 EV_TIME
90              
91             The offset in an event (array ref) of the delta time.
92              
93             =head2 EV_CHAN
94              
95             The offset in an event (array ref) of the channel.
96              
97             =head2 EV_NOTE_PITCH
98              
99             The offset in a note event of the pitch.
100              
101             =head2 EV_NOTE_VELO
102              
103             The offset in a note event of the velocity.
104              
105             =head2 EV_MARKER_NAME
106              
107             The offset in a marker event of the name.
108              
109             =head1 FUNCTIONS
110              
111             The following functions can be exported on demand.
112              
113             =head2 MIDI::Tweaks::is_note_event
114              
115             Function. Takes an event (array reference) as argument.
116             Returns true if the event is a 'note on' or 'note off' event.
117              
118             =cut
119              
120             sub is_note_event {
121 315     315 1 212 my ($e) = shift;
122 315         798 $e->[EV_TYPE] =~ /^note_o(n|ff)$/;
123             }
124              
125             =head2 MIDI::Tweaks::is_note_on
126              
127             Function. Takes an event (array reference) as argument.
128             Returns true if the event is a 'note on' event with a non-zero velocity.
129              
130             =cut
131              
132             sub is_note_on {
133 4528     4528 1 3111 my ($e) = shift;
134 4528 100       9515 $e->[EV_TYPE] eq 'note_on' && $e->[EV_NOTE_VELO];
135             }
136              
137             =head2 MIDI::Tweaks::is_note_off
138              
139             Function. Takes an event (array reference) as argument.
140             Returns true if the event is a 'note off' event, or a 'note on' event
141             with zero velocity.
142              
143             =cut
144              
145             sub is_note_off {
146 2337     2337 1 1541 my ($e) = shift;
147 2337 100 100     8798 $e->[EV_TYPE] eq 'note_off'
148             || $e->[EV_TYPE] eq 'note_on' && !$e->[EV_NOTE_VELO];
149             }
150              
151             =head2 MIDI::Tweaks::is_channel_event
152              
153             Function. Takes an event (array reference) as argument.
154             Returns true if the event aqpplies to specific channel.
155              
156             =cut
157              
158             my $evpat;
159             INIT {
160 22     22   171 $evpat = qr/^
161             note_off
162             | note_on
163             | key_after_touch
164             | control_change
165             | patch_change
166             | channel_after_touch
167             | pitch_wheel_change
168             $/x;
169             }
170              
171             sub is_channel_event {
172 5654     5654 1 3801 my ($e) = shift;
173 5654         16183 $e->[EV_TYPE] =~ $evpat;
174             }
175              
176             =head1 OPUS METHODS
177              
178             =cut
179              
180             package MIDI::Tweaks::Opus;
181              
182 22     22   93 use strict;
  22         23  
  22         630  
183 22     22   59 use warnings;
  22         17  
  22         612  
184 22     22   63 use base qw(MIDI::Opus);
  22         18  
  22         1112  
185 22     22   151 use MIDI::Tweaks;
  22         21  
  22         978  
186 22     22   68 use Carp;
  22         21  
  22         15130  
187              
188             =head2 MIDI::Tweaks::Opus::new
189              
190             Method. Does whatever MIDI::Opus::new does, but checks for sanity and
191             produces an Opus with absolute time stamps.
192              
193             The options hash may contain a key C that controls the
194             level of sanity checking:
195              
196             0: no checking
197             1: normal checking
198             warn: normal checking, but warn instead of die
199              
200             =cut
201              
202             sub new {
203 23     23   30216 my $pkg = shift;
204              
205 23 50       81 my $args = $_[0] ? { %{$_[0]} } : {};
  23         101  
206 23         52 my $require_sanity = delete($args->{require_sanity});
207 23 50       86 $require_sanity = 1 unless defined $require_sanity;
208              
209 23         196 my $op = $pkg->SUPER::new($args);
210              
211 23         49499 $op->delta2time;
212 23 50       186 $op->check_sanity({ strict => $require_sanity }) if $require_sanity;
213              
214 21         961 return $op;
215             }
216              
217             =head2 MIDI::Tweaks::Opus::write_to_handle
218              
219             Method. Copies the Opus, converts the time stamps to delta times and
220             passes the result to MIDI::Opus::write_to_handle.
221              
222             Note that this method is used internally by write_to_file.
223              
224             =cut
225              
226             sub write_to_handle {
227 2     2   239 my $op = shift->copy;
228 2         611 $op->time2delta;
229 2         19 $op->SUPER::write_to_handle(@_);
230             }
231              
232             =head2 MIDI::Tweaks::Opus::dump
233              
234             Method. Copies the Opus, converts the time stamps to delta times and
235             passes the result to MIDI::Opus::dump.
236              
237             =cut
238              
239             sub dump {
240 23     23   20321 my $op = shift->copy;
241 23         3272 $op->time2delta;
242 23         144 $op->SUPER::dump(@_);
243             }
244              
245             =head2 MIDI::Tweaks::Opus::check_sanity
246              
247             Method, internal. Verifies that the MIDI data obeys certain criteria
248             that make it suitable for tweaking. In particular, there must be a
249             one-to-one relationship between tracks and channels.
250              
251             This method is called internally by the MIDI::Tweaks::Opus::new method.
252              
253             =cut
254              
255             sub check_sanity {
256 24     24   41 my ($self, $args) = @_;
257 24   50     70 $args ||= {};
258              
259 24         35 my $strict = 1;
260 24 100       62 if ( $args->{strict} ) {
261 23         48 $strict = delete $args->{strict}; # 1, or 'warn'
262             }
263              
264 24         32 my @channel_seen;
265             my $fail;
266 24         36 my $tn = 1;
267 24         81 foreach my $track ( $self->tracks ) {
268 51         122 my $chan;
269             my $noteon;
270 51         131 foreach ( $track->events ) {
271 5634 100       5245 next unless MIDI::Tweaks::is_channel_event($_);
272 4293 100       4437 if ( defined $chan ) {
273 4260 100       5314 if ( $_->[EV_CHAN] != $chan ) {
274 2         297 carp("Sanity check: track $tn controls channels ",
275             $chan+1,
276             " and ",
277             $_->[EV_CHAN]+1);
278 2         54 $fail++;
279             }
280             }
281             else {
282 33         47 $chan = $_->[EV_CHAN];
283 33 100       87 if ( $channel_seen[$chan] ) {
284 1         183 carp("Sanity check: channel ",
285             $chan+1,
286             " is controlled by tracks ",
287             $channel_seen[$chan],
288             " and $tn");
289 1         30 $fail++;
290             }
291 33         43 $channel_seen[$chan] = $tn;
292             }
293 4293 100       3764 if ( MIDI::Tweaks::is_note_on($_) ) {
    100          
294 2095 100       2099 if ( defined $noteon->[$_->[EV_NOTE_PITCH]] ) {
295 1         86 carp("Sanity warning: track $tn, time $_->[EV_TIME], "
296             . "note $_->[EV_NOTE_PITCH] already on (since "
297             . $noteon->[$_->[EV_NOTE_PITCH]] . ")");
298             }
299             else {
300 2094         2140 $noteon->[$_->[EV_NOTE_PITCH]] = $_->[EV_TIME];
301             }
302             }
303             elsif ( MIDI::Tweaks::is_note_off($_) ) {
304 2095 100       2270 if ( defined $noteon->[$_->[EV_NOTE_PITCH]] ) {
305 2093         2153 $noteon->[$_->[EV_NOTE_PITCH]] = undef;
306             }
307             else {
308 2         202 carp("Sanity warning: track $tn, time $_->[EV_TIME], "
309             . "note $_->[EV_NOTE_PITCH] not on");
310             }
311             }
312             }
313 51         215 foreach my $i ( 0 .. $#{$noteon} ) {
  51         122  
314 2449 100       3022 next unless defined $noteon->[$i];
315 1         79 carp("Sanity check: track $tn, "
316             . "unfinished note $i (on since $noteon->[$i])");
317 1         20 $fail++;
318             }
319 51         162 $tn++;
320             }
321 24 100       76 if ( $fail ) {
322 2 50       6 return if $strict eq 'warn';
323 2         169 croak("Sanity check failed");
324             }
325 22         51 return 1;
326             }
327              
328             =head2 MIDI::Tweaks::Opus::delta2time
329              
330             Method, internal. Modifies the Opus by changing the delta times of all
331             events of all tracks to an absolute time value.
332              
333             This method is called internally by the MIDI::Tweaks::Opus::new method.
334              
335             THIS MAKES THE OPUS NO LONGER DIRECTLY VALID FOR MIDI. When this
336             method has been applied to an Opus it should be undone later by a call
337             to time2delta. This is handled transparently by the
338             MIDI::Tweaks::Opus::write_to_file and MIDI::Tweaks::Opus::dump
339             methods.
340              
341             =cut
342              
343             sub delta2time {
344 24     24   391 my ($self) = @_;
345 24         134 foreach my $track ( $self->tracks ) {
346 51         256 $track->delta2time;
347             }
348             }
349              
350             =head2 MIDI::Tweaks::Opus::time2delta
351              
352             Method, internal. Modifies the Opus by making all time events relative
353             (delta times).
354              
355             This method undoes the effect of a previous delta2time, making the
356             Opus valid MIDI data again.
357              
358             This method is called internally by MIDI::Tweaks::Opus::write_to_file and
359             MIDI::Tweaks::Opus::dump methods.
360              
361             =cut
362              
363             sub time2delta {
364 26     26   419 my ($self) = @_;
365 26         67 foreach my $track ( $self->tracks ) {
366 65         214 $track->time2delta;
367             }
368             }
369              
370             =head2 MIDI::Tweaks::Opus::change_pitch
371              
372             Method. One argument, the options hash.
373              
374             Modifies the pitch of the Opus.
375              
376             This method just calls MIDI::Track::change_pitch on all tracks. See
377             L for details. It skips the track
378             associated with channel 9 which is typically associated with
379             percussion.
380              
381             =cut
382              
383             sub change_pitch {
384 4     4   2587 my $self = shift;
385 4         11 foreach my $track ( $self->tracks ) {
386 8 50       28 next if $track->channel == MIDI::Tweaks::MIDI_CHAN_PERCUSSION; # skip drums
387 8         17 $track->change_pitch(@_);
388             }
389             }
390              
391             =head2 MIDI::Tweaks::Opus::change_tempo
392              
393             Method. One argument, the options hash.
394              
395             Modifies the tempo settings of the Opus.
396              
397             The options has must contain either C<< value => number >> or C<<
398             ratio => number >>. In the first case, the tempo is set to the
399             specified value (beats per minute). In the second case, the tempo is
400             changed according to the ratio.
401              
402             =cut
403              
404             sub change_tempo {
405 2     2   1090 my $self = shift;
406 2         6 foreach my $track ( $self->tracks ) {
407 4         16 $track->change_tempo(@_);
408             }
409             }
410              
411             # We need to override MIDI::Opus::dump for this to work...
412              
413 22     22   107 no warnings qw(redefine once);
  22         20  
  22         7180  
414              
415             sub MIDI::Opus::dump { # method; read-only
416 23     23 1 32 my $this = $_[0];
417 23         119 my %info = $this->info();
418 23 50       245 my $options_r = ref($_[1]) eq 'HASH' ? $_[1] : {};
419              
420 23 50       69 if($options_r->{'flat'}) { # Super-barebones dump mode
421 0   0     0 my $d = $options_r->{'delimiter'} || "\t";
422 0         0 foreach my $track ($this->tracks) {
423 0         0 foreach my $event (@{ $track->events_r }) {
  0         0  
424 0         0 print( join($d, @$event), "\n" );
425             }
426             }
427 0         0 return;
428             }
429              
430             # This is the only change to the original code: replace the
431             # hard-wired class name MIDI::Opus with ref($this), so derived
432             # classes work.
433             # WAS: print "MIDI::Opus->new({\n",
434             # IS NOW:
435             print ref($this), "->new({\n",
436             # End of change.
437             " 'format' => ", &MIDI::_dump_quote($this->{'format'}), ",\n",
438 23         120 " 'ticks' => ", &MIDI::_dump_quote($this->{'ticks'}), ",\n";
439              
440 23         983 my @tracks = $this->tracks;
441 23 50       156 if( $options_r->{'dump_tracks'} ) {
442 23         108 print " 'tracks' => [ # ", scalar(@tracks), " tracks...\n\n";
443 23         65 foreach my $x (0 .. $#tracks) {
444 56         37515 my $track = $tracks[$x];
445 56         107 print " # Track \#$x ...\n";
446 56 50       113 if(ref($track)) {
447 56         140 $track->dump($options_r);
448             } else {
449 0         0 print " # \[$track\] is not a reference!!\n";
450             }
451             }
452 23         41761 print " ]\n";
453             } else {
454 0         0 print " 'tracks' => [ ], # ", scalar(@tracks), " tracks (not dumped)\n";
455             }
456 23         124 print "});\n";
457 23         838 return 1;
458             }
459              
460             =head1 TRACK METHODS
461              
462             =cut
463              
464             # We cannot use package MIDI::Track, since that is owbed by MIDI.pm.
465             package MIDI::Tweaks;
466              
467 22     22   87 use strict;
  22         23  
  22         377  
468 22     22   61 use warnings;
  22         21  
  22         410  
469 22     22   63 use Carp;
  22         20  
  22         49087  
470              
471             =head2 MIDI::Track::name
472              
473             Method. Returns the first track name as designated by an 'track name' event.
474             If none was found, returns undef.
475              
476             =cut
477              
478             sub MIDI::Track::name {
479 0     0 0 0 my $track = shift;
480 0         0 foreach my $e ( $track->events ) {
481 0 0       0 return $e->[EV_MARKER_NAME]
482             if $e->[EV_TYPE] eq 'track_name';
483             }
484 0         0 return;
485             }
486              
487             =head2 MIDI::Track::channel
488              
489             Method. Returns the channel controlled by this track.
490             If none was found, returns zero.
491              
492             Note that channels are numbered from one, as per MIDI standard.
493              
494             =cut
495              
496             sub MIDI::Track::channel {
497 8     8 0 6 my $track = shift;
498 8         17 foreach my $e ( $track->events ) {
499 20 100       41 next unless MIDI::Tweaks::is_channel_event($e);
500 4         11 return $e->[EV_CHAN] + 1;
501             }
502 4         11 return 0;
503             }
504              
505             =head2 MIDI::Track::delta2time
506              
507             Method, internal. Modifies the track by changing the delta times of
508             all events to an absolute time value.
509              
510             THIS MAKES THE TRACK NO LONGER VALID FOR MIDI. When this method has
511             been applied to a track it should be undone later by a call to
512             time2delta.
513              
514             =cut
515              
516             sub MIDI::Track::delta2time {
517 51     51 0 65 my ($self, $force) = @_;
518              
519 51 50       131 if ( $self->{_tweaky_abstime} ) {
520 0 0       0 croak("MIDI::Track::delta2time: Already abstime")
521             unless $force;
522             }
523              
524 51         51 my $time = 0; # time until now
525 51         127 foreach my $e ( $self->events ) {
526 5634         4266 $time = $e->[EV_TIME] += $time;
527             }
528              
529 51         203 $self->{_tweaky_abstime} = 1;
530              
531             # For convenience:
532 51         75 return $self;
533             }
534              
535             =head2 MIDI::Track::time2delta
536              
537             Method, internal. Modifies the track by making all time events
538             relative (delta times).
539              
540             This method undoes the effect of a previous delta2time, making the
541             track valid MIDI data again.
542              
543             =cut
544              
545             sub MIDI::Track::time2delta {
546 65     65 0 79 my ($self, $force) = @_;
547              
548 65 50       128 unless ( $self->{_tweaky_abstime} ) {
549 0 0       0 croak("MIDI::Track::delta2time: Already delta time")
550             unless $force;
551             }
552              
553 65         89 my $time = 0; # time until now
554 65         148 foreach my $e ( $self->events ) {
555 7128 50       8319 carp("NEGATIVE DELTA \@ $time: @{[$e->[EV_TIME]-$time]}\n")
  0         0  
556             if $e->[EV_TIME] < $time;
557             # Make time relative.
558 7128         5936 ($time, $e->[EV_TIME]) = ($e->[EV_TIME], $e->[EV_TIME]-$time);
559             }
560              
561 65         217 delete $self->{_tweaky_abstime};
562              
563             # For convenience:
564 65         85 return $self;
565             }
566              
567             =head2 MIDI::Track::has_deltatime
568              
569             Method, internal. Returns true if the track events have delta time
570             stamps.
571              
572             This method is not fail safe, i.e., it can return thw wrong result if
573             a track does not have sensible events.
574              
575             =cut
576              
577             sub MIDI::Track::has_deltatime {
578 3     3 0 946 my ($self) = @_;
579 3         2 my $time = 0;
580 3         8 foreach my $e ( $self->events ) {
581 699 100       724 return 1 if $e->[EV_TIME] < $time;
582 698         435 $time = $e->[EV_TIME];
583             }
584 2         17 return;
585             }
586              
587             =head2 MIDI::Track::mapper
588              
589             Method. One or two arguments.
590             First argument is optional: an options hash.
591             Second, or only, argument must be a code ref.
592              
593             Applies to code ref to all events of
594             the track. Returns the track (for convenience);
595              
596             Note that if the code ref modifies the event, this actually modifies
597             the track. If this is not desired, copy it first, or use
598             C<< copy => 1 >> in the options hash.
599              
600             The code ref gets two arguments, the event (an array ref), and the
601             remainder of the options hash.
602              
603             Examples:
604              
605             $track->mapper(sub { print $_->[0] });
606             $new = $track->mapper({ copy => 1 },
607             sub { $_->[1] += 10 });
608              
609             =cut
610              
611             sub MIDI::Track::mapper {
612 16     16 0 14 my $track = shift;
613              
614 16         15 my $opts = {};
615 16 50       39 $opts = {%{shift()}} if ref($_[0]) eq 'HASH';
  16         25  
616              
617 16         18 my $mapper = shift;
618 16 50       35 croak("MIDI::Track::mapper requires a CODE argument")
619             unless ref($mapper) eq 'CODE';
620              
621 16 100       30 $track = $track->copy if delete $opts->{copy};
622              
623 16         51 foreach ( $track->events ) {
624 325         322 $mapper->($_, $opts);
625             }
626              
627 16         95 $track;
628             }
629              
630             =head2 MIDI::Track::change_pitch
631              
632             Method. One argument, the options hash.
633              
634             Changes the pitch of each 'note on' event according to the options.
635              
636             The options has must contain C<< int => number >>. The number
637             indicates the number of half-tones the pitch should be raised. A
638             negative number will lower the pitch.
639             Any remaining options are passed to the mapper function.
640              
641             Note that key signatures will be changed as well.
642              
643             =cut
644              
645             sub MIDI::Track::change_pitch {
646 8     8 0 5 my ($track, $args) = @_;
647 8 50       16 croak("MIDI::Track::change_pitch requires a HASH argument")
648             unless ref($args) eq 'HASH';
649 8         20 $args = {%$args};
650              
651 8         7 my $mapper_func;
652             # C Db D Es E F Gb G As A Bb B
653 8         15 my @k = ( 0, -5, 2, -3, 4, -1, -6, 1, -4, 3, -2, 5);
654 8         6 my %k; $k{$k[$_]} = $_ for 0 .. $#k;
  8         66  
655              
656 8 50       19 if ( $args->{int} ) {
657 8         10 my $value = int(delete $args->{int});
658              
659             $mapper_func = sub {
660 136 100   136   110 if ( MIDI::Tweaks::is_note_event($_[0]) ) {
661 72         50 $_[0]->[EV_NOTE_PITCH] += $value;
662 72 50 33     175 croak("MIDI::Track::change_pitch: transposed pitch out of range")
663             unless $_[0]->[EV_NOTE_PITCH] >= 0 && $_[0]->[EV_NOTE_PITCH] <= 127;
664 72         63 return;
665             }
666 64 100       86 if ( $_[0]->[0] eq 'key_signature' ) {
667             # Warning: ugly code ahead.
668             # This is expected to be run only a few times.
669             # Don't spent much effort on elegance and optimizing.
670 4         6 my $f = $_[0]->[2]; # current #sharps
671 4 50       6 $f -= 12 if $f >= 6; # normalize
672 4 50       7 $f += 12 if $f < -6;
673 4         5 $f = $k{$f}; # get note
674 4         4 $f += $value; # transpose
675 4         10 $f -= 12 while $f >= 12; # normalize
676 4         7 $f += 12 while $f < 0;
677 4         4 $_[0]->[2] = $k[$f]; # get #sharps
678 4         5 return;
679             }
680 8         27 };
681             }
682              
683 8 50       19 croak("MIDI::Track::change_pitch: Missing 'value' or 'ratio' option")
684             unless $mapper_func;
685              
686 8         14 $track->mapper($args, $mapper_func);
687             }
688              
689             =head2 MIDI::Track::change_velocity
690              
691             Method. One argument, the options hash.
692              
693             Changes the velocity of each 'note on' event according to the options.
694              
695             The options has must contain either C<< value => number >> or
696             C<< ratio => number >>. In the first case, the velocity is set to the
697             specified value (which must be a number between 0 and 127). In the
698             second case, the velocity is changed according to the ratio.
699              
700             Any remaining options are passed to the mapper function.
701              
702             Note that setting the velocity to zero effectively turns the 'note on'
703             events into 'note off' events.
704              
705             Also note that tracks usually have an initial 'control_change' event
706             that controls the overall volume for a channel. Use change_volume to
707             change this setting.
708              
709             =cut
710              
711             sub MIDI::Track::change_velocity {
712 3     3 0 1473 my ($track, $args) = @_;
713 3 50       22 croak("MIDI::Track::change_velocity requires a HASH argument")
714             unless ref($args) eq 'HASH';
715 3         8 $args = {%$args};
716              
717 3         4 my $mapper_func;
718              
719 3 100       7 if ( $args->{value} ) {
    50          
720 2         4 my $value = int(delete $args->{value});
721 2 50 33     9 croak("MIDI::Track::change_velocity: value should be between 0 and 127")
722             unless $value >= 0 && $value <= 127;
723              
724             $mapper_func = sub {
725 58 100   58   65 return unless MIDI::Tweaks::is_note_on($_[0]);
726 18         16 $_[0]->[EV_NOTE_VELO] = $value;
727 2         7 };
728             }
729             elsif ( $args->{ratio} ) {
730 1         2 my $ratio = delete $args->{ratio};
731             $mapper_func = sub {
732 29 100   29   24 return unless MIDI::Tweaks::is_note_on($_[0]);
733 9         12 $_[0]->[EV_NOTE_VELO] = int($_[0]->[EV_NOTE_VELO] * $ratio);
734 9 50       12 $_[0]->[EV_NOTE_VELO] = 127 if $_[0]->[EV_NOTE_VELO] > 127;
735 1         4 };
736             }
737              
738 3 50       5 croak("MIDI::Track::change_velocity: Missing 'value' or 'ratio' option")
739             unless $mapper_func;
740              
741 3         8 $track->mapper($args, $mapper_func);
742             }
743              
744             =head2 MIDI::Track::change_tempo
745              
746             Method. One argument, the options hash.
747              
748             Changes the tempo of a trackaccording to the options.
749              
750             The options has must contain either C<< value => number >> or C<<
751             ratio => number >>. In the first case, each occurence of a tempo event
752             is changed to the specified value. In the second case, the tempo is
753             changed according to the ratio.
754              
755             Any remaining options are passed to the mapper function.
756              
757             Note that usually track 0 controls the tempi for an opus.
758              
759             =cut
760              
761             sub MIDI::Track::change_tempo {
762 4     4 0 4 my ($track, $args) = @_;
763 4 50       11 croak("MIDI::Track::change_tempo requires a HASH argument")
764             unless ref($args) eq 'HASH';
765 4         9 $args = {%$args};
766              
767 4         4 my $mapper_func;
768              
769 4 100       9 if ( $args->{value} ) {
    50          
770 2         5 my $value = int(60000000 / int(delete $args->{value}));
771              
772             $mapper_func = sub {
773 36 100   36   46 return unless $_[0]->[0] eq 'set_tempo';
774 3         3 $_[0]->[2] = $value;
775 2         6 };
776             }
777             elsif ( $args->{ratio} ) {
778 2         3 my $ratio = delete $args->{ratio};
779             $mapper_func = sub {
780 36 100   36   42 return unless $_[0]->[0] eq 'set_tempo';
781 3         7 $_[0]->[2] = int($_[0]->[2] / $ratio);
782 2         6 };
783             }
784              
785 4 50       8 croak("MIDI::Track::change_tempo: Missing 'value' or 'ratio' option")
786             unless $mapper_func;
787              
788 4         10 $track->mapper($args, $mapper_func);
789             }
790              
791             =head2 MIDI::Track::change_volume
792              
793             Method. One argument, the options hash.
794              
795             Changes the volume of the channel.
796              
797             The options has must contain either C<< value => number >> or
798             C<< ratio => number >>. In the first case, the volume is set to the
799             specified value (which must be a number between 0 and 127). In the
800             second case, the volume is changed according to the ratio.
801              
802             Any remaining options are passed to the mapper function.
803              
804             =cut
805              
806             sub MIDI::Track::change_volume {
807 1     1 0 560 my ($track, $args) = @_;
808 1 50       4 croak("MIDI::Track::change_volume requires a HASH argument")
809             unless ref($args) eq 'HASH';
810 1         3 $args = {%$args};
811              
812 1         1 my $mapper_func;
813              
814 1 50       4 if ( $args->{value} ) {
    50          
815 0         0 my $value = int(delete $args->{value});
816 0 0 0     0 croak("MIDI::Track::change_volume: value should be between 0 and 127")
817             unless $value >= 0 && $value <= 127;
818              
819             $mapper_func = sub {
820             return unless
821 0 0 0 0   0 $_[0]->[0] eq 'control_change'
822             && $_[0]->[3] == 7;
823 0         0 $_[0]->[4] = $value;
824 0         0 };
825             }
826             elsif ( $args->{ratio} ) {
827 1         2 my $ratio = delete $args->{ratio};
828             $mapper_func = sub {
829             return unless
830 30 100 100 30   50 $_[0]->[0] eq 'control_change'
831             && $_[0]->[3] == 7;
832 1         4 $_[0]->[4] = int($_[0]->[4] * $ratio);
833 1 50       2 $_[0]->[4] = 127 if $_[0]->[4] > 127;
834 1         4 };
835             }
836              
837 1 50       3 croak("MIDI::Track::change_volume: Missing 'value' or 'ratio' option")
838             unless $mapper_func;
839              
840 1         2 $track->mapper($args, $mapper_func);
841             }
842              
843             =head2 MIDI::Track::split_pitch
844              
845             Method. One argument, the options hash.
846              
847             The track is split into two tracks, depending on whether the pitch of
848             a note event is lower than a preset value. Non-note events are copied
849             to both tracks.
850              
851             The options hash may contain C<< pitch => number >> to specify the
852             pitch value to split on. All notes whose pitches are less than the
853             split value are copied to the lower track, all other notes are copied
854             to the upper track.
855              
856             Default value is 56. This is a suitable value to split a single MIDI
857             track containing a piano part into left hand and right hand tracks.
858              
859             All events are copied, and the track is not modified.
860              
861             This method returns a list, the higher track and the lower track.
862              
863             =cut
864              
865             sub MIDI::Track::split_pitch {
866 1     1 0 535 my ($track, $args) = @_;
867 1   50     3 $args ||= {};
868 1 50       3 croak("MIDI::Track::split_pitch requires a HASH argument")
869             unless ref($args) eq 'HASH';
870 1         3 $args = {%$args};
871              
872 1   50     4 my $split ||= 56;
873              
874 1 50       3 if ( $args->{pitch} ) {
875 1         2 $split = delete $args->{pitch};
876 1 50 33     7 croak("MIDI::Track::split_pitch: split value should be between 0 and 127")
877             unless $split >= 0 && $split <= 127;
878             }
879              
880 1 50       2 croak("MIDI::Track::split_pitch: unknown options: ".
881             join(" ", sort keys %$args)) if %$args;
882              
883             croak("MIDI::Track::split_pitch: FATAL: track has delta times")
884 1 50       3 unless $track->{_tweaky_abstime};
885              
886 1         1 my @hi;
887             my @lo;
888              
889 1         3 foreach ( $track->events ) {
890 29 100       31 unless ( MIDI::Tweaks::is_note_event($_) ) {
891             # Copy.
892 11         17 push(@hi, [@$_]);
893 11         12 push(@lo, [@$_]);
894 11         12 next;
895             }
896              
897 18 100       21 if ( $_->[EV_NOTE_PITCH] >= $split ) {
898 12         17 push(@hi, [@$_]);
899             }
900             else {
901 6         12 push(@lo, [@$_]);
902             }
903             }
904              
905 1         4 my $hi = MIDI::Track->new;
906 1         14 $hi->type($track->type);
907 1         8 $hi->events(@hi);
908 1         4 $hi->{_tweaky_abstime} = 1;
909              
910 1         3 my $lo = MIDI::Track->new;
911 1         12 $lo->type($track->type);
912 1         5 $lo->events(@lo);
913 1         4 $lo->{_tweaky_abstime} = 1;
914              
915 1         3 return ( $hi, $lo );
916             }
917              
918             =head2 MIDI::Track::split_hilo
919              
920             Method. No arguments.
921              
922             The track is split into two tracks, high and low.
923              
924             If there are two 'note on' (or 'note off') events at the same time,
925             the event with the highest pitch gets copied to the high track and the
926             other to the low track. If there's only one note, or if it is not a
927             note event, it gets copied to both tracks.
928              
929             All events are copied, and the track is not modified.
930              
931             This method returns a list (high track, low track).
932              
933             NOTE: This process assumes that if there are two notes, they start and
934             end at the same time.
935              
936             NOTE: This process discards all non-note events from the resultant
937             tracks. Sorry.
938              
939             =cut
940              
941             sub MIDI::Track::split_hilo {
942 3     3 0 2040 my ($track) = @_;
943              
944             croak("MIDI::Track::split_hilo: FATAL: track has delta times")
945 3 50       13 unless $track->{_tweaky_abstime};
946              
947 3         5 my @hi;
948             my @lo;
949 3     50   15 my $eqtimes = sub { $_[0]->[EV_TIME] == $_[1]->[EV_TIME] };
  50         166  
950              
951 3         12 my @events = $track->events;
952 3         30 while ( @events ) {
953 94         76 my $this_event = shift(@events);
954 94         79 my $next_event = $events[0];
955              
956             # Skip lyrics.
957 94 100       193 next if $this_event->[EV_TYPE] =~ /^lyric$/;
958              
959             # Assert we're still in phase.
960 70 50       92 unless ( @hi == @lo ) {
961 0         0 croak("!t1 = ", scalar(@hi), " events\n",
962             "!t2 = ", scalar(@lo), " events\n");
963             }
964              
965 70 100 100     90 unless ( MIDI::Tweaks::is_note_event($this_event)
      100        
966             && @events && $eqtimes->($this_event, $next_event) ) {
967             # Copy.
968 36         53 push(@hi, [@$this_event]);
969 36         51 push(@lo, [@$this_event]);
970 36         64 next;
971             }
972              
973 34 100 66     50 if ( MIDI::Tweaks::is_note_on($this_event)
      66        
      66        
974             && MIDI::Tweaks::is_note_on($next_event)
975             or
976             MIDI::Tweaks::is_note_off($this_event)
977             && MIDI::Tweaks::is_note_off($next_event) ) {
978              
979             # Remove from events.
980 20         20 shift(@events);
981              
982 20 100       35 if ( $this_event->[EV_NOTE_PITCH] > $next_event->[EV_NOTE_PITCH] ) {
983 12         24 push(@hi, [@$this_event]);
984 12         35 push(@lo, [@$next_event]);
985             }
986             else {
987 8         21 push(@hi, [@$next_event]);
988 8         24 push(@lo, [@$this_event]);
989             }
990             }
991             else {
992             # Not a multi-note, copy.
993 14         24 push(@hi, [@$this_event]);
994 14         35 push(@lo, [@$this_event]);
995             }
996             }
997              
998 3         15 my $hi = MIDI::Track->new;
999 3         55 $hi->type($track->type);
1000 3         29 $hi->events(@hi);
1001 3         19 $hi->{_tweaky_abstime} = 1;
1002              
1003 3         9 my $lo = MIDI::Track->new;
1004 3         36 $lo->type($track->type);
1005 3         20 $lo->events(@lo);
1006 3         14 $lo->{_tweaky_abstime} = 1;
1007              
1008 3         20 return ( $hi, $lo );
1009             }
1010              
1011             =head2 MIDI::Track::split_hml
1012              
1013             Method. No arguments.
1014              
1015             The track is split into three tracks, high, middle and low.
1016              
1017             If there are three 'note on' (or 'note off') events at the same time,
1018             the event with the highest pitch gets copied to the high track, the
1019             event with the lowest pitch gets copied to the low track, and the
1020             other to the middle track.
1021              
1022             If there are two 'note on' (or 'note off') events at the same time,
1023             the event with the highest pitch gets copied to the high track and the
1024             other to the middle and low tracks.
1025              
1026             If there's only one note event at that time, or if it is not a note
1027             event, it gets copied to all tracks.
1028              
1029             All events are copied, and the track is not modified.
1030              
1031             This method returns a list (high track, middle track, low track).
1032              
1033             NOTE: This process assumes that if there are two or three notes, they
1034             start and end at the same time.
1035              
1036             NOTE: This process discards all non-note events from the resultant
1037             tracks. Sorry.
1038              
1039             =cut
1040              
1041             sub MIDI::Track::split_hml {
1042 3     3 0 1812 my ($track) = @_;
1043              
1044             croak("MIDI::Track::split_hml: FATAL: track has delta times")
1045 3 50       13 unless $track->{_tweaky_abstime};
1046              
1047 3         3 my @hi;
1048             my @md;
1049 0         0 my @lo;
1050 3     28   11 my $eqtimes = sub { $_[0]->[EV_TIME] == $_[1]->[EV_TIME] };
  28         58  
1051              
1052 3         10 my @events = $track->events;
1053 3         23 my $time = 0;
1054              
1055 3         9 while ( @events ) {
1056 104         72 my $this_event = shift(@events);
1057 104         64 my $next_event = $events[0];
1058 104 100       174 next if $this_event->[EV_TYPE] =~ /^lyric$/;
1059              
1060 77 50 33     218 unless ( @hi == @md && @md == @lo ) {
1061 0         0 croak("!t1 = ", scalar(@hi), " events\n",
1062             "!t2 = ", scalar(@md), " events\n",
1063             "!t3 = ", scalar(@lo), " events\n");
1064             }
1065              
1066 77         58 $time = $this_event->[EV_TIME];
1067              
1068 77 100       73 if ( MIDI::Tweaks::is_note_event($this_event) ) {
1069             # Check if there's a note already at this time.
1070 60 100 66     53 if ( MIDI::Tweaks::is_note_on($this_event)
    100 100        
      66        
      100        
      100        
      66        
1071             && @events && MIDI::Tweaks::is_note_on($next_event)
1072             && $eqtimes->($this_event, $next_event) ) {
1073 11 100 66     12 if ( MIDI::Tweaks::is_note_on($events[1])
1074             && $eqtimes->($this_event, $events[1]) ) {
1075             # Remove next from events.
1076 3         3 shift(@events);
1077             # Store higher in hi, lower in md, etc.
1078             # (also removes afternext from events)
1079             my @a = sort {
1080 9         10 $b->[EV_NOTE_PITCH] <=> $a->[EV_NOTE_PITCH]
1081 3         11 } ( [@$this_event], [@$next_event], [@{shift(@events)}] );
  3         3  
1082 3         3 push(@hi, $a[0]);
1083 3         4 push(@md, $a[1]);
1084 3         3 push(@lo, $a[2]);
1085             }
1086             else {
1087             # Remove next from events.
1088 8         9 shift(@events);
1089             my @a = sort {
1090 8         27 $b->[EV_NOTE_PITCH] <=> $a->[EV_NOTE_PITCH]
  8         19  
1091             } ( [@$this_event], [@$next_event] );
1092 8         12 push(@hi, $a[0]);
1093 8         9 push(@md, $a[1]);
1094 8         10 push(@lo, $a[1]);
1095             }
1096 11         8 $hi[-1]->[EV_TIME] = $time;
1097 11         8 $md[-1]->[EV_TIME] = $time;
1098 11         22 $lo[-1]->[EV_TIME] = $time;
1099             }
1100             elsif ( MIDI::Tweaks::is_note_off($this_event)
1101             && @events && MIDI::Tweaks::is_note_off($next_event)
1102             && $eqtimes->($this_event, $next_event) ) {
1103 11 100 66     12 if ( MIDI::Tweaks::is_note_off($events[1])
1104             && $eqtimes->($this_event, $events[1]) ) {
1105             # Remove next from events.
1106 3         3 shift(@events);
1107             # Store higher in hi, lower in md, etc.
1108             # (also removes afternext from events)
1109             my @a = sort {
1110 9         9 $b->[EV_NOTE_PITCH] <=> $a->[EV_NOTE_PITCH]
1111 3         6 } ( [@$this_event], [@$next_event], [@{shift(@events)}] );
  3         4  
1112 3         3 push(@hi, $a[0]);
1113 3         3 push(@md, $a[1]);
1114 3         3 push(@lo, $a[2]);
1115             }
1116             else {
1117             # Remove next from events.
1118 8         8 shift(@events);
1119             my @a = sort {
1120 8         19 $b->[EV_NOTE_PITCH] <=> $a->[EV_NOTE_PITCH]
  8         14  
1121             } ( [@$this_event], [@$next_event] );
1122 8         10 push(@hi, $a[0]);
1123 8         5 push(@md, $a[1]);
1124 8         8 push(@lo, $a[1]);
1125             }
1126 11         11 $hi[-1]->[EV_TIME] = $time;
1127 11         7 $md[-1]->[EV_TIME] = $time;
1128 11         21 $lo[-1]->[EV_TIME] = $time;
1129             }
1130             else {
1131             # Not a multi-note, copy.
1132 38         56 push(@hi, [@$this_event]);
1133 38         49 push(@md, [@$this_event]);
1134 38         85 push(@lo, [@$this_event]);
1135             }
1136             }
1137             else {
1138             # Not a note, copy.
1139 17         23 push(@hi, [@$this_event]);
1140 17         21 push(@md, [@$this_event]);
1141 17         32 push(@lo, [@$this_event]);
1142             }
1143             }
1144              
1145 3         13 my $hi = MIDI::Track->new;
1146 3         48 $hi->type($track->type);
1147 3         25 $hi->events(@hi);
1148 3         19 $hi->{_tweaky_abstime} = 1;
1149              
1150 3         7 my $md = MIDI::Track->new;
1151 3         33 $md->type($track->type);
1152 3         44 $md->events(@md);
1153 3         16 $md->{_tweaky_abstime} = 1;
1154              
1155 3         7 my $lo = MIDI::Track->new;
1156 3         34 $lo->type($track->type);
1157 3         20 $lo->events(@lo);
1158 3         16 $lo->{_tweaky_abstime} = 1;
1159              
1160 3         18 return ( $hi, $md, $lo );
1161             }
1162              
1163             package main;
1164              
1165             =head1 AUTHOR
1166              
1167             Johan Vromans, C<< >>
1168              
1169             =head1 BUGS
1170              
1171             Please report any bugs or feature requests to C
1172             rt.cpan.org>, or through the web interface at
1173             L. I will
1174             be notified, and then you'll automatically be notified of progress on
1175             your bug as I make changes.
1176              
1177             =head1 SEE ALSO
1178              
1179             L, L, L, L.
1180              
1181             =head1 SUPPORT
1182              
1183             You can find documentation for this module with the perldoc command.
1184              
1185             perldoc MIDI::Tweaks
1186              
1187             You can also look for information at:
1188              
1189             =over 4
1190              
1191             =item * RT: CPAN's request tracker
1192              
1193             L
1194              
1195             =item * CPAN Ratings
1196              
1197             L
1198              
1199             =item * Search CPAN
1200              
1201             L
1202              
1203             =back
1204              
1205             =head1 ACKNOWLEDGEMENTS
1206              
1207             =head1 COPYRIGHT & LICENSE
1208              
1209             Copyright 2008,2017 Johan Vromans, Squirrel Consultancy. All rights reserved.
1210              
1211             This program is free software; you can redistribute it and/or modify it
1212             under the same terms as Perl itself.
1213              
1214             =cut
1215              
1216             1;