# $Id: Conductor.pm,v 1.3 2004/12/11 16:24:23 root Exp $

package POE::Framework::MIDI::Conductor;

use strict;
use vars '$VERSION'; $VERSION = '0.02';

sub new {
    my ( $self, $class ) = ({}, shift);
    $self->{cfg} = shift or die "I need a config hashref to start the conductor.";
    bless( $self,  $class );
    $self->{bars} = {};
    return $self;
}

sub musician_names {
    my $self = shift;
    my $musicians = $self->{cfg}->{musicians} or die "no musicians defined in config";
    my $to_return;
    for (@$musicians) {
        push @$to_return, $_->{name};
    }
    return $to_return;
}

# register a bar/phrase in the internal datastructure
sub add_bar {
    my $self 			= shift;
    my $params 	= shift;
    die "Malformed add_bar data - " . Dumper($params) . "\n\n"
    	unless ($params->{musician_name} and $params->{barnum} and $params->{bar});
    die "$params->{bar} isn't a P:F:M:Bar object"
    	unless (ref($params->{bar}) =~ /POE::Framework::MIDI::Bar/);
    $self->{bars}->{$params->{musician_name}}->{$params->{barnum}} = $params->{bar};
}


sub bars {
	my $self = shift;
	return $self->{bars};
}

# convert event objects (note, rest, bar, etc) to perl code for
# midi simple.   this gets pretty ugly.
sub render {
    my ( $self,  $filename ) = @_ or die "render needs a filename to render to";
    my $musicians 	= $self->{cfg}->{musicians};
    my $perlcode 	= $self->perl_head;
    
    for my $musician (@$musicians) {
        my $events 	= $self->{bars}->{$musician->{name}};
        my $musician_perlcode = $self->musician_subroutine_header($musician);
        my $channel 	= $musician->{channel};
        my $patch 		= $musician->{patch};        
        
        for my $event (
            sort { $events->{$a}->number <=> $events->{$b}->number }
                keys %$events
        ) {
            # $this_event is a bar, a phrase or maybe a noop
            my $this_event = $events->{$event};
                            
            # we need the events out of the bar or phrase
            my $eventstack 	= $this_event->events;
            my $printwrap 	= 5; # how many events to print per line..so we can debug later
            my $wrap;

            for my $stackitem (@$eventstack) {
                ++$wrap;
                 $musician_perlcode .= $self->perl_from_event(
                	event => $stackitem,
                	musician_name => $musician->{name}) .'; ';
                if ($wrap >= $printwrap) {
                    $musician_perlcode .= "\n\t";
                    $wrap = 0;    
                }
            }
        }
        
        $perlcode .= $musician_perlcode . "\n}";                    
    }

    if ($self->{cfg}->{debug}) {
        open DEBUG, ">debug.perl" or die "Can't open debug.perl for writing - $!\n";
        print DEBUG $perlcode;
        close DEBUG;
    }

    eval "$perlcode" or
        die "uh oh - bad stuff happened during rendering: " . join "\n", $@;
}

# header chunk of perl code for eventual eval
sub perl_head {
    my $self 		= shift;
    my $now  	= scalar(localtime);
    my $code 	= qq!
# magically generated by $0 - $now
    
use MIDI::Simple;
no strict 'subs';
new_score;
set_tempo 500000;

synch(! ;    
    # add the subroutine refs
    for (@{$self->{cfg}->{musicians}}) {
        $code .= "\\&$_->{name},";
    }
    $code =~ s/\,$/)\;\n\n/;

    $code .= "\nwrite_score '$self->{cfg}->{filename}';\n\n";
    return $code;
}

# create perlcode from an event for eventual
# eval to MIDI::Simple code.  
sub perl_from_event {
	my $self = shift;
	#my $p = shift;
	my %params = @_;	
  	die "perl from event needs an event, and a musician_name: \n\n"
  		unless ($params{event} and $params{musician_name});

    # what type of event do we have? 

    # should MIDI::Bar recursively call this sub for notes and rests, or keep
    # the seperate code? 
    my $eventname = ref $params{event};

    if ($eventname eq 'POE::Framework::MIDI::Bar') {
        my $stack = $params{event}->events; # get the bar's eventstack

        for my $stackitem (@$stack) {
            if ($eventname eq 'POE::Framework::MIDI::Note') {
                my $duration 	= $stackitem->duration;
                my $note     	= $stackitem->note;
                # n wn, Cs4
                return "n $duration, $note";
            }
            elsif ($eventname eq 'POE::Framework::MIDI::Rest') {
                my $duration = $stackitem->duration;
                return "r $duration";
            }
        }
    }
    elsif ($eventname eq 'POE::Framework::MIDI::Note') {
        my $duration 		= $params{event}->duration;
        my $note     		= $params{event}->note;
        # n wn, Cs4
        return "n $duration, $note";
    }
    elsif ($eventname eq 'POE::Framework::MIDI::Rest') {
        my $duration = $params{event}->duration;
        return "r $duration";
    }
    elsif($eventname eq 'POE::Framework::MIDI::Interval') {
    	my $duration 		= $params{event}->duration;
    	my $notes 			= $params{event}->notes;
    	return "n $duration, " . join ",", @$notes;    	
    }
 
    else {
        warn "Unhandled event type $params{event} ignored";
    }
}

# the perl code to start a musician subroutine for MIDI::Simplels
# 'synch' function
sub musician_subroutine_header {
    my ($self,$musician) = @_;
    my ($name,$channel,$patch) = ($musician->{name}, $musician->{channel},$musician->{patch});
    
    return qq!  
sub $name {
    noop c$channel, f, o4;
    patch_change $channel, $patch;
    #instrument_name $name;

    !;
}

# eventually this should evolve into a way for musicians or $self to query about
# what any musician played in any bar for follow the leader-type-stuff, and so on.
sub query {
    my $self = shift;
    my $querystring = shift or die  __PACKAGE__ . "query needs a querystring";
    print "query $querystring\n";
}

1;

__END__

=head1 NAME 

POE::Framework::MIDI::Conductor - Algorithmic composition using POE

=head1 SYNOPSIS

This package is used by the POEConductor module internally.

=head1 ABSTRACT

Algorithmic composition using POE

=head1 DESCRIPTION

The Conductor object creates Musician objects which generate MIDI 
event streams and run them through rules and transformations.  The 
Conductor then renders the events down to perl code that MIDI::Simple 
can handle, and then runs that code to write a .mid file

=head1 SEE ALSO

L<POE>

L<http://justsomeguy.com/code/POE/POE-Framework-MIDI>

=head1 AUTHOR

Primary: Steve McNabb E<lt>steve@justsomeguy.comE<gt>

CPAN ID: SMCNABB

Secondary: Gene Boggs E<lt>cpan@ology.netE<gt>

CPAN ID: GENE

=head1 COPYRIGHT AND LICENCE

Copyright (c) 2002 Steve McNabb. All rights reserved.
This program is free software; you can redistribute it and/or modify 
it under the same terms as Perl itself.

The full text of the license can be found in the LICENSE file 
included with this module.

=cut
