File Coverage

blib/lib/MIDI/Score.pm
Criterion Covered Total %
statement 140 153 91.5
branch 40 62 64.5
condition 10 15 66.6
subroutine 10 11 90.9
pod 8 8 100.0
total 208 249 83.5


line stmt bran cond sub pod time code
1             # Time-stamp: "2023-10-11 11:08:01 conklin"
2             require 5;
3             package MIDI::Score;
4 12     12   87 use strict;
  12         143  
  12         426  
5 12     12   58 use vars qw($Debug $VERSION);
  12         21  
  12         758  
6 12     12   59 use Carp;
  12         21  
  12         17267  
7              
8             $VERSION = '0.84';
9              
10             =head1 NAME
11              
12             MIDI::Score - MIDI scores
13              
14             =head1 SYNOPSIS
15              
16             # it's a long story; see below
17              
18             =head1 DESCRIPTION
19              
20             This module provides functions to do with MIDI scores.
21             It is used as the basis for all the functions in MIDI::Simple.
22             (Incidentally, MIDI::Opus's draw() method also uses some of the
23             functions in here.)
24              
25             Whereas the events in a MIDI event structure are items whose timing
26             is expressed in delta-times, the timing of items in a score is
27             expressed as an absolute number of ticks from the track's start time.
28             Moreover, pairs of 'note_on' and 'note_off' events in an event structure
29             are abstracted into a single 'note' item in a score structure.
30              
31             'note' takes the following form:
32              
33             ('note_on', start_time, duration, channel, note, velocity)
34              
35             The problem that score structures are meant to solve is that 1)
36             people definitely don't think in delta-times -- they think in absolute
37             times or in structures based on that (like 'time from start of measure');
38             2) people think in notes, not note_on and note_off events.
39              
40             So, given this event structure:
41              
42             ['text_event', 0, 'www.ely.anglican.org/parishes/camgsm/chimes.html'],
43             ['text_event', 0, 'Lord through this hour/ be Thou our guide'],
44             ['text_event', 0, 'so, by Thy power/ no foot shall slide'],
45             ['patch_change', 0, 1, 8],
46             ['note_on', 0, 1, 25, 96],
47             ['note_off', 96, 0, 1, 0],
48             ['note_on', 0, 1, 29, 96],
49             ['note_off', 96, 0, 1, 0],
50             ['note_on', 0, 1, 27, 96],
51             ['note_off', 96, 0, 1, 0],
52             ['note_on', 0, 1, 20, 96],
53             ['note_off', 192, 0, 1, 0],
54             ['note_on', 0, 1, 25, 96],
55             ['note_off', 96, 0, 1, 0],
56             ['note_on', 0, 1, 27, 96],
57             ['note_off', 96, 0, 1, 0],
58             ['note_on', 0, 1, 29, 96],
59             ['note_off', 96, 0, 1, 0],
60             ['note_on', 0, 1, 25, 96],
61             ['note_off', 192, 0, 1, 0],
62             ['note_on', 0, 1, 29, 96],
63             ['note_off', 96, 0, 1, 0],
64             ['note_on', 0, 1, 25, 96],
65             ['note_off', 96, 0, 1, 0],
66             ['note_on', 0, 1, 27, 96],
67             ['note_off', 96, 0, 1, 0],
68             ['note_on', 0, 1, 20, 96],
69             ['note_off', 192, 0, 1, 0],
70             ['note_on', 0, 1, 20, 96],
71             ['note_off', 96, 0, 1, 0],
72             ['note_on', 0, 1, 27, 96],
73             ['note_off', 96, 0, 1, 0],
74             ['note_on', 0, 1, 29, 96],
75             ['note_off', 96, 0, 1, 0],
76             ['note_on', 0, 1, 25, 96],
77             ['note_off', 192, 0, 1, 0],
78              
79             here is the corresponding score structure:
80              
81             ['text_event', 0, 'www.ely.anglican.org/parishes/camgsm/chimes.html'],
82             ['text_event', 0, 'Lord through this hour/ be Thou our guide'],
83             ['text_event', 0, 'so, by Thy power/ no foot shall slide'],
84             ['patch_change', 0, 1, 8],
85             ['note', 0, 96, 1, 25, 96],
86             ['note', 96, 96, 1, 29, 96],
87             ['note', 192, 96, 1, 27, 96],
88             ['note', 288, 192, 1, 20, 96],
89             ['note', 480, 96, 1, 25, 96],
90             ['note', 576, 96, 1, 27, 96],
91             ['note', 672, 96, 1, 29, 96],
92             ['note', 768, 192, 1, 25, 96],
93             ['note', 960, 96, 1, 29, 96],
94             ['note', 1056, 96, 1, 25, 96],
95             ['note', 1152, 96, 1, 27, 96],
96             ['note', 1248, 192, 1, 20, 96],
97             ['note', 1440, 96, 1, 20, 96],
98             ['note', 1536, 96, 1, 27, 96],
99             ['note', 1632, 96, 1, 29, 96],
100             ['note', 1728, 192, 1, 25, 96]
101              
102             Note also that scores aren't crucially ordered. So this:
103              
104             ['note', 768, 192, 1, 25, 96],
105             ['note', 960, 96, 1, 29, 96],
106             ['note', 1056, 96, 1, 25, 96],
107              
108             means the same thing as:
109              
110             ['note', 960, 96, 1, 29, 96],
111             ['note', 768, 192, 1, 25, 96],
112             ['note', 1056, 96, 1, 25, 96],
113              
114             The only exception to this is in the case of things like:
115              
116             ['patch_change', 200, 2, 15],
117             ['note', 200, 96, 2, 25, 96],
118              
119             where two (or more) score items happen I and where one
120             affects the meaning of the other.
121              
122             =head1 WHAT CAN BE IN A SCORE
123              
124             Besides the new score structure item C (covered above),
125             the possible contents of a score structure can be summarized thus:
126             Whatever can appear in an event structure can appear in a score
127             structure, save that its second parameter denotes not a
128             delta-time in ticks, but instead denotes the absolute number of ticks
129             from the start of the track.
130              
131             To avoid the long periphrase "items in a score structure", I will
132             occasionally refer to items in a score structure as "notes", whether or
133             not they are actually C commands. This leaves "event" to
134             unambiguously denote items in an event structure.
135              
136             These, below, are all the items that can appear in a score.
137             This is basically just a repetition of the table in
138             L, with starttime substituting for dtime --
139             so refer to L for an explanation of what the data types
140             (like "velocity" or "pitch_wheel").
141             As far as order, the first items are generally the most important:
142              
143             =over
144              
145             =item ('note', I, I, I, I, I)
146              
147             =item ('key_after_touch', I, I, I, I)
148              
149             =item ('control_change', I, I, I, I)
150              
151             =item ('patch_change', I, I, I)
152              
153             =item ('channel_after_touch', I, I, I)
154              
155             =item ('pitch_wheel_change', I, I, I)
156              
157             =item ('set_sequence_number', I, I)
158              
159             =item ('text_event', I, I)
160              
161             =item ('copyright_text_event', I, I)
162              
163             =item ('track_name', I, I)
164              
165             =item ('instrument_name', I, I)
166              
167             =item ('lyric', I, I)
168              
169             =item ('marker', I, I)
170              
171             =item ('cue_point', I, I)
172              
173             =item ('text_event_08', I, I)
174              
175             =item ('text_event_09', I, I)
176              
177             =item ('text_event_0a', I, I)
178              
179             =item ('text_event_0b', I, I)
180              
181             =item ('text_event_0c', I, I)
182              
183             =item ('text_event_0d', I, I)
184              
185             =item ('text_event_0e', I, I)
186              
187             =item ('text_event_0f', I, I)
188              
189             =item ('end_track', I)
190              
191             =item ('set_tempo', I, I)
192              
193             =item ('smpte_offset', I, I
, I, I, I, I)
194              
195             =item ('time_signature', I, I, I
, I, I)
196              
197             =item ('key_signature', I, I, I)
198              
199             =item ('sequencer_specific', I, I)
200              
201             =item ('raw_meta_event', I, I(0-255), I)
202              
203             =item ('sysex_f0', I, I)
204              
205             =item ('sysex_f7', I, I)
206              
207             =item ('song_position', I)
208              
209             =item ('song_select', I, I)
210              
211             =item ('tune_request', I)
212              
213             =item ('raw_data', I, I)
214              
215             =back
216              
217              
218             =head1 FUNCTIONS
219              
220             This module provides these functions:
221              
222             =over
223              
224             =item $score2_r = MIDI::Score::copy_structure($score_r)
225              
226             This takes a I to a score structure, and returns a
227             I to a copy of it. Example usage:
228              
229             @new_score = @{ MIDI::Score::copy_structure( \@old_score ) };
230              
231             =cut
232              
233             sub copy_structure {
234 0     0 1 0 return &MIDI::Event::copy_structure(@_);
235             # hey, a LoL is an LoL
236             }
237             ##########################################################################
238              
239             =item $events_r = MIDI::Score::score_r_to_events_r( $score_r )
240              
241             =item ($events_r, $ticks) = MIDI::Score::score_r_to_events_r( $score_r )
242              
243             This takes a I to a score structure, and converts it to an
244             event structure, which it returns a I to. In list context,
245             also returns a second value, a count of the number of ticks that
246             structure takes to play (i.e., the end-time of the temporally last
247             item).
248              
249             =cut
250              
251             sub score_r_to_events_r {
252             # list context: Returns the events_r AND the total tick time
253             # scalar context: Returns events_r
254 5     5 1 310 my $score_r = $_[0];
255 5         10 my $time = 0;
256 5         12 my @events = ();
257 5 50       16 croak "MIDI::Score::score_r_to_events_r's first arg must be a listref"
258             unless ref($score_r);
259              
260             # First, turn instances of 'note' into 'note_on' and 'note_off':
261 5         17 foreach my $note_r (@$score_r) {
262 98 50       141 next unless ref $note_r;
263 98 100       137 if($note_r->[0] eq 'note') {
264 48         88 my @note_on = @$note_r;
265             #print "In: ", map("<$_>", @note_on), "\n";
266 48         55 $note_on[0] = 'note_on';
267 48         57 my $duration = splice(@note_on, 2, 1);
268              
269 48         96 my @note_off = @note_on; # /now/ copy it
270 48         50 $note_off[0] = 'note_off';
271 48         49 $note_off[1] += $duration;
272 48         49 $note_off[4] = 0; # set volume to 0
273 48         107 push(@events, \@note_on, \@note_off);
274             #print "on: ", map("<$_>", @note_on), "\n";
275             #print "off: ", map("<$_>", @note_off), "\n";
276             } else {
277 50         90 push(@events, [@$note_r]);
278             }
279             }
280             # warn scalar(@events), " events in $score_r";
281 5         15 $score_r = sort_score_r(\@events);
282             # warn scalar(@$score_r), " events in $score_r";
283              
284             # Now we turn it into an event structure by fiddling the timing
285 5         15 $time = 0;
286 5         21 foreach my $event (@$score_r) {
287 146 50 33     326 next unless ref($event) && @$event;
288 146         155 my $delta = $event->[1] - $time; # Figure out the delta
289 146         150 $time = $event->[1]; # Move it forward
290 146         168 $event->[1] = $delta; # Swap it in
291             }
292 5 100       16 return($score_r, $time) if wantarray;
293 4         26 return $score_r;
294             }
295             ###########################################################################
296              
297             =item $score2_r = MIDI::Score::sort_score_r( $score_r)
298              
299             This takes a I to a score structure, and returns a
300             I to a sorted (by time) copy of it. Example usage:
301              
302             @sorted_score = @{ MIDI::Score::sort_score_r( \@old_score ) };
303              
304             =cut
305              
306             sub sort_score_r {
307             # take a reference to a score LoL, and sort it by note start time,
308             # and return a reference to that sorted LoL. Notes from the same
309             # time must be left in the order they're found!!!! That's why we can't
310             # just use sort { $a->[1] <=> $b->[1] } (@$score_r)
311 6     6 1 11 my $score_r = $_[0];
312 6         10 my %timing = ();
313 6         9 foreach my $note_r (@$score_r) {
314             push(
315 173 50       237 @{$timing{
316 173         325 $note_r->[1]
317             }},
318             $note_r
319             ) if ref($note_r);
320             }
321             # warn scalar(@$score_r), " events in $score_r";
322             #print "sequencing for times: ", map("<$_> ",
323             # sort {$a <=> $b} keys(%timing)
324             # ), "\n";
325              
326             return
327             [
328 82         176 map(@{ $timing{$_} },
329 6         34 sort {$a <=> $b} keys(%timing)
  248         267  
330             )
331             ];
332             }
333             ###########################################################################
334              
335             =item $score_r = MIDI::Score::events_r_to_score_r( $events_r )
336              
337             =item ($score_r, $ticks) = MIDI::Score::events_r_to_score_r( $events_r )
338              
339             This takes a I to an event structure, converts it to a
340             score structure, which it returns a I to. If called in
341             list context, also returns a count of the number of ticks that
342             structure takes to play (i.e., the end-time of the temporally last
343             item).
344              
345             =cut
346              
347             sub events_r_to_score_r {
348             # Returns the score_r AND the total tick time
349 10     10 1 13 my $events_r = $_[0];
350 10 50       24 croak "first argument to MIDI::Score::events_to_score is not a listref!"
351             unless $events_r;
352 10 50       34 my $options_r = ref($_[1]) ? $_[1] : {};
353              
354 10         17 my $time = 0;
355 10 50       22 if( $options_r->{'no_note_abstraction'} ) {
356 0         0 my $score_r = MIDI::Event::copy_structure($events_r);
357 0         0 foreach my $event_r (@$score_r) {
358             # print join(' ', @$event_r), "\n";
359 0 0       0 $event_r->[1] = ($time += $event_r->[1]) if ref($event_r);
360             }
361 0 0       0 return($score_r, $time) if wantarray;
362 0         0 return $score_r;
363             } else {
364 10         16 my %note = ();
365             my @score =
366             map
367             {
368 10 50       23 if(!ref($_)) {
  207         270  
369 0         0 ();
370             } else {
371             # 0.82: the following must be declared local
372 207         314 local $_ = [@$_]; # copy.
373 207 50       347 $_->[1] = ($time += $_->[1]) if ref($_);
374            
375 207 100 100     577 if($_->[0] eq 'note_off'
    100 100        
376             or($_->[0] eq 'note_on' &&
377             $_->[4] == 0) )
378             { # End of a note
379             # print "Note off : @$_\n";
380             # 0.82: handle multiple prior events with same chan/note.
381 58 50 33     66 if ((exists $note{pack 'CC', @{$_}[2,3]}) && (@{$note{pack 'CC', @{$_}[2,3]}})) {
  58         122  
  58         67  
  58         134  
382 58         62 shift(@{$note{pack 'CC', @{$_}[2,3]}})->[2] += $time;
  58         57  
  58         96  
383 58 100       67 unless(@{$note{pack 'CC', @{$_}[2,3]}}) {delete $note{pack 'CC', @{$_}[2,3]};}
  58         61  
  58         106  
  57         61  
  57         94  
384             }
385 58         86 (); # Erase this event.
386             } elsif ($_->[0] eq 'note_on') {
387             # Start of a note
388 58         100 $_ = [@$_];
389            
390 58         63 push(@{$note{ pack 'CC', @{$_}[2,3] }},$_);
  58         59  
  58         133  
391 58         94 splice(@$_, 2, 0, -$time);
392 58         68 $_->[0] = 'note';
393             # ('note', Starttime, Duration, Channel, Note, Veloc)
394 58         76 $_;
395             } else {
396 91         129 $_;
397             }
398             }
399             }
400             @$events_r
401             ;
402              
403             #print "notes remaining on stack: ", scalar(values %note), "\n"
404             # if values %note;
405             # 0.82: clean up pending events gracefully
406 10         40 foreach my $k (keys %note) {
407 0         0 foreach my $one (@{$note{$k}}) {
  0         0  
408 0         0 $one->[2] += $time;
409             }
410             }
411 10 50       23 return(\@score, $time) if wantarray;
412 10         41 return \@score;
413             }
414             }
415             ###########################################################################
416              
417             =item $ticks = MIDI::Score::score_r_time( $score_r )
418              
419             This takes a I to a score structure, and returns
420             a count of the number of ticks that structure takes to play
421             (i.e., the end-time of the temporally last item).
422              
423             =cut
424              
425             sub score_r_time {
426             # returns the duration of the score you pass a reference to
427 2     2 1 9 my $score_r = $_[0];
428 2 50       7 croak "arg 1 of MIDI::Score::score_r_time isn't a ref" unless ref $score_r;
429 2         4 my $track_time = 0;
430 2         15 foreach my $event_r (@$score_r) {
431 31 50       46 next unless @$event_r;
432 31 100       45 my $event_end_time = ($event_r->[0] eq 'note') ?
433             ($event_r->[1] + $event_r->[2]) : $event_r->[1] ;
434             #print "event_end_time: $event_end_time\n";
435 31 100       47 $track_time = $event_end_time if $event_end_time > $track_time;
436             }
437 2         9 return $track_time;
438             }
439             ###########################################################################
440              
441             =item MIDI::Score::dump_score( $score_r )
442              
443             This dumps (via C) a text representation of the contents of
444             the event structure you pass a reference to.
445              
446             =cut
447              
448             sub dump_score {
449 2     2 1 9 my $score_r = $_[0];
450 2         340 print "\@notes = ( # ", scalar(@$score_r), " notes...\n";
451 2         9 foreach my $note_r (@$score_r) {
452 27 50       136 print " [", &MIDI::_dump_quote(@$note_r), "],\n" if @$note_r;
453             }
454 2         160 print ");\n";
455 2         10 return;
456             }
457              
458             ###########################################################################
459              
460             =item MIDI::Score::quantize( $score_r )
461              
462             This takes a I to a score structure, performs a grid
463             quantize on all events, returning a new score reference with new
464             quantized events. Two parameters to the method are: 'grid': the
465             quantization grid, and 'durations': whether or not to also quantize
466             event durations (default off).
467              
468             When durations of note events are quantized, they can get 0 duration.
469             These events are I from the returned score, and it is the
470             responsibility of the caller to deal with them.
471              
472             =cut
473              
474             # new in 0.82!
475             sub quantize {
476 1     1 1 2 my $score_r = $_[0];
477 1 50       3 my $options_r = ref($_[1]) eq 'HASH' ? $_[1] : {};
478 1         2 my $grid = $options_r->{grid};
479 1 50       3 if ($grid < 1) {carp "bad grid $grid in MIDI::Score::quantize!"; $grid = 1;}
  0         0  
  0         0  
480 1         2 my $qd = $options_r->{durations}; # quantize durations?
481 1         2 my $new_score_r = [];
482 1         2 my $n_event_r;
483 1         1 foreach my $event_r (@{$score_r}) {
  1         2  
484 25         28 my $n_event_r = [];
485 25         23 @{$n_event_r} = @{$event_r};
  25         44  
  25         26  
486 25         41 $n_event_r->[1] = $grid * int(($n_event_r->[1] / $grid) + 0.5);
487 25 100 66     60 if ($qd && $n_event_r->[0] eq 'note') {
488 20         27 $n_event_r->[2] = $grid * int(($n_event_r->[2] / $grid) + 0.5);
489             }
490 25         22 push @{$new_score_r}, $n_event_r;
  25         36  
491             }
492 1         3 $new_score_r;
493             }
494              
495             ###########################################################################
496              
497             =item MIDI::Score::skyline( $score_r )
498              
499             This takes a I to a score structure, performs skyline
500             (create a monophonic track by extracting the event with highest pitch
501             at unique onset times) on the score, returning a new score reference.
502             The parameters to the method is: 'clip': whether durations of events
503             are preserved or possibly clipped and modified.
504              
505             To explain this, consider the following (from Bach 2 part invention
506             no.6 in E major):
507              
508             |------e------|-------ds--------|-------d------|...
509             |****--E-----|-------Fs-------|------Gs-----|...
510              
511             Without duration cliping, the skyline is E, Fs, Gs...
512              
513             With duration clipping, the skyline is E, e, ds, d..., where the
514             duration of E is clipped to just the * portion above
515              
516             =cut
517              
518             sub skyline {
519 1     1 1 2 my $score_r = $_[0];
520 1 50       4 my $options_r = ref($_[1]) eq 'HASH' ? $_[1] : {};
521 1         2 my $clip = $options_r->{clip};
522 1         2 my $new_score_r = [];
523 1         2 my %events = ();
524 1         1 my $n_event_r;
525 1         3 my ($typeidx,$stidx,$duridx,$pitchidx) = (0,1,2,4); # create some nicer event indices
526             # gather all note events into an onset-index hash. push all others directly into the new score.
527 1         2 foreach my $event_r (@{$score_r}) {
  1         2  
528 27 100       41 if ($event_r->[$typeidx] eq "note") {push @{$events{$event_r->[$stidx]}}, $event_r;}
  6         5  
  6         12  
529 21         22 else {push @{$new_score_r}, $event_r;}
  21         24  
530             }
531 1         2 my $loff = 0; my $lev = [];
  1         2  
532             # iterate over increasing onsets
533 1         5 foreach my $onset (sort {$a<=>$b} (keys %events)) {
  11         23  
534             # find highest pitch at this onset
535 6         6 my $ev = (sort {$b->[$pitchidx] <=> $a->[$pitchidx]} (@{$events{$onset}}))[0];
  0         0  
  6         9  
536 6 100       12 if ($onset >= ($lev->[$stidx] + $lev->[$duridx])) {
    50          
537 3         3 push @{$new_score_r}, $ev;
  3         5  
538 3         4 $lev = $ev;
539             }
540             elsif ($clip) {
541 3 100       7 if ($ev->[$pitchidx] > $lev->[$pitchidx]) {
542 1         2 $lev->[$duridx] = $ev->[$stidx] - $lev->[$stidx];
543 1         2 push @{$new_score_r}, $ev;
  1         2  
544 1         1 $lev = $ev;
545             }
546             }
547             }
548 1         11 $new_score_r;
549             }
550              
551             ###########################################################################
552              
553             =back
554              
555             =head1 COPYRIGHT
556              
557             Copyright (c) 1998-2002 Sean M. Burke. All rights reserved.
558              
559             This library is free software; you can redistribute it and/or
560             modify it under the same terms as Perl itself.
561              
562             =head1 AUTHORS
563              
564             Sean M. Burke C (until 2010)
565              
566             Darrell Conklin C (from 2010)
567              
568             =cut
569              
570             1;
571              
572             __END__