Re: MIDI

[email protected] ("Sean M. Burke") Tue, 12 Dec 2000 22:23:34 -0700
Newsgroups perl.midi
Message-ID <[email protected]>
At 11:18 PM 2000-12-10 +0100, Elettrone Gravitante wrote:

>>What sorts of changes are you planning to make between the source file
>>and the new file?  That would probably dictate what kind of data
>>structure to use.
>[...]I think that use MIDI::Score data structure is the best
>solution but, how can I import a score from a Midi file?[...]

Hm, I don't completely understand the transform you want to apply to
notes, altho I understand that it's a function of each note's
note-number and start-time.

I've written some example code that modifies a score structure's
notes' start-times and note-numbers; hopefully you can make sense
of it, put your transform in place of mine.
I don't get asked that much about modifying MIDI files, so maybe the
MIDI Perl list'd like to see this.  I'll copy them on this message.



OK, first to make a MIDI file, so we all know what we're talking about:

use MIDI::Simple;
new_score;
patch_change 1, 8;  # set Channel 1 to Patch 8 = Celesta
noop c1, f, o5;  # Setup
# Now play
n qn, Cs;    n F;   n Ds;  n hn, Gs_d1;
n qn, Cs;    n Ds;  n F;   n hn, Cs;
n qn, F;     n Cs;  n Ds;  n hn, Gs_d1;
n qn, Gs_d1; n Ds;  n F;   n hn, Cs;
write_score 'c1.mid';
# that's the Westminster Chimes.  Tired of it yet?  SING ALONG!
#  Lord through this hour/ be Thou our guide
#  so, by Thy power/ no foot shall slide



Now to take an arbitrary track (well, the one track from the
file that the above program generated), and mess with it:

##### New program #####

use strict;
use MIDI;
my $o = MIDI::Opus->new(
  { 'from_file' => 'c1.mid' } # read in whatever file
);

my(@t) = $o->tracks;
die "Wrong number of tracks (" . scalar(@t) . ")" unless @t == 1;
  # For example's sake, deal with only one track.  In practice, tho,
  # we could iterate over each track, changing each.

my $t = $t[0];
my $score =
  MIDI::Score::events_r_to_score_r( $t->events_r );

# And if you wanna look at it:
MIDI::Score::dump_score( $score );
#...That'll print:
# @notes = (   # 18 notes...
#  ['text_event', 0, 'make1.pl at Tue Dec 12 21:05:52 2000'],
#  ['patch_change', 0, 1, 8],
#  ['note', 0, 96, 1, 61, 96],
#  ['note', 96, 96, 1, 65, 96],
#  ['note', 192, 96, 1, 63, 96],
#  ['note', 288, 192, 1, 56, 96],
#  ['note', 480, 96, 1, 61, 96],
#  ['note', 576, 96, 1, 63, 96],
#  ['note', 672, 96, 1, 65, 96],
#  ['note', 768, 192, 1, 61, 96],
#  ['note', 960, 96, 1, 65, 96],
#  ['note', 1056, 96, 1, 61, 96],
#  ['note', 1152, 96, 1, 63, 96],
#  ['note', 1248, 192, 1, 56, 96],
#  ['note', 1440, 96, 1, 56, 96],
#  ['note', 1536, 96, 1, 63, 96],
#  ['note', 1632, 96, 1, 65, 96],
#  ['note', 1728, 192, 1, 61, 96],
# );

# See MIDI::Score for the info on score structures.
# The short story, tho, is:
# ('note', starttime, duration, channel, note, velocity) 
#   0       1          2         3        4     5

my @more;

foreach my $n (@$score) {
  next unless $n->[0] eq 'note'; # skip non-notes.

  # Change the note-number somehow.  For example:

  $n->[4] += 10; # raise 5 steps

  # Change the time somehow.  For example:

  $n->[1] = int( ($n->[1] ** 1.5) / 50 );
    # molto allegro, ma rallentando presto

  # Or even make more notes -- just as long as 
  #  we don't change the array we're in the middle of
  #  iterating over:
  push @more, [@$n]; # make a copy of that note
  # and alter it...
  $more[-1][4] += 3 + int(rand 1.4); # note-number
  $more[-1][1] += 45 - int(rand 90); # and start-time
  $more[-1][1] = 0 if $more[-1][1] < 0;
   # Having a negative start-time will be a fatal error
   #  later on.  If you can't just force things to 0,
   #  then figure out the lowest negative number (in the
   #  track? in the file?), then go and subtract that much
   #  from all the notes' start time -- i.e., moving everything
   #  up, so the earliest thing starts now at time 0.
}

push @$score, @more; # if you made additional notes


print "\n\nScore after change:\n";
MIDI::Score::dump_score( $score );
# Yes, it'll be a bit out-of-order.  But score_r_to_events_r
#  will still make good sense of it.


# Now, make an events structure, and put it back in the track
# we got it from.
$t->events_r( 
  scalar MIDI::Score::score_r_to_events_r( $score )
);

# Now write out the opus containing that track.
$o->write_to_file('c2.mid');
exit;


--
Sean M. Burke  [email protected]  http://www.spinn.net/~sburke/