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