MIDI::Event callbacks; MIDI::Opus methods

[email protected] ("Sean M. Burke") Wed, 25 Oct 2000 20:33:22 -0600
Newsgroups perl.midi
Message-ID <[email protected]>
At 09:47 AM 2000-10-25 -0400, you wrote:
>[...]
>Your docs say "So if you want to alter the event stream on the way to the
>event structure (which counts as deep voodoo), define 'event_callback' and
>have it modify its @_. "  But that doesn't actually, work, I don't think?
>MIDI/Event.pm just calls $event_callback() passing in @E (by value, not by
>reference), and it ignores any return value.  So the event callback can't
>change the event.  That's sort of too bad!

I think that when I wrote that, I was under the impression (which I swear I
arrived at by actual experimentation, but it must have been ages ago and
with a buggy Perl 4 or something) that if you called a sub as:
  foo(@array);
Then foo's @_ would actually be an alias to @array (instead of the normal
case of merely having @_'s elements be aliases to the elements of the
parameter list).
In other words, I thought one say @_ = (...new things...) and thereby
change @array.

But that's not the way it is.  @_'s elements are aliased to the elements of
the parameter list, but that's it.

So my callback mechanism isn't very expressive.

Currently, I have:
     if(@E){
	if( $exclusive_event_callback ) {
	  &{ $exclusive_event_callback }( @E );
	} else {
	  &{ $event_callback }( @E ) if $event_callback;
	  push(@events, [ @E ]);
	}
     }

>Might I suggest something like:
>
>   if(@E){
>     if( $exclusive_event_callback ) {
>       &{ $exclusive_event_callback }( @E );
>     } elsif ($event_callback) {
>        my $replacement = &{ $event_callback }( @E );
>       @E = @$replacement if isref $replacement;
>       push(@events, [ @E ]);
>     }
>   }
>
>(and the corresponding change to the docs), which would
>allow an event_callback to return a reference to a
>replacement event-list, without (I think!) breaking any
>uses of the current readonly callback?  (Note that
>I just typed in the above, and didn't test it at all.)

Well, if I had it to do over, I'd probably have:

     if(@E){
	if( $exclusive_event_callback ) {
	  &{ $exclusive_event_callback }( @E );
	} else {
         if($event_callback) {
           push(@events, &{ $event_callback }( @E );
         } else {
           push(@events, \@E);
         }
	}
     }

But if I did that now, that would break any existing code that uses the
'event_callback' option to, say, merely alter the channels of events (by
altering $_[2] -- well, for those events that have channels in their
parameters).  But it /would/ allow the callback to turn @E into no events,
one event, or any number of events.

But I think I'll just err toward breaking nothing by:
1) declaring the current behavior a feature (if a crippled one),
2) just adding a new kind of callback, event_callback_deluxe, that can
  do damned near anything:

     if(@E){
	if( $exclusive_event_callback ) {
	  &{ $exclusive_event_callback }( @E );
	} elsif($event_callback_deluxe) {
         push(@events, &{ $event_callback_deluxe }(\@events, $eot, \@E);
          # so that an abort-track-parse can be signalled
          #  by setting $_[1] = 1;
       } else {
         &{ $event_callback }( @E );
         push(@events, \@E) if @E;
          # so that event_callback can, at most, kill this event.
	}
     }

>Also, I'd like to have a method of MIDI::Opus that returned the data for
>the MIDIfile (rather than writing it to a handle or a file).  This would be
>useful when you need the data (and/or its length) for something else, like
>embedding into a RIFF/rmi file or returning from a CGI or whatever.  Should
>be simple to write, and then write_to_handle could just use it (I'd be glad
>to draft the mods and send them to you if you'd like).

1) in principle, a good idea.

2) having write_to_handle use it is a bad idea -- it'd mean /always having/
to create an scalar in memory the size of the whole MIDI file, and then
dumping it.  Currently, one creates only a scalar as big as each track chunk.

However, I think there's no harm in making a write_to_scalar that takes:
 $opus->write_to_scalar(SCALARREF, { ...options...} )
but just not having any of the other methods use it.  It'd basically be
just the current write_to_handle, but with
 $$scalar_r .= join '', ...
replacing
  print $fh ...

So I think I'll do that.  That's easy.

3) If ever you need a module to do something like this (i.e., do to a
string what it expects to do only to a filehandle), then I think there's a
class -- IO::Stringy? -- that does that -- you give it a string, and it
makes an object that manipulates that string as if it were a file,
presenting a filehandle's usual interface methods; then you pass that
around to whatever class that expects a FH.  I've never used it myself, but
I suggest looking at it.  (Or IO::Stringy might present a string interface
to a FH, in which case what I'm thinking of is another module entirely,
hopefully not one confined to my imagination.)

4) For a CGI, I think what you're dancing around is this:

  print "Content-type: audio/x-midi\n\n"; # or whatever
  $opus->write_to_handle(*STDOUT);

5) I'm now also considering how a read_from_string might be nice to have.
I'll throw that in, too.

I won't go making the above changes immediately.  I figure I'll give the
MIDI-Perl list a week or two to think about this, and make any comments.

That'll also give me a little time to puzzle over some optimizations I've
got in mind now for the encode/decode loops in Events; to say nothing of
finishing my next TPJ article, proofing a friend's upcoming Perl book, and
finally fixing Locale::Maketext, which has been wilting from neglect.


By the way, I bet the TPJ editors would like it if someone wrote a piece or
two about using MIDI-Perl to do, hell, anything.  My TPJ13 article on
MIDI::Simple is hardly the last word possible on the subject of music and
Perl.

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