# 
#
# vocalips2musicxml_01.pl
#
# Rev 0.00    Jan. 2, 2010   by zhuo : initial.
# Rev 0.01    Jan. 7, 2010   by zhuo : "remove-rest shorter than n" option added.
#
# 
# input:    resulting tab-separated file of vocalips 0.42 (in standard mode)
# output:   music xml,  similar to the example on sinsy.jp
#
# usage:
#     perl vocalips2musicxml_00.pl [-b bpm] [-t template] [-r gap] < vocalips_txt > musicxml.xml
#
#     -b bpm  :  Set the BPM to bpm;  120.0 by default.
#     -t template : Use template as the template file, instead of the default
#                   ( musicxml_template.txt ).
#     -r gap : remove the rests shorter than the gap (in second)  by
#               make the preceding note longer.
#     vocalips_txt : the event list output of vocalis, in "-m e2" mode.

$tool_name = "vocalips2musicxml";
$tool_version = "0.01";
$encoding_date = "2010-01-08";



#
# set up params
#
$g_tpq = 480; # fixed; matched to the value in the template
$g_bpm = 120.0;
$g_template_name = "musicxml_template.txt";
$g_rest_gap = 0.0;

while ( 1 ) {
    my $arg = $ARGV[0];
    unless ( $arg =~ /^\-/ ) { last; }
    shift;
    if ( $arg eq "-b" ) {
	$g_bpm = $ARGV[0];
	shift;
	if ( $g_bpm eq "" || ! ($g_bpm =~ /^[0-9]+$/) ) {
	    die "bpm not correct.\n";
	}
	next;
    }
    if ( $arg eq "-t" ) {
	$g_template_name = $ARGV[0];
	shift;
	if ( $g_template_name eq "" || ! (-f $g_template_name ) ) {
	    die "template not specified. \n";
	}
	next;
    }
    if ( $arg eq "-r" ) {
	$g_rest_gap = $ARGV[0];
	shift;
	if ( ! ($g_rest_gap =~ /^[0-9.]+$/) ) {
	    die "the gap must be a number in second.\n";
	}
	next;
    }
}	    
    

#
# Read in the event list from stdin;
# the list is an output of vsqtxt2lipsync_051.pl, run in -e m2 mode,
# and is in the following format (so-called tab-separated text).
#
#  ##	begin	eventlist
#     4.333	     4.667	UTT	95	69	ど	d	o	O	
#     4.667	     5.000	UTT	73	68	こ	k	o	O	
#     5.000	     5.167	UTT	78	69	か	k	a	A	
#     5.167	     5.333	UTT	64	69	あ	 	a	A	
#     5.333	     6.000	UTT	54	66	の	n	o	O	
#     6.000	     7.000	SIL	112	66	や	j	a	M	
#     7.000	     7.167	UTT	112	66	や	j	a	A	
#  ##	end	eventlist


@g_pitchclass = ("C","C","D","D","E","F","F","G","G","A","A","B");
@g_accidental = ( 0,  1,  0,  1,  0,  0,  1,  0,  1,  0,  1,  0 );

sub generate_note {
    my ( $beg, $end, $utt, $note, $lyric ) = @_;

    $text = "<note>\n";
    if ( $utt eq "UTT" ) {
	my ( $oct ) = int(($note-24)/12 );
	my ( $pclass ) = $g_pitchclass[$note % 12];

	$text .= "<pitch>\n";
	$text .= "<step>$pclass</step>\n";
	if ( $g_accidental[$note%12] ) {
	    $text .= "<alter>1</alter>\n";
	}
	$text .= "<octave>$oct</octave>\n";
	$text .= "</pitch>\n";
    } else { # SIL. 
	$text .= "<rest />\n";
    }
    $dur = int( ($end - $beg) * ($g_bpm / 60.0) * $g_tpq );
    $text .= "<duration>$dur</duration>\n";
    $text .= "<voice>1</voice>\n";
    if ( $utt eq "UTT" ) {
	$text .= "<lyric><text>$lyric</text></lyric>\n";
    }
    $text .= "</note>\n";
    return $text;
}



# skip the header part of the input
while (<>) {
    last if ( $_ =~ /^\#\#\tbegin\teventlist/ );
}

# pass 1 :  load eventlist to array, merging a short rest to the preceding notes
$g_events = ();
while ( <> ) {
    last if ( $_ =~ /^\#\#\tend\teventlist/ );
    my ( $beg, $end, $utt, $vel, $note, $lyric, @rest ) = split( /\t/, $_ );
    if ( $utt eq 'SIL' && $end - $beg < $g_rest_gap && $#g_events >= 0 ) {
	$g_events[ $#g_events ]->{'end'} = $end;
	# note: Make the previous utterance longer, by 
	# replacing  the 'end' value of the previous note
	# with that of this rest; This takes place
	# when a very short silence (rest) follows after a note.
    } else { 
	push( @g_events, { 'beg'=>$beg, 'end'=>$end, 'utt'=>$utt, 'note'=>$note, 'lyric'=>$lyric } );
	# note: you can't generate the note text here yet,
	# as the 'end' value may be altered at the next iteration.
    }
}

# pass 2 : generate events,  based on the processed eventlist 
foreach $pe (@g_events) {
    $g_eventlist_text .= &generate_note( $pe->{'beg'}, $pe->{'end'}, $pe->{'utt'}, $pe->{'note'}, $pe->{'lyric'} );
}


# The event part of musicxml has got stored in $musicxml_text.
# Now, let's read the template, replacing the keywords with 
# the contents generated,  and write it out to stdout.


open( MUSICXML_TEMPLATE, $g_template_name ) || die "unable to open $g_template_name. \n";

while ( <MUSICXML_TEMPLATE> ) {
    if ( /\%measures\%/ ) {
	print $g_eventlist_text;
	next;
    }
    s/\%tempo\%/$g_bpm/e;
    print $_;
}

close MUSICXML_TEMPLATE;