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