#! /usr/bin/perl
#
# Special purpose program for Unix/Linux:
# Read all arguments as aligned praat phoneme label files with 
# translit, words, syllables, syllparts, and phonemes tiers.
#
# Construct a Database table for each tier.
#
# Use:
# ./ConstructPhonemeTables.pl <SpeakerGlob>
#
# This will read in all files from <SpeakerGlob> (e.g., F*)
# and constructs Table files with the names <Speaker><tier>.txt
# in the phoneme/<tier> subdirectories of the DatabaseFiles/ directory
#
#
###############################################################################
#
# Copyright R.J.J.H. van Son © 2000, 2001
#
# Author Rob van Son
# Institute of Phonetic Sciences & ACLC
# University of Amsterdam
# Herengracht 338
# NL-1016CG Amsterdam, The Netherlands
# Email: Rob.van.Son@hum.uva.nl
#        rob.van.son@workmail.com
# WWW  : http://www.fon.hum.uva.nl/rob/
# mail:  Institute of Phonetic Sciences
#        University of Amsterdam
#        Herengracht 338
#        NL-1016CG Amsterdam
#        The Netherlands
#        tel +31 205252183
#        fax +31 205252197
#
# License for use and disclaimers
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
#
#######################################################
#
$HomeDir = '../../..';
require "$HomeDir/Links.pl";
require "$Scripts/CGN_phonemes.pl";  # For the phoneme classes
require "$Scripts/SentenceLabel.pl";

#
# Where are the files
my $TypeExtension = 'phoneme';
my $TableDirectory = "$DatabaseTables/$TypeExtension";

#
# Tiers to handle
@TierList = reverse('PHONEMES', 'SYLLPARTS', 'LEXSYLL', 'SYLLABLES',
  'LEXCGN', 'CGN', 'WORDS', 'TRANSLIT');

# Postediting routines for all tiers
# Given a Sentence Label and a Record list, they will change/add the
# values in the Record.
# 
# ColumnHeaderPostEditing list will change column header names (should work on $_)
my @ColumnHeaderPostEditing = (
's/TRANSLIT/SENTENCE/g',
's/(LEX)?CGN(\S)/WORDS\2/g',
's/LEXSYLL(\S)/SYLLABLES\1/g',
);

# The Column Headers list will add the corresponding column headers.
my @PHONEMEScolumnHeaders = (
'Manner [CHAR(1)] Manner of Articulation', 
'Articulator [CHAR(1)] Primary Articulator', 
'Voicing [CHAR(1)] Voiced (+) / Unvoiced (-)',
'prevManner [CHAR(1)] Manner for previous item', 
'prevArticulator [CHAR(1)] Articulator for previous item', 
'prevVoicing [CHAR(1)] Voicing of previous item',
'nextManner [CHAR(1)] Manner for next item', 
'nextArticulator [CHAR(1)] Articulator for next item', 
'nextVoicing [CHAR(1)] Voicing of next item',
'SYLLPARTS [TEXT] Part of syllable', 
'SYLLSTRUC [CHAR(1)] Onset, Kernel, or Rhyme',
'CLUSTERLEN [INT2] Length of SYLLSTRUC (cluster-length)',
'SYLLABLES [TEXT] Realized Syllable', 
'STRESS [CHAR(1)] Syllable Stress (+/-)', 
'POSSYLL [INT2] Position of syllable in word', 
'LEXSYLL [TEXT] Lexical Syllable', 
'WORDS [TEXT] Orthographic word * marker', 
'CGN [TEXT] CGN transcribed word', 
'NumSyll [INT2] Number of syllables in word',
'LEXCGN [TEXT] Lexical CGN transcribed word',
);
sub PHONEMESedit        # ($Label, \@Records)
{
    my $Label = shift || return 0;
    my $Record = shift || return 0;
    
    # Phoneme information of current, previous and next Phoneme
    my $i;
    foreach $i (5, 13, 15)
    {
        # Get Manner, Articulator and Voicing
        my $Manner = $CGN_phonemes{$Record->[$i]}[0] || 'NULL';
        my $Articulator = $CGN_phonemes{$Record->[$i]}[1] || 'NULL';
        my $Voicing = $CGN_phonemes{$Record->[$i]}[2] || 'NULL';
        
        push(@$Record, $Manner, $Articulator, $Voicing);
    };
    
    # 
    # Get more information (all tiers are already aligned)
    foreach $i ('SYLLPARTS', 'SYLLABLES', 'LEXSYLL', 'WORDS', 'CGN', 'LEXCGN')
    {
    	my $CurrentValue = $Label->currentValue($i);
	$CurrentValue = 'NULL' unless $CurrentValue =~ /\S/;
	# Remove boundary validity marker
	$CurrentValue =~ s/\=//g;
    	push(@$Record, $CurrentValue);
	
	if($i eq 'SYLLPARTS')
	{
		my $SyllPart = $Label->currentIDcode($i);
		$SyllPart =~ /([OKC])[A-Z\-]*$/;
		$SyllPart = $1;
		$SyllPart = 'NULL' unless $SyllPart =~ /\S/;
		$CurrentValue =~ s/[\"\+\~\:]//g;
		push(@$Record, $SyllPart, length($CurrentValue));
	}
	elsif($i eq 'SYLLABLES')
	{
		my $Stress = $CurrentValue =~ /\"/ ? '+' : '-';
		my $SyllPos = $Label->currentIDcode($i);
		$SyllPos =~ /([S-Z\-]+)$/;
		my $Ch = $`."S";
		my $count;
		for($count = 1; ($Ch cmp $SyllPos) < 0; ++$count)
		{
			$Ch = SentenceLabel::IncreaseIDcode($Ch, 'S');
		};
		push(@$Record, $Stress, $count);
	}
	elsif($i eq 'CGN')
	{
		my $NumSylls = ($CurrentValue =~ s/[\-]/-/g);
		++$NumSylls;
		push(@$Record, $NumSylls);
	};
    };
    
    return @$Record;
}

my @SYLLPARTScolumnHeaders = ('NumPhon [INT2] Number of phonemes in syllable part');
sub SYLLPARTSedit       # ($Label, \@Records)  
{
    my $Label = shift || return 0;
    my $Record = shift || return 0;
    
    my $Value = $Record->[5];
    $Value =~ s/[\+\-\~\:]//g;
    push(@$Record, length($Value));
    
    return 1;
}

my @SYLLABLEScolumnHeaders = (
'Stress [CHAR(1)] Syllable stress (+/-)', 
'prevStress [CHAR(1)] Stress of previous item', 
'nextStress [CHAR(1)] Stress of next item', 
'NumPhon [INT2] Number of phonemes in syllable'
);
sub SYLLABLESedit       # ($Label, \@Record)  
{
    my $Label = shift || return 0;
    my $Record = shift || return 0;
    
    # The syllable stress marker
    my $StressedSyllable = ($Record->[5] =~ s/\"//g) ? '+' : '-';
    my $prevStressedSyllable = ($Record->[13] =~ s/\"//g) ? '+' : '-';
    my $nextStressedSyllable = ($Record->[15] =~ s/\"//g) ? '+' : '-';
    
    push(@$Record, $StressedSyllable, $prevStressedSyllable, $nextStressedSyllable);
    
    my $Value = $Record->[5];
    $Value =~ s/[\+\-\~\:\"]//g;
    push(@$Record, length($Value));
    
    return 1;
}

my @LEXSYLLcolumnHeaders = (
'Stress [CHAR(1)] Lexical syllable stress (+/-)', 
'prevStress [CHAR(1)] Stress of previous item', 
'nextStress [CHAR(1)] Stress of next item', 
'NumPhon [INT2] Number of phonemes in (lexical) syllable'
);
sub LEXSYLLedit # ($Label, \@Record)  
{
    my $Label = shift || return 0;
    my $Record = shift || return 0;
    
    # The syllable stress marker
    my $StressedSyllable = ($Record->[5] =~ s/\"//g) ? '+' : '-';
    my $prevStressedSyllable = ($Record->[13] =~ s/\"//g) ? '+' : '-';
    my $nextStressedSyllable = ($Record->[15] =~ s/\"//g) ? '+' : '-';
    
    push(@$Record, $StressedSyllable, $prevStressedSyllable, $nextStressedSyllable);
    
    my $Value = $Record->[5];
    $Value =~ s/[\+\-\~\:\"]//g;
    push(@$Record, length($Value));
    
    return 1;
}

my @CGNcolumnHeaders = (
'NumSyll [INT2] Number of syllables in word', 
'StressPatt [TEXT] Stress patter of word', 
'NumPhon [INT2] Number of phonemes in word'
);
sub CGNedit     # ($Label, \@Records)  
{
    my $Label = shift || return 0;
    my $Record = shift || return 0;
    
    my $Value = $Record->[5];
    # Get the number of Syllables and the stress pattern
    my @Syllables = map {/\"/ ? 1 : 0;} split(/[\-\_]/, $Value);
    push(@$Record, scalar(@Syllables), join("", @Syllables));
    # Get the number of phonemes
    $Value =~ s/[\+\-\~\:\"]//g;
    push(@$Record, length($Value));
    
    return 1;
}

my @LEXCGNcolumnHeaders = (
'NumSyll [INT2] Number of syllables in lexical word', 
'StressPatt [TEXT] Stress patter of lexical word', 
'NumPhon [INT2] Number of phonemes in lexical word'
);
sub LEXCGNedit  # ($Label, \@Records)  
{
    my $Label = shift || return 0;
    my $Record = shift || return 0;
    
    my $Value = $Record->[5];
    # Get the number of Syllables and the stress pattern
    my @Syllables = map {/\"/ ? 1 : 0;} split(/[\-\_]/, $Value);
    push(@$Record, scalar(@Syllables), join("", @Syllables));
    # Get the number of phonemes
    $Value =~ s/[\+\-\~\:\"]//g;
    push(@$Record, length($Value));
    
    return 1;
}

my @WORDScolumnHeaders = (
'Marker [CHAR(1)] Star marker of transliteration (a/u/v/x)',
'Accent [INT2] Sentence Accent (0, 1, ...)'
);
sub WORDSedit   # ($Label, \@Records)  
{
    my $Label = shift || return 0;
    my $Record = shift || return 0;
    
    # Split of transcription markers like *a, *u, *v, and *x
    my $Value = $Record->[5];
    my ($Word, $Marker) = ($Value, 'NULL');
    if($Word =~ /\*(\S)/)
    {
        $Marker = uc($1);
        $Word = $`;
    };
    $Record->[5] = $Word;
    
    push(@$Record, $Marker);
    
    # Remove markers from other words
    $Record->[13] =~ s/\*[\w]+//g;
    $Record->[15] =~ s/\*[\w]+//g;
    
    # Adde empty accent asignment
    push(@$Record, 'NULL');
    
    return 1;
}

my @TRANSLITcolumnHeaders = (
'SentenceNbr [INT2] Number of sentence in the Chunk', 
'OrderNbr [INT4] A number for sorting in order of recording', 
'NrWords [INT2] Number of words in transliteration', 
'NrSyllables [INT2] Number of syllables in transliteration', 
'NrPhonemes [INT2] Number of phonemes in transliteration',
'WordRate [FLOAT4] Rate in words per second', 
'SyllableRate [FLOAT4] Rate in syllables per second', 
'PhonemeRate [FLOAT4] Rate in phonemes per second'
);
sub TRANSLITedit        # ($Label, \@Records) 
{
    my $Label = shift || return 0;
    my $Record = shift || return 0;
    
    # The word tier
    my $WordTier = 'CGN';
    
    # Remove unwanted items
    splice(@$Record, 12, 4);
    
    # Add a placeholder for the sentence number and sorting order number
    push(@$Record, 'NULL', 'NULL');
    
    # Initialize
    my ($WordNumber, $SyllableNumber, $PhonemeNumber) = (0, 0, 0);
    my $Duration = 0;
    
    # Get the mean words, syllables and phonemes per second from CGN
    # First, count the non-pause words etc. and sum their durations
    $Label->presetItem($WordTier);
    while($Label->nextStartininterval($WordTier, 'TRANSLIT') > -1)
    {
        $Label->currentItem('WORDS', $Label->currentItem($WordTier));
        my $Word = $Label->currentValue('WORDS');
        
        # Skip (filled) pauses (Silence, uh, uhm, enne)
        next if $Word =~ /^\s*(uh|uhm|enne|enneh|h[m]+|eh|\*|\#)\s*$/i;
        
        $Word = $Label->currentValue($WordTier);
        
        # Get the duration
        $Duration += $Label->currentEnd($WordTier) - $Label->currentStart($WordTier);
        
        # Get Syllables
        my @SyllableList = split(/[\-\_]/, $Word);
        my $Phonemes = $Word;
        # Remove all superfluous chars
        $Phonemes =~ s/[\"\'\-\:\~\+]//g;
        
        # Count words, syllables and phonemes
        ++$WordNumber;
        $SyllableNumber += scalar(@SyllableList);
        $PhonemeNumber += length($Phonemes);
    };
    
    # Calculate the rates (in items per second)
    my ($WordRate, $SyllableRate, $PhonemeRate) = (0, 0, 0);
    if($Duration > 0)
    {
        $WordRate = $WordNumber / $Duration;
        $SyllableRate = $SyllableNumber / $Duration;
        $PhonemeRate = $PhonemeNumber / $Duration;
    };
    
    # Add numbers and rates
    push(@$Record, $WordNumber, $SyllableNumber, $PhonemeNumber, $WordRate, $SyllableRate, $PhonemeRate);
    
    return 1;
}


my %PostEditors = (
'PHONEMES' => \&PHONEMESedit, 
'SYLLPARTS' => \&SYLLPARTSedit, 
'SYLLABLES' => \&SYLLABLESedit, 
'LEXSYLL' => \&LEXSYLLedit, 
'CGN' => \&CGNedit, 
'LEXCGN' => \&LEXCGNedit, 
'WORDS' => \&WORDSedit, 
'TRANSLIT' => \&TRANSLITedit
);

my %PostHeader = (
'PHONEMES' => \@PHONEMEScolumnHeaders, 
'SYLLPARTS' => \@SYLLPARTScolumnHeaders, 
'SYLLABLES' => \@SYLLABLEScolumnHeaders, 
'LEXSYLL' => \@LEXSYLLcolumnHeaders, 
'CGN' => \@CGNcolumnHeaders, 
'LEXCGN' => \@LEXCGNcolumnHeaders, 
'WORDS' => \@WORDScolumnHeaders, 
'TRANSLIT' => \@TRANSLITcolumnHeaders
);


#
# Major loop, process all speakers
my $SpeakerGlob;

foreach $SpeakerGlob (@ARGV)
{
    # Get the label filenames
    @PhonemeFileList = 
    glob("$SentenceLabels/$SpeakerGlob/$TypeExtension/*.$TypeExtension");
    # Process all files
    my $File;
    foreach $File (@PhonemeFileList)
    {
        # Get the ID of the current speaker
        my $CurrentSpeaker = "";
        if($File =~ m@/([^/]+)/$TypeExtension/[^/]+$@)
        {
            $CurrentSpeaker = $1;
        }
        else
        {
            die "No speaker found: $File\n";
        };
        
        # Get the ID of the current author
        my $CurrentAuthor = 'NULL';
        if($File =~ /\_([^\.]+)\.[^\.]+$/)
        {
            $CurrentAuthor = $1;
        };
        
        my $Label = new SentenceLabel;
        next unless $Label->ReadLabelFile($File);
        
        $Label->presetItem('TRANSLIT');
        while($Label->nextItem('TRANSLIT') > -1)
        {
            # Skip empty sentences
            next unless $Label->currentValue('TRANSLIT') =~ /[^\#\*\s\_]/;
            
            # Correct some odd errors (SentenceID =! TranslitID)
            $Label->currentItem('SENTENCE', $Label->currentItem('TRANSLIT'));
            $Label->currentIDcode('SENTENCE', $Label->currentIDcode('TRANSLIT'));
            
            my $Tier;
            foreach $Tier (@TierList)
            {
                my @Table = $Label->TierToTable($Tier, 'TRANSLIT', $PostEditors{$Tier}, $CurrentAuthor);
                print STDERR "Error with '$Tier' from $File\n" unless @Table;
                
                # Print result to file
                my $PrintHeader = (-e "$TableDirectory/$CurrentSpeaker$Tier$TypeExtension.txt") ? 0 : 1;
                open(OUTPUT, ">>$TableDirectory/$CurrentSpeaker$Tier$TypeExtension.txt") 
                || die ">>$TableDirectory/$CurrentSpeaker$Tier$TypeExtension.txt: $!\n";
                
                # Print the column headers
                if($PrintHeader)
                {
                    # Make sure the tier is somewhere
                    $Label->currentItem($Tier, 1) unless $Tier eq 'TRANSLIT';
                    
                    # Construct the list with column headers
                    my @CurrentHeaders = $Label->TierToTableColumnheaders($Tier, @{$PostHeader{$Tier}}, 
                    'Author [TEXT] ID of annotator');
                    
                    # Post editing of the column headers
                    @CurrentHeaders = map {eval join(";", @ColumnHeaderPostEditing); $_;} @CurrentHeaders;
                    
                    # Remove some items if using the TRANSLIT tier
                    splice(@CurrentHeaders, 12, 4) if $Tier eq 'TRANSLIT';
                    
                    # Print the column headers
                    print OUTPUT "\#> ", join("\n\#> ", @CurrentHeaders), "\n";
                    
                    # Print some extra information
                    print OUTPUT "\# ";
                    print OUTPUT "Tier: $Tier\n\# Speaker: $CurrentSpeaker\n\# Source: $TypeExtension\n";
                    print OUTPUT "\#\n";
                    

		    # Print the copyright license
		    if(open(LICENSE, "<./licenseterms.txt"))
		    {
			    my $Date = localtime();
			    $Date =~ /\s([\d]+)\s*$/;
			    my $Year = $1;
			    while(<LICENSE>)
			    {
				    if(/\©/ && ! /$Year/)
				    {
					    s/(\d)\s{6}(\s+\#)/\1, $Year\2/ig;
				    };
				    print OUTPUT $_;
			    };
			    close(LICENSE)
		    };

                    # Header is printed ONLY once
                    $PrintHeader = 0;
                };
                
                my $Record;
                foreach $Record (@Table)
                {
                    print OUTPUT join("\t", @$Record), "\n";
                };
                close(OUTPUT);
            };
        };
    };
    
};



