File Coverage

blib/lib/MIDI/Tweaks.pm
Criterion Covered Total %
statement 391 413 94.6
branch 116 168 69.0
condition 44 71 61.9
subroutine 56 58 96.5
pod 5 18 27.7
total 612 728 84.0


line stmt bran cond sub pod time code
1             #! perl
2              
3             package MIDI::Tweaks;
4              
5 22     22   305806 use warnings;
  22         51  
  22         644  
6 22     22   101 use strict;
  22         45  
  22         679  
7              
8             =head1 NAME
9              
10             MIDI::Tweaks - Enhancements to MIDI.pm.
11              
12             =cut
13              
14             our $VERSION = '1.01';
15              
16 22     22   7746 use MIDI;
  22         219358  
  22         728  
17 22     22   143 use Carp;
  22         46  
  22         1088  
18              
19             # These are valid for all events.
20 22     22   116 use constant EV_TYPE => 0;
  22         41  
  22         1558  
21 22     22   110 use constant EV_TIME => 1;
  22         39  
  22         804  
22             # These are for events that apply to a channel only.
23 22     22   111 use constant EV_CHAN => 2;
  22         44  
  22         782  
24             # These are for note events.
25 22     22   104 use constant EV_NOTE_PITCH => 3;
  22         38  
  22         791  
26 22     22   120 use constant EV_NOTE_VELO => 4;
  22         43  
  22         804  
27             # These if for track_name events.
28 22     22   216 use constant EV_MARKER_NAME => 2;
  22         39  
  22         784  
29              
30             # Drum channel
31 22     22   100 use constant MIDI_CHAN_PERCUSSION => 10;
  22         36  
  22         764  
32              
33 22     22   100 use base qw(Exporter);
  22         37  
  22         2514  
34              
35             our @EXPORT;
36             our @EXPORT_OK;
37              
38             BEGIN {
39 22     22   91 @EXPORT = qw(EV_TYPE EV_TIME EV_CHAN EV_NOTE_PITCH EV_NOTE_VELO EV_MARKER_NAME);
40 22         5245 @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 438 my ($e) = shift;
122 315         1001 $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 6575 my ($e) = shift;
134 4528 100       13692 $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 3624 my ($e) = shift;
147 2337 100 100     11060 $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   195 $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 8111 my ($e) = shift;
173 5654         23104 $e->[EV_TYPE] =~ $evpat;
174             }
175              
176             =head1 OPUS METHODS
177              
178             =cut
179              
180             package MIDI::Tweaks::Opus;
181              
182 22     22   132 use strict;
  22         44  
  22         344  
183 22     22   82 use warnings;
  22         32  
  22         560  
184 22     22   89 use base qw(MIDI::Opus);
  22         40  
  22         1115  
185 22     22   186 use MIDI::Tweaks;
  22         41  
  22         959  
186 22     22   103 use Carp;
  22         34  
  22         14419  
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   37124 my $pkg = shift;
204              
205 23 50       88 my $args = $_[0] ? { %{$_[0]} } : {};
  23         100  
206 23         66 my $require_sanity = delete($args->{require_sanity});
207 23 50       85 $require_sanity = 1 unless defined $require_sanity;
208              
209 23         216 my $op = $pkg->SUPER::new($args);
210              
211 23         96070 $op->delta2time;
212 23 50       196 $op->check_sanity({ strict => $require_sanity }) if $require_sanity;
213              
214 21         755 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   249 my $op = shift->copy;
228 2         707 $op->time2delta;
229 2         31 $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   39806 my $op = shift->copy;
241 23         3624 $op->time2delta;
242 23         157 $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   89 my ($self, $args) = @_;
257 24   50     87 $args ||= {};
258              
259 24         48 my $strict = 1;
260 24 100       96 if ( $args->{strict} ) {
261 23         65 $strict = delete $args->{strict}; # 1, or 'warn'
262             }
263              
264 24         52 my @channel_seen;
265             my $fail;
266 24         50 my $tn = 1;
267 24         97 foreach my $track ( $self->tracks ) {
268 51         232 my $chan;
269             my $noteon;
270 51         190 foreach ( $track->events ) {
271 5634 100       10073 next unless MIDI::Tweaks::is_channel_event($_);
272 4293 100       8938 if ( defined $chan ) {
273 4260 100       8831 if ( $_->[EV_CHAN] != $chan ) {
274 2         289 carp("Sanity check: track $tn controls channels ",
275             $chan+1,
276             " and ",
277             $_->[EV_CHAN]+1);
278 2         65 $fail++;
279             }
280             }
281             else {
282 33         80 $chan = $_->[EV_CHAN];
283 33 100       100 if ( $channel_seen[$chan] ) {
284 1         175 carp("Sanity check: channel ",
285             $chan+1,
286             " is controlled by tracks ",
287             $channel_seen[$chan],
288             " and $tn");
289 1         32 $fail++;
290             }
291 33         72 $channel_seen[$chan] = $tn;
292             }
293 4293 100       7374 if ( MIDI::Tweaks::is_note_on($_) ) {
    100          
294 2095 100       4021 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         3732 $noteon->[$_->[EV_NOTE_PITCH]] = $_->[EV_TIME];
301             }
302             }
303             elsif ( MIDI::Tweaks::is_note_off($_) ) {
304 2095 100       4172 if ( defined $noteon->[$_->[EV_NOTE_PITCH]] ) {
305 2093         3916 $noteon->[$_->[EV_NOTE_PITCH]] = undef;
306             }
307             else {
308 2         213 carp("Sanity warning: track $tn, time $_->[EV_TIME], "
309             . "note $_->[EV_NOTE_PITCH] not on");
310             }
311             }
312             }
313 51         304 foreach my $i ( 0 .. $#{$noteon} ) {
  51         180  
314 2449 100       4620 next unless defined $noteon->[$i];
315 1         78 carp("Sanity check: track $tn, "
316             . "unfinished note $i (on since $noteon->[$i])");
317 1         23 $fail++;
318             }
319 51         137 $tn++;
320             }
321 24 100       92 if ( $fail ) {
322 2 50       8 return if $strict eq 'warn';
323 2         194 croak("Sanity check failed");
324             }
325 22         71 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   462 my ($self) = @_;
345 24         130 foreach my $track ( $self->tracks ) {
346 51         322 $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   525 my ($self) = @_;
365 26         90 foreach my $track ( $self->tracks ) {
366 65         326 $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   3204 my $self = shift;
385 4         13 foreach my $track ( $self->tracks ) {
386 8 50       44 next if $track->channel == MIDI::Tweaks::MIDI_CHAN_PERCUSSION; # skip drums
387 8         20 $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   817 my $self = shift;
406 2         8 foreach my $track ( $self->tracks ) {
407 4         29 $track->change_tempo(@_);
408             }
409             }
410              
411             # We need to override MIDI::Opus::dump for this to work...
412              
413 22     22   143 no warnings qw(redefine once);
  22         35  
  22         6585  
414              
415             sub MIDI::Opus::dump { # method; read-only
416 23     23 1 53 my $this = $_[0];
417 23         117 my %info = $this->info();
418 23 50       278 my $options_r = ref($_[1]) eq 'HASH' ? $_[1] : {};
419              
420 23 50       86 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         128 " 'ticks' => ", &MIDI::_dump_quote($this->{'ticks'}), ",\n";
439              
440 23         817 my @tracks = $this->tracks;
441 23 50       269 if( $options_r->{'dump_tracks'} ) {
442 23         144 print " 'tracks' => [ # ", scalar(@tracks), " tracks...\n\n";
443 23         92 foreach my $x (0 .. $#tracks) {
444 56         2048420 my $track = $tracks[$x];
445 56         153 print " # Track \#$x ...\n";
446 56 50       162 if(ref($track)) {
447 56         200 $track->dump($options_r);
448             } else {
449 0         0 print " # \[$track\] is not a reference!!\n";
450             }
451             }
452 23         74235 print " ]\n";
453             } else {
454 0         0 print " 'tracks' => [ ], # ", scalar(@tracks), " tracks (not dumped)\n";
455             }
456 23         101 print "});\n";
457 23         839 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   131 use strict;
  22         38  
  22         367  
468 22     22   86 use warnings;
  22         37  
  22         381  
469 22     22   130 use Carp;
  22         36  
  22         47321  
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 13 my $track = shift;
498 8         19 foreach my $e ( $track->events ) {
499 20 100       82 next unless MIDI::Tweaks::is_channel_event($e);
500 4         16 return $e->[EV_CHAN] + 1;
501             }
502 4         13 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 126 my ($self, $force) = @_;
518              
519 51 50       145 if ( $self->{_tweaky_abstime} ) {
520 0 0       0 croak("MIDI::Track::delta2time: Already abstime")
521             unless $force;
522             }
523              
524 51         90 my $time = 0; # time until now
525 51         151 foreach my $e ( $self->events ) {
526 5634         8422 $time = $e->[EV_TIME] += $time;
527             }
528              
529 51         200 $self->{_tweaky_abstime} = 1;
530              
531             # For convenience:
532 51         112 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 132 my ($self, $force) = @_;
547              
548 65 50       186 unless ( $self->{_tweaky_abstime} ) {
549 0 0       0 croak("MIDI::Track::delta2time: Already delta time")
550             unless $force;
551             }
552              
553 65         127 my $time = 0; # time until now
554 65         164 foreach my $e ( $self->events ) {
555 7128 50       13755 carp("NEGATIVE DELTA \@ $time: @{[$e->[EV_TIME]-$time]}\n")
  0         0  
556             if $e->[EV_TIME] < $time;
557             # Make time relative.
558 7128         10985 ($time, $e->[EV_TIME]) = ($e->[EV_TIME], $e->[EV_TIME]-$time);
559             }
560              
561 65         264 delete $self->{_tweaky_abstime};
562              
563             # For convenience:
564 65         131 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 925 my ($self) = @_;
579 3         5 my $time = 0;
580 3         9 foreach my $e ( $self->events ) {
581 699 100       1263 return 1 if $e->[EV_TIME] < $time;
582 698         933 $time = $e->[EV_TIME];
583             }
584 2         20 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 28 my $track = shift;
613              
614 16         41 my $opts = {};
615 16 50       51 $opts = {%{shift()}} if ref($_[0]) eq 'HASH';
  16         36  
616              
617 16         32 my $mapper = shift;
618 16 50       47 croak("MIDI::Track::mapper requires a CODE argument")
619             unless ref($mapper) eq 'CODE';
620              
621 16 100       48 $track = $track->copy if delete $opts->{copy};
622              
623 16         68 foreach ( $track->events ) {
624 325         595 $mapper->($_, $opts);
625             }
626              
627 16         128 $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 16 my ($track, $args) = @_;
647 8 50       18 croak("MIDI::Track::change_pitch requires a HASH argument")
648             unless ref($args) eq 'HASH';
649 8         23 $args = {%$args};
650              
651 8         13 my $mapper_func;
652             # C Db D Es E F Gb G As A Bb B
653 8         23 my @k = ( 0, -5, 2, -3, 4, -1, -6, 1, -4, 3, -2, 5);
654 8         13 my %k; $k{$k[$_]} = $_ for 0 .. $#k;
  8         68  
655              
656 8 50       26 if ( $args->{int} ) {
657 8         18 my $value = int(delete $args->{int});
658              
659             $mapper_func = sub {
660 136 100   136   204 if ( MIDI::Tweaks::is_note_event($_[0]) ) {
661 72         116 $_[0]->[EV_NOTE_PITCH] += $value;
662 72 50 33     241 croak("MIDI::Track::change_pitch: transposed pitch out of range")
663             unless $_[0]->[EV_NOTE_PITCH] >= 0 && $_[0]->[EV_NOTE_PITCH] <= 127;
664 72         119 return;
665             }
666 64 100       151 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         8 my $f = $_[0]->[2]; # current #sharps
671 4 50       9 $f -= 12 if $f >= 6; # normalize
672 4 50       9 $f += 12 if $f < -6;
673 4         8 $f = $k{$f}; # get note
674 4         7 $f += $value; # transpose
675 4         10 $f -= 12 while $f >= 12; # normalize
676 4         11 $f += 12 while $f < 0;
677 4         7 $_[0]->[2] = $k[$f]; # get #sharps
678 4         7 return;
679             }
680 8         29 };
681             }
682              
683 8 50       21 croak("MIDI::Track::change_pitch: Missing 'value' or 'ratio' option")
684             unless $mapper_func;
685              
686 8         19 $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 1446 my ($track, $args) = @_;
713 3 50       13 croak("MIDI::Track::change_velocity requires a HASH argument")
714             unless ref($args) eq 'HASH';
715 3         11 $args = {%$args};
716              
717 3         6 my $mapper_func;
718              
719 3 100       9 if ( $args->{value} ) {
    50          
720 2         5 my $value = int(delete $args->{value});
721 2 50 33     11 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   93 return unless MIDI::Tweaks::is_note_on($_[0]);
726 18         31 $_[0]->[EV_NOTE_VELO] = $value;
727 2         9 };
728             }
729             elsif ( $args->{ratio} ) {
730 1         3 my $ratio = delete $args->{ratio};
731             $mapper_func = sub {
732 29 100   29   45 return unless MIDI::Tweaks::is_note_on($_[0]);
733 9         18 $_[0]->[EV_NOTE_VELO] = int($_[0]->[EV_NOTE_VELO] * $ratio);
734 9 50       20 $_[0]->[EV_NOTE_VELO] = 127 if $_[0]->[EV_NOTE_VELO] > 127;
735 1         5 };
736             }
737              
738 3 50       11 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 12 my ($track, $args) = @_;
763 4 50       16 croak("MIDI::Track::change_tempo requires a HASH argument")
764             unless ref($args) eq 'HASH';
765 4         13 $args = {%$args};
766              
767 4         10 my $mapper_func;
768              
769 4 100       13 if ( $args->{value} ) {
    50          
770 2         6 my $value = int(60000000 / int(delete $args->{value}));
771              
772             $mapper_func = sub {
773 36 100   36   77 return unless $_[0]->[0] eq 'set_tempo';
774 3         6 $_[0]->[2] = $value;
775 2         7 };
776             }
777             elsif ( $args->{ratio} ) {
778 2         4 my $ratio = delete $args->{ratio};
779             $mapper_func = sub {
780 36 100   36   77 return unless $_[0]->[0] eq 'set_tempo';
781 3         8 $_[0]->[2] = int($_[0]->[2] / $ratio);
782 2         8 };
783             }
784              
785 4 50       11 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 442 my ($track, $args) = @_;
808 1 50       6 croak("MIDI::Track::change_volume requires a HASH argument")
809             unless ref($args) eq 'HASH';
810 1         3 $args = {%$args};
811              
812 1         2 my $mapper_func;
813              
814 1 50       6 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   70 $_[0]->[0] eq 'control_change'
831             && $_[0]->[3] == 7;
832 1         5 $_[0]->[4] = int($_[0]->[4] * $ratio);
833 1 50       4 $_[0]->[4] = 127 if $_[0]->[4] > 127;
834 1         5 };
835             }
836              
837 1 50       5 croak("MIDI::Track::change_volume: Missing 'value' or 'ratio' option")
838             unless $mapper_func;
839              
840 1         12 $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 742 my ($track, $args) = @_;
867 1   50     3 $args ||= {};
868 1 50       5 croak("MIDI::Track::split_pitch requires a HASH argument")
869             unless ref($args) eq 'HASH';
870 1         3 $args = {%$args};
871              
872 1   50     8 my $split ||= 56;
873              
874 1 50       3 if ( $args->{pitch} ) {
875 1         3 $split = delete $args->{pitch};
876 1 50 33     8 croak("MIDI::Track::split_pitch: split value should be between 0 and 127")
877             unless $split >= 0 && $split <= 127;
878             }
879              
880 1 50       4 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         2 my @hi;
887             my @lo;
888              
889 1         4 foreach ( $track->events ) {
890 29 100       58 unless ( MIDI::Tweaks::is_note_event($_) ) {
891             # Copy.
892 11         26 push(@hi, [@$_]);
893 11         21 push(@lo, [@$_]);
894 11         15 next;
895             }
896              
897 18 100       37 if ( $_->[EV_NOTE_PITCH] >= $split ) {
898 12         26 push(@hi, [@$_]);
899             }
900             else {
901 6         13 push(@lo, [@$_]);
902             }
903             }
904              
905 1         13 my $hi = MIDI::Track->new;
906 1         24 $hi->type($track->type);
907 1         14 $hi->events(@hi);
908 1         8 $hi->{_tweaky_abstime} = 1;
909              
910 1         3 my $lo = MIDI::Track->new;
911 1         20 $lo->type($track->type);
912 1         11 $lo->events(@lo);
913 1         7 $lo->{_tweaky_abstime} = 1;
914              
915 1         4 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 2217 my ($track) = @_;
943              
944             croak("MIDI::Track::split_hilo: FATAL: track has delta times")
945 3 50       12 unless $track->{_tweaky_abstime};
946              
947 3         7 my @hi;
948             my @lo;
949 3     50   14 my $eqtimes = sub { $_[0]->[EV_TIME] == $_[1]->[EV_TIME] };
  50         173  
950              
951 3         11 my @events = $track->events;
952 3         39 while ( @events ) {
953 94         141 my $this_event = shift(@events);
954 94         117 my $next_event = $events[0];
955              
956             # Skip lyrics.
957 94 100       230 next if $this_event->[EV_TYPE] =~ /^lyric$/;
958              
959             # Assert we're still in phase.
960 70 50       141 unless ( @hi == @lo ) {
961 0         0 croak("!t1 = ", scalar(@hi), " events\n",
962             "!t2 = ", scalar(@lo), " events\n");
963             }
964              
965 70 100 100     124 unless ( MIDI::Tweaks::is_note_event($this_event)
      100        
966             && @events && $eqtimes->($this_event, $next_event) ) {
967             # Copy.
968 36         80 push(@hi, [@$this_event]);
969 36         70 push(@lo, [@$this_event]);
970 36         85 next;
971             }
972              
973 34 100 66     65 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         28 shift(@events);
981              
982 20 100       46 if ( $this_event->[EV_NOTE_PITCH] > $next_event->[EV_NOTE_PITCH] ) {
983 12         29 push(@hi, [@$this_event]);
984 12         42 push(@lo, [@$next_event]);
985             }
986             else {
987 8         20 push(@hi, [@$next_event]);
988 8         24 push(@lo, [@$this_event]);
989             }
990             }
991             else {
992             # Not a multi-note, copy.
993 14         32 push(@hi, [@$this_event]);
994 14         44 push(@lo, [@$this_event]);
995             }
996             }
997              
998 3         13 my $hi = MIDI::Track->new;
999 3         88 $hi->type($track->type);
1000 3         39 $hi->events(@hi);
1001 3         23 $hi->{_tweaky_abstime} = 1;
1002              
1003 3         10 my $lo = MIDI::Track->new;
1004 3         61 $lo->type($track->type);
1005 3         31 $lo->events(@lo);
1006 3         24 $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 1638 my ($track) = @_;
1043              
1044             croak("MIDI::Track::split_hml: FATAL: track has delta times")
1045 3 50       14 unless $track->{_tweaky_abstime};
1046              
1047 3         10 my @hi;
1048             my @md;
1049 3         0 my @lo;
1050 3     28   15 my $eqtimes = sub { $_[0]->[EV_TIME] == $_[1]->[EV_TIME] };
  28         92  
1051              
1052 3         14 my @events = $track->events;
1053 3         34 my $time = 0;
1054              
1055 3         10 while ( @events ) {
1056 104         158 my $this_event = shift(@events);
1057 104         138 my $next_event = $events[0];
1058 104 100       252 next if $this_event->[EV_TYPE] =~ /^lyric$/;
1059              
1060 77 50 33     292 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         109 $time = $this_event->[EV_TIME];
1067              
1068 77 100       137 if ( MIDI::Tweaks::is_note_event($this_event) ) {
1069             # Check if there's a note already at this time.
1070 60 100 66     124 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     22 if ( MIDI::Tweaks::is_note_on($events[1])
1074             && $eqtimes->($this_event, $events[1]) ) {
1075             # Remove next from events.
1076 3         5 shift(@events);
1077             # Store higher in hi, lower in md, etc.
1078             # (also removes afternext from events)
1079             my @a = sort {
1080 9         17 $b->[EV_NOTE_PITCH] <=> $a->[EV_NOTE_PITCH]
1081 3         17 } ( [@$this_event], [@$next_event], [@{shift(@events)}] );
  3         7  
1082 3         6 push(@hi, $a[0]);
1083 3         6 push(@md, $a[1]);
1084 3         6 push(@lo, $a[2]);
1085             }
1086             else {
1087             # Remove next from events.
1088 8         13 shift(@events);
1089             my @a = sort {
1090 8         36 $b->[EV_NOTE_PITCH] <=> $a->[EV_NOTE_PITCH]
  8         24  
1091             } ( [@$this_event], [@$next_event] );
1092 8         19 push(@hi, $a[0]);
1093 8         16 push(@md, $a[1]);
1094 8         14 push(@lo, $a[1]);
1095             }
1096 11         34 $hi[-1]->[EV_TIME] = $time;
1097 11         16 $md[-1]->[EV_TIME] = $time;
1098 11         29 $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     21 if ( MIDI::Tweaks::is_note_off($events[1])
1104             && $eqtimes->($this_event, $events[1]) ) {
1105             # Remove next from events.
1106 3         4 shift(@events);
1107             # Store higher in hi, lower in md, etc.
1108             # (also removes afternext from events)
1109             my @a = sort {
1110 9         15 $b->[EV_NOTE_PITCH] <=> $a->[EV_NOTE_PITCH]
1111 3         8 } ( [@$this_event], [@$next_event], [@{shift(@events)}] );
  3         10  
1112 3         7 push(@hi, $a[0]);
1113 3         5 push(@md, $a[1]);
1114 3         6 push(@lo, $a[2]);
1115             }
1116             else {
1117             # Remove next from events.
1118 8         12 shift(@events);
1119             my @a = sort {
1120 8         26 $b->[EV_NOTE_PITCH] <=> $a->[EV_NOTE_PITCH]
  8         21  
1121             } ( [@$this_event], [@$next_event] );
1122 8         15 push(@hi, $a[0]);
1123 8         14 push(@md, $a[1]);
1124 8         13 push(@lo, $a[1]);
1125             }
1126 11         18 $hi[-1]->[EV_TIME] = $time;
1127 11         16 $md[-1]->[EV_TIME] = $time;
1128 11         26 $lo[-1]->[EV_TIME] = $time;
1129             }
1130             else {
1131             # Not a multi-note, copy.
1132 38         91 push(@hi, [@$this_event]);
1133 38         113 push(@md, [@$this_event]);
1134 38         109 push(@lo, [@$this_event]);
1135             }
1136             }
1137             else {
1138             # Not a note, copy.
1139 17         54 push(@hi, [@$this_event]);
1140 17         36 push(@md, [@$this_event]);
1141 17         47 push(@lo, [@$this_event]);
1142             }
1143             }
1144              
1145 3         15 my $hi = MIDI::Track->new;
1146 3         76 $hi->type($track->type);
1147 3         40 $hi->events(@hi);
1148 3         28 $hi->{_tweaky_abstime} = 1;
1149              
1150 3         10 my $md = MIDI::Track->new;
1151 3         63 $md->type($track->type);
1152 3         43 $md->events(@md);
1153 3         31 $md->{_tweaky_abstime} = 1;
1154              
1155 3         11 my $lo = MIDI::Track->new;
1156 3         61 $lo->type($track->type);
1157 3         38 $lo->events(@lo);
1158 3         20 $lo->{_tweaky_abstime} = 1;
1159              
1160 3         23 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             Development of this module is hosted on GitHub:
1188             L. Feel free to fork and
1189             contribute.
1190              
1191             =head1 ACKNOWLEDGEMENTS
1192              
1193             =head1 COPYRIGHT & LICENSE
1194              
1195             Copyright 2008,2017 Johan Vromans, Squirrel Consultancy. All rights reserved.
1196              
1197             This program is free software; you can redistribute it and/or modify it
1198             under the same terms as Perl itself.
1199              
1200             =cut
1201              
1202             1;