File Coverage

blib/lib/MIDI/Track.pm
Criterion Covered Total %
statement 57 86 66.2
branch 26 54 48.1
condition 9 21 42.8
subroutine 11 15 73.3
pod 9 11 81.8
total 112 187 59.8


line stmt bran cond sub pod time code
1              
2             # Time-stamp: "2013-02-01 22:40:38 conklin"
3             require 5;
4             package MIDI::Track;
5 12     12   76 use strict;
  12         26  
  12         372  
6 12     12   63 use vars qw($Debug $VERSION);
  12         23  
  12         490  
7 12     12   62 use Carp;
  12         21  
  12         13852  
8              
9             $Debug = 0;
10             $VERSION = '0.84';
11              
12             =head1 NAME
13              
14             MIDI::Track -- functions and methods for MIDI tracks
15              
16             =head1 SYNOPSIS
17              
18             use MIDI; # ...which "use"s MIDI::Track et al
19             $taco_track = MIDI::Track->new;
20             $taco_track->events(
21             ['text_event', 0, "I like tacos!"],
22             ['note_on', 0, 4, 50, 96 ],
23             ['note_off', 300, 4, 50, 96 ],
24             );
25             $opus = MIDI::Opus->new(
26             { 'format' => 0, 'ticks' => 240, 'tracks' => [ $taco_track ] }
27             );
28             ...etc...
29              
30             =head1 DESCRIPTION
31              
32             MIDI::Track provides a constructor and methods for objects
33             representing a MIDI track. It is part of the MIDI suite.
34              
35             MIDI tracks have, currently, three attributes: a type, events, and
36             data. Almost all tracks you'll ever deal with are of type "MTrk", and
37             so this is the type by default. Events are what make up an MTrk
38             track. If a track is not of type MTrk, or is an unparsed MTrk, then
39             it has (or better have!) data.
40              
41             When an MTrk track is encoded, if there is data defined for it, that's
42             what's encoded (and "encoding data" means just passing it thru
43             untouched). Note that this happens even if the data defined is ""
44             (but it won't happen if the data is undef). However, if there's no
45             data defined for the MTrk track (as is the general case), then the
46             track's events are encoded, via a call to C.
47              
48             (If neither events not data are defined, it acts as a zero-length
49             track.)
50              
51             If a non-MTrk track is encoded, its data is encoded. If there's no
52             data for it, it acts as a zero-length track.
53              
54             In other words, 1) events are meaningful only in an MTrk track, 2) you
55             probably don't want both data and events defined, and 3) 99.999% of
56             the time, just worry about events in MTrk tracks, because that's all
57             you ever want to deal with anyway.
58              
59             =head1 CONSTRUCTOR AND METHODS
60              
61             MIDI::Track provides...
62              
63             =over
64              
65             =cut
66              
67             ###########################################################################
68              
69             =item the constructor MIDI::Track->new({ ...options... })
70              
71             This returns a new track object. By default, the track is of type
72             MTrk, which is probably what you want. The options, which are
73             optional, is an anonymous hash. There are four recognized options:
74             C, which sets the data of the new track to the string provided;
75             C, which sets the type of the new track to the string provided;
76             C, which sets the events of the new track to the contents of
77             the list-reference provided (i.e., a reference to a LoL -- see
78             L for the skinny on LoLs); and C, which is an exact
79             synonym of C.
80              
81             =cut
82              
83             sub new {
84             # make a new track.
85 21     21 1 486 my $class = shift;
86 21         45 my $this = bless( {}, $class );
87 21 50       58 print "New object in class $class\n" if $Debug;
88 21         95 $this->_init( @_ );
89 21         64 return $this;
90             }
91              
92             sub _init {
93             # You can specify options:
94             # 'event' => [a list of events], AKA 'event_r'
95             # 'type' => 'Whut', # default is 'MTrk'
96             # 'data' => 'scads of binary data as you like it'
97 21     21   35 my $this = shift;
98 21 100       62 my $options_r = ref($_[0]) eq 'HASH' ? $_[0] : {};
99 21 50       80 print "_init called against $this\n" if $Debug;
100 21 50       62 if($Debug) {
101 0 0       0 if(%$options_r) {
102 0         0 print "Parameters: ", map("<$_>", %$options_r), "\n";
103             } else {
104 0         0 print "Null parameters for opus init\n";
105             }
106             }
107              
108             $this->{'type'} =
109 21 50       113 defined($options_r->{'type'}) ? $options_r->{'type'} : 'MTrk';
110             $this->{'data'} = $options_r->{'data'}
111 21 50       70 if defined($options_r->{'data'});
112              
113             $options_r->{'events'} = $options_r->{'events_r'}
114             if( exists( $options_r->{'events_r'} ) and not
115 21 100 66     76 exists( $options_r->{'events'} )
116             );
117             # so events_r => [ @events ] is a synonym for
118             # events => [ @events ]
119             # as on option for new()
120              
121             $this->{'events'} =
122             ( defined($options_r->{'events'})
123             and ref($options_r->{'events'}) eq 'ARRAY' )
124 21 100 66     142 ? $options_r->{'events'} : []
125             ;
126 21         66 return;
127             }
128              
129             =item the method $new_track = $track->copy
130              
131             This duplicates the contents of the given track, and returns
132             the duplicate. If you are unclear on why you may need this function,
133             consider:
134              
135             $funk = MIDI::Opus->new({'from_file' => 'funk1.mid'});
136             $samba = MIDI::Opus->new({'from_file' => 'samba1.mid'});
137            
138             $bass_track = ( $funk->tracks )[-1]; # last track
139             push(@{ $samba->tracks_r }, $bass_track );
140             # make it the last track
141            
142             &funk_it_up( ( $funk->tracks )[-1] );
143             # modifies the last track of $funk
144             &turn_it_out( ( $samba->tracks )[-1] );
145             # modifies the last track of $samba
146            
147             $funk->write_to_file('funk2.mid');
148             $samba->write_to_file('samba2.mid');
149             exit;
150              
151             So you have your routines funk_it_up and turn_it_out, and they each
152             modify the track they're applied to in some way. But the problem is that
153             the above code probably does not do what you want -- because the last
154             track-object of $funk and the last track-object of $samba are the
155             I. An object, you may be surprised to learn, can be in
156             different opuses at the same time -- which is fine, except in cases like
157             the above code. That's where you need to do copy the object. Change
158             the above code to read:
159              
160             push(@{ $samba->tracks_r }, $bass_track->copy );
161              
162             and what you want to happen, will.
163              
164             Incidentally, this potential need to copy also occurs with opuses (and
165             in fact any reference-based data structure, altho opuses and tracks
166             should cover almost all cases with MIDI stuff), which is why there's
167             $opus->copy, for copying entire opuses.
168              
169             (If you happen to need to copy a single event, it's just $new = [@$old] ;
170             and if you happen to need to copy an event structure (LoL) outside of a
171             track for some reason, use MIDI::Event::copy_structure.)
172              
173             =cut
174              
175             sub copy {
176             # Duplicate a given track. Even dupes the events.
177             # Call as $new_one = $track->copy
178 0     0 1 0 my $track = shift;
179              
180 0         0 my $new = bless( { %{$track} }, ref $track );
  0         0  
181             # a first crude dupe
182             $new->{'events'} = &MIDI::Event::copy_structure( $new->{'events'} )
183 0 0       0 if $new->{'events'};
184 0         0 return $new;
185             }
186              
187             ###########################################################################
188              
189             =item track->skyline({ ...options... })
190              
191             skylines the entire track. Modifies the track. See MIDI::Score for
192             documentation on skyline
193              
194             =cut
195              
196             =item track->skyline({ ...options... })
197              
198             skylines the entire track. Modifies the track. See MIDI::Score for
199             documentation on skyline
200              
201             =cut
202              
203             sub skyline {
204 1     1 1 2 my $track = shift;
205 1 50       3 my $options_r = ref($_[0]) eq 'HASH' ? $_[0] : {};
206 1         3 my $score_r = MIDI::Score::events_r_to_score_r($track->events_r);
207 1         3 my $new_score_r = MIDI::Score::skyline($score_r,$options_r);
208 1         3 my $events_r = MIDI::Score::score_r_to_events_r($new_score_r);
209 1         3 $track->events_r($events_r);
210             }
211              
212              
213             ###########################################################################
214             # These three modify all the possible attributes of a track
215              
216             =item the method $track->events( @events )
217              
218             Returns the list of events in the track, possibly after having set it
219             to @events, if specified and not empty. (If you happen to want to set
220             the list of events to an empty list, for whatever reason, you have to use
221             "$track->events_r([])".)
222              
223             In other words: $track->events(@events) is how to set the list of events
224             (assuming @events is not empty), and @events = $track->events is how to
225             read the list of events.
226              
227             =cut
228              
229             sub events { # list or set events in this object
230 9     9 1 21 my $this = shift;
231 9 50       36 $this->{'events'} = [ @_ ] if @_;
232 9         25 return @{ $this->{'events'} };
  9         196  
233             }
234              
235             =item the method $track->events_r( $event_r )
236              
237             Returns a reference to the list of events in the track, possibly after
238             having set it to $events_r, if specified. Actually, "$events_r" can be
239             any listref to a LoL, whether it comes from a scalar as in
240             C<$some_events_r>, or from something like C<[@events]>, or just plain
241             old C<\@events>
242              
243             Originally $track->events was the only way to deal with events, but I
244             added $track->events_r to make possible 1) setting the list of events
245             to (), for whatever that's worth, and 2) so you can directly
246             manipulate the track's events, without having to I the list of
247             events (which might be tens of thousands of elements long) back
248             and forth. This way, you can say:
249              
250             $events_r = $track->events_r();
251             @some_stuff = splice(@$events_r, 4, 6);
252              
253             But if you don't know how to deal with listrefs outside of LoLs,
254             that's OK, just use $track->events.
255              
256             =cut
257              
258             sub events_r {
259             # return (maybe set) a list-reference to the event-structure for this track
260 48     48 1 124 my $this = shift;
261 48 100       122 if(@_) {
262 2 50       4 croak "parameter for MIDI::Track::events_r must be an array-ref"
263             unless ref($_[0]);
264 2         9 $this->{'events'} = $_[0];
265             }
266 48         263 return $this->{'events'};
267             }
268              
269             =item the method $track->type( 'MFoo' )
270              
271             Returns the type of $track, after having set it to 'MFoo', if provided.
272             You probably won't ever need to use this method, other than in
273             a context like:
274              
275             if( $track->type eq 'MTrk' ) { # The usual case
276             give_up_the_funk($track);
277             } # Else just keep on walkin'!
278              
279             Track types must be 4 bytes long; see L for details.
280              
281             =cut
282              
283             sub type {
284 13     13 1 1517 my $this = shift;
285 13 100       40 $this->{'type'} = $_[0] if @_; # if you're setting it
286 13         52 return $this->{'type'};
287             }
288              
289             =item the method $track->data( $kooky_binary_data )
290              
291             Returns the data from $track, after having set it to
292             $kooky_binary_data, if provided -- even if it's zero-length! You
293             probably won't ever need to use this method. For your information,
294             $track->data(undef) is how to undefine the data for a track.
295              
296             =cut
297              
298             sub data {
299             # meant for reading/setting generally non-MTrk track data
300 0     0 1 0 my $this = shift;
301 0 0       0 $this->{'data'} = $_[0] if @_;
302 0         0 return $this->{'data'};
303             }
304              
305             ###########################################################################
306              
307             =item the method $track->new_event('event', ...parameters... )
308              
309             This adds the event ('event', ...parameters...) to the end of the
310             event list for $track. It's just sugar for:
311              
312             push( @{$this_track->events_r}, [ 'event', ...params... ] )
313              
314             If you want anything other than the equivalent of that, like some
315             kinda splice(), then do it yourself with $track->events_r or
316             $track->events.
317              
318             =cut
319              
320             sub new_event {
321             # Usage:
322             # $this_track->new_event('text_event', 0, 'Lesbia cum Prono');
323              
324 0     0 1 0 my $track = shift;
325 0         0 push( @{$track->{'events'}}, [ @_ ] );
  0         0  
326             # this returns the new number of events in that event list, if that
327             # interests you.
328             }
329              
330             ###########################################################################
331              
332             =item the method $track->dump({ ...options... })
333              
334             This dumps the track's contents for your inspection. The dump format
335             is code that looks like Perl code that you'd use to recreate that track.
336             This routine outputs with just C, so you can use C
337             change where that'll go. I intended this to be just an internal
338             routine for use only by the method MIDI::Opus::dump, but I figure it
339             might be useful to you, if you need to dump the code for just a given
340             track.
341             Read the source if you really need to know how this works.
342              
343             =cut
344              
345             sub dump { # dump a track's contents
346 0     0 1 0 my $this = $_[0];
347 0 0       0 my $options_r = ref($_[1]) eq 'HASH' ? $_[1] : {};
348 0         0 my $type = $this->type;
349              
350 0         0 my $indent = ' ';
351 0         0 my @events = $this->events;
352             print(
353             $indent, "MIDI::Track->new({\n",
354             $indent, " 'type' => ", &MIDI::_dump_quote($type), ",\n",
355             defined($this->{'data'}) ?
356             ( $indent, " 'data' => ",
357 0 0       0 &MIDI::_dump_quote($this->{'data'}), ",\n" )
358             : (),
359             $indent, " 'events' => [ # ", scalar(@events), " events.\n",
360             );
361 0         0 foreach my $event (@events) {
362 0         0 &MIDI::Event::dump(@$event);
363             # was: print( $indent, " [", &MIDI::_dump_quote(@$event), "],\n" );
364             }
365 0         0 print( "$indent ]\n$indent}),\n$indent\n" );
366 0         0 return;
367             }
368              
369             ###########################################################################
370              
371             # CURRENTLY UNDOCUMENTED -- no end-user ever needs to call this as such
372             #
373             sub encode { # encode a track object into track data (not a chunk)
374             # Calling format:
375             # $data_r = $track->encode( { .. options .. } )
376             # The (optional) argument is an anonymous hash of options.
377             # Returns a REFERENCE to track data.
378             #
379 2     2 0 4 my $track = $_[0];
380 2 50       14 croak "$track is not a track object!" unless ref($track);
381 2 50       10 my $options_r = ref($_[1]) eq 'HASH' ? $_[1] : {};
382              
383 2         4 my $data = '';
384              
385 2 50 33     16 if( exists( $track->{'data'} ) and defined( $track->{'data'} ) ) {
386             # It might be 0-length, by the way. Might this be problematic?
387 0         0 $data = $track->{'data'};
388             # warn "Encoding 0-length track data!" unless length $data;
389             } else { # Data is not defined for this track. Parse the events
390 2 50 33     29 if( ($track->{'type'} eq 'MTrk' or length($track->{'type'}) == 0)
      33        
      33        
391             and defined($track->{'events'})
392             # not just exists -- but DEFINED!
393             and ref($track->{'events'})
394             ) {
395 2 50       7 print "Encoding ", $track->{'events'}, "\n" if $Debug;
396             return
397 2         12 &MIDI::Event::encode($track->{'events'}, $options_r );
398             } else {
399 0         0 $data = ''; # what else to do?
400 0 0       0 warn "Spork 8851\n" if $Debug;
401             }
402             }
403 0         0 return \$data;
404             }
405             ###########################################################################
406              
407             # CURRENTLY UNDOCUMENTED -- no end-user ever needs to call this as such
408             #
409             sub decode { # returns a new object, but doesn't accept constructor syntax
410             # decode track data (not a chunk) into a new track object
411             # Calling format:
412             # $new_track =
413             # MIDI::Track::decode($type, \$track_data, { .. options .. })
414             # Returns a new track_object.
415             # The anonymous hash of options is, well, optional
416              
417 17     17 0 87 my $track = MIDI::Track->new();
418              
419 17         398 my ($type, $data_r, $options_r) = @_[0,1,2];
420 17 50       71 $options_r = {} unless ref($options_r) eq 'HASH';
421              
422 17 50       57 die "\$_[0] \"$_[0]\" is not a data reference!"
423             unless ref($_[1]) eq 'SCALAR';
424              
425 17         39 $track->{'type'} = $type;
426 17 50 33     94 if($type eq 'MTrk' and not $options_r->{'no_parse'}) {
427 17         74 $track->{'events'} =
428             &MIDI::Event::decode($data_r, $options_r);
429             # And that's where all the work happens
430             } else {
431 0         0 $track->{'data'} = $$data_r;
432             }
433 17         231 return $track;
434             }
435              
436             ###########################################################################
437              
438             =back
439              
440             =head1 COPYRIGHT
441              
442             Copyright (c) 1998-2002 Sean M. Burke. All rights reserved.
443              
444             This library is free software; you can redistribute it and/or
445             modify it under the same terms as Perl itself.
446              
447             =head1 AUTHOR
448              
449             Sean M. Burke C (until 2010)
450              
451             Darrell Conklin C (from 2010)
452              
453             =cut
454              
455             1;
456              
457             __END__