#!/usr/bin/perl
# mlf2target.pl
#
# Convert MLF file format into target file for a neural network.
#
# Usage text (printed if timit2lm.pl is called with no arguments):
$USAGE=<<ENDUSAGE;

mlf2target.pl: Convert MLF file format into target file for a neural network.

Usage:
  mlf2target.pl <mlf> <dir> <rate> ...
Example: 
  mlf2target.pl TRAIN_LM.mlf TRAIN_LM_targets 0.01

For each section <file>.lab in an input MLF, this script creates an
output neural-network target specification file <dir>/<file>.tgt.  

The neural network target specification contains N+1 columns and M+1
rows: N is the number of unique labels used in the MLF, and
M=T/<rate>, where T is the largest time (in seconds) specified in the
section <file>.lab (conversion from MLF units to seconds is done
automatically, assuming 1e7 MLF units per second).

The first row provides column labels: first column is labeled "time,"
remaining columns are labeled with the corresponding MLF label string.
Remaining rows contain data in the following format.  In each data
row, the first column specifies the time alignment of the row in
seconds; time alignments are in steps of <rate> seconds.  Each
remaining column is set to 1 if the column label is active at the row
time, and 0 otherwise.

WARNING: don't try this script using a word transcription or triphone
transcription as input.  You will wind up with TGT files containing
thousands of columns.  This script is intended for inputs with a
handful of distinct labels, e.g., pitch accent types, acoustic
landmark types, or perhaps monophones.

ENDUSAGE

####################################################################
# GLOBAL VARIABLES

# Conversion rates for various file formats
$MLFPERIOD = 0.0000001;

# Math functions
sub abs { my($x)=shift; ($x>0) ? $x : -$x ; }
sub round { $_[0] =~ /([^\.]*)(.*)/; ($2 >= 0.5) ? $1+1 : $1; }


###################################################
# Subroutine to read MLF-format transcription file
#
# Syntax: $data = read_MLF_file("filename");
#
# Returned value $data is a reference to a hash.
# Hash keys are strings: roots of filenames given in the MLF file.
# Hash values are references to phn-structure arrays, i.e.
#   arrayrefs of the form returned by read_NIST_file or read_ALA_file:
#   Each element of the arrayref is a subarrayref containing start time,
#   end time, and labels.
# $$data{$filename}[0][0] is starttime, $$data{$filename}[0][1] is endtime,
# @{$$data{$filename}[0]}[2..$#{$$data{$filename}[0]}] is the array
# slice containing all remaining labels 
#
sub read_MLF_file {
  # Initialize %MLF to null
  my(%MLF)  = ();

  print "Reading from $_[0]\n";

  # Open the INPUTFILE or die with an error message
  open(INPUTFILE,$_[0]) || die "Unable to open MLF file $_[0]: $!";

  # Read in all lines from the INPUTFILE
  while (<INPUTFILE>) {
    # If this line starts a transcription, read it
    if (/\".*\/([^\/\.]+)[^\/]*\"/) {
      my($filename) = $1;
      $MLF{$filename} = read_MLF_entry($filename, \*INPUTFILE);
    }
  }
  close(INPUTFILE);

  # Return a reference to the MLF structure
  return(\%MLF);
}
  
###################################################
# Subroutine to read MLF entry
#
# Syntax: $data = read_MLF_entry("filename", \*INPUTFILE);
#
# Returned value $data is an arrayref of arrayrefs
# $$tran[0][0] is first seg start time, $$tran[0][1] is end time,
# @$$tran[0][2..$#{$$tran[0]}] is the sequence of labels at first time.
#
sub read_MLF_entry {
  my($filename) = $_[0];
  my($fh) = $_[1];
  my(@tran) = ();
      
  # Read all consecutive legible transcription lines
  for($l=<$fh>; $l=~/(\d+)\s+(\d+)\s+(.*)/; $l=<$fh>) {
    my(@labels) = split(/\s+/,$3);
    # Push a reference to @record onto the @tran array
    push(@tran, [ $1 * $MLFPERIOD, $2 * $MLFPERIOD, @labels ] );
  }
  return(\@tran);
}

###################################################
# Print phoneme data to an MLF file
#
# Syntax: print_MLF_entry("root_filename", \@phndata, $filehandleref);
#
# The output filehandle MLF must be globally defined
# and already open.
#
sub print_MLF_entry {
  my($filename) = $_[0];
  my($phndata) = $_[1];
  my($fh) = $_[2];

  # Print the filename to the MLF
  $filename =~ s:.*/::g;
  $filename =~ s/\..*//g;
  print $fh "\"*/$filename\.lab\"\n";

  # Print out the converted data into MLF, and to the label file
  foreach $r (@$phndata) {
    my(@l) = @$r[2..$#$r];
    print $fh sprintf("%d %d ",round($$r[0]/$MLFPERIOD),
		      round($$r[1]/$MLFPERIOD)),"@l\n";
  }
  # Print final period that separates sentences
  print $fh '.',"\n";
}


################################################################
# Main program

# Print usage if too few arguments specified
if (@ARGV < 3) { print $USAGE; exit; }

# Define names for each argv
$MLF_file = $ARGV[0];
$OUT_dir = $ARGV[1];
$OUT_rate = $ARGV[2];

# Load the MLF
$IMLF = read_MLF_file($MLF_file);

# Get a list of all labels used in the entire MLF
%ALL_LABELS_HASH = ();
foreach $filename (keys %{$IMLF}) {
  $array = $$IMLF{$filename};
  foreach $p (@$array) 
    { 
      $ALL_LABELS_HASH{$$p[2]}=1; 
    }
}
@ALL_LABELS = sort keys %ALL_LABELS_HASH;

# Process each <file>.lab section in the MLF
foreach $filename (keys %{$IMLF}) {
  # Open the output file
  $outfile = $OUT_dir . '/' . $filename;
  $outfile =~ s/(\.\S+)?$/\.tgt/;
  open(OUTFILE, ">$outfile") || die "Unable to write to $outfile: $!";

  # Print column headings
  print OUTFILE "  time @ALL_LABELS\n";

  # Get the array 
  $array = $$IMLF{$filename};

  print "Writing $#$array lines to $outfile\n";

  # Unshift the array entries one at a time, and write
  $time = 0.00;
  foreach $p (@$array) {
    
    # First, print all lines prior to start of this element
    while($time < $$p[0]) {
      $line = sprintf("%6.3f",$time);
      foreach $lab (@ALL_LABELS) { $line .= "  0"; }
      print OUTFILE $line,"\n";
      $time += $OUT_rate;
    }

    # Second, print all lines between start and end of this element
    while($time < $$p[1]) {
      $line = sprintf("%6.3f",$time);
      foreach $lab (@ALL_LABELS) { 
	if ($$p[2] eq $lab) { $line .= "  1"; }
	else { $line .= "  0"; }
      }
      print OUTFILE $line,"\n";
      $time += $OUT_rate;
    }
  }
  close(OUTFILE);
}



