function NormLogFormants = CrossLangNormalise(GroupingIndices, RawLogFormants)
%
% function NormLogFormants = CrossLangNormalise(GroupingIndices, RawLogFormants)
%
% Geoffrey Stewart Morrison
% Release 2008-12-29 (almost identical to Release 2007-04-20)
%
% Cross-language / cross-dialect version of constant log interval normalisation of vowel formant values.
% Also normalises speakers' L2 vowels on the basis of their L1 data.
%
% Procedure described in:
%       Morrison, G. S., & Nearey, T. M.  (in prep). A simple pattern recognition model and cross-language vowel normalization 
%           for prediction of cross-language vowel perception. 
% Variant of procedure described in:
%       Morrison, G. S., & Nearey, T. M.  (2006). A cross-language vowel normalisation procedure. 
%           Canadian Acoustics, 34(3), 94–95.
%
%
% INPUT ARGUMENTSS:
%   GroupingIndices:    A matrix of numeric grouping indices. Rows are tokens. 
%                       Columns are [lang1, lang_spoken, gender, speaker, vowel_cat]
%                                       1       2           3       4       5
%
%                       lang1       indicates speaker's first language or dialect
%
%                       lang_spoken indicates language being spoken (optional column, default value = lang1)
%                                       Tokens with lang_spoken ~= lang1 are not used to calculate speaker and language normalisation
%                                       factors (Gspeaker & Glang), but are normalised using the Gspeaker & Glang values calculated using
%                                       the lang_spoken = lang1 data. Use lang_spoken to specify test tokens such as the speakers' L2 vowels.
%
%                       speaker     ids must be unique, i.e., use combinations such as
%                                       [lang1=1 speaker=1; lang1=1 speaker=2; ... lang1=2 speaker=11; lang1=2 speaker=12]
%                                       not [lang1=1 speaker=1; lang1=1 speaker=2; ... lang1=2 speaker=1; lang1=2 speaker=2]
%
%                       vowel_cat   ids must be unique
%
%   RawLogFormants:     A matrix of formant values (in log Hertz). Rows are tokens. 
%                           Columns are measures taken at points on formant tracks, e.g., [F1@25 F1@75 F2@25 F2@75]
%                           (use same number of points from each formant)
%                       OR
%                       A unidimensional cell array. Cells are tokens.
%                           Each cell contains a matrix of formant tracks, e.g., [F1_track F2_track F3_track]
%                           (time can be along either dimension)
%
% OUTPUT ARGUMENT:
%   NormLogFormants:    Normalised formant values (in log Hertz) arranged in same format as RawLogFormants.


if nargin == 0 
    % sample input data (only one token per vowel cat per speaker, only two male and two female speakers per lang)
    % speaker: 1=Spanish 2=English      gender: 0=Female 1=Male
    % vowel: 1=Sp/i/ 2=Sp/ei/ 3=Sp/e/ 4=Eng/i/ 5=Eng/I/ 6=Eng/e/ 7=Eng/E/
    GroupingIndices =...
    [1     0   206     1
     1     0   206     2
     1     0   206     3
     1     0   209     1
     1     0   209     2
     1     0   209     3
     1     1   200     1
     1     1   200     2
     1     1   200     3
     1     1   201     1
     1     1   201     2
     1     1   201     3
     2     0   165     4
     2     0   165     5
     2     0   165     6
     2     0   165     7
     2     0   167     4
     2     0   167     5
     2     0   167     6
     2     0   167     7
     2     1   166     4
     2     1   166     5
     2     1   166     6
     2     1   166     7
     2     1   175     4
     2     1   175     5
     2     1   175     6
     2     1   175     7];
    RawLogFormants =...
   [5.8859    5.8547    7.9167    7.9151
    6.3490    6.0536    7.7448    7.8548
    6.2899    6.2698    7.7153    7.7413
    5.9041    5.8424    7.8288    7.8694
    6.3401    5.9541    7.7615    7.8931
    6.1871    6.2079    7.7229    7.7252
    5.8675    5.8075    7.5662    7.5898
    6.2724    5.9672    7.4233    7.5649
    6.2422    6.2359    7.3461    7.3526
    5.7206    5.6800    7.5838    7.5709
    6.0621    5.7661    7.4527    7.6007
    6.0514    6.0679    7.4051    7.4289
    5.6702    5.7970    7.8323    7.8269
    6.2297    6.4688    7.6098    7.5711
    6.1980    6.1327    7.7110    7.7680
    6.5402    6.6139    7.5188    7.4597
    5.9205    5.9394    7.9543    7.9476
    6.4793    6.5030    7.5792    7.5800
    6.3094    6.1912    7.8575    7.9044
    6.6902    6.7667    7.5510    7.5219
    5.9513    5.7251    7.6815    7.6926
    6.1013    6.1083    7.5013    7.4530
    6.1748    6.0765    7.5503    7.6127
    6.3397    6.4229    7.4091    7.3083
    5.6346    5.6906    7.6608    7.6606
    6.1442    6.1783    7.3817    7.3173
    6.0755    6.0236    7.5348    7.6898
    6.2842    6.3597    7.3283    7.3083];
end


% Gtoken: mean of formant values for each token
if iscell(RawLogFormants)
    Gtoken = cellfun(@mean,RawLogFormants,'UniformOutput',0);
    Gtoken = cellfun(@mean,Gtoken,'UniformOutput',1);
else
    Gtoken = mean(RawLogFormants,2); % Gtoken
end

% if lang_spoken is not provided, assume pure cross-language situation (assume missing column is lang_spoken)
if size(GroupingIndices,2) == 4 
    GroupingIndices = [GroupingIndices(:,1), GroupingIndices(:,1), GroupingIndices(:,2:4)];
end

% CALCULATE NORMALISATION FACTORS
%   Gspeaker:   mean of formant values for each speaker
%   Glang:      mean of formant values for each language 

langs = unique(GroupingIndices(:,1));     num_langs = length(langs);
genders = unique(GroupingIndices(:,3));   num_genders = length(genders);
speakers = unique(GroupingIndices(:,4));  num_speakers = length(speakers);

II_gender = cell(num_genders,1); % using cells because ther may be unequal numbers of speakers per gender etc.
for Igender = 1:num_genders
    II_gender{Igender} = find(GroupingIndices(:,3) == genders(Igender));
end

II_speaker = cell(num_speakers,1);
for Ispeaker=1:num_speakers
    II_speaker{Ispeaker}=  find(GroupingIndices(:,4) == speakers(Ispeaker));
end

Glang_gender = zeros(num_langs,num_genders);
Gspeaker = zeros(num_speakers,1);

II_lang = cell(num_langs,1);
for Ilang = 1:num_langs
    II_lang{Ilang} = find(GroupingIndices(:,1) == langs(Ilang) & GroupingIndices(:,2) == langs(Ilang)); % use L1 data only
    
    for Igender = 1:num_genders % gender within language
        II_lang_gender = intersect(II_lang{Ilang}, II_gender{Igender}); 
        speakers_sub = unique(GroupingIndices(II_lang_gender,4));
        num_speakers_sub = length(speakers_sub);
        Gspeaker_sub = zeros(num_speakers_sub,1);
        
        for Ispeaker_sub = 1:num_speakers_sub % speaker within gender within language
            Ispeaker = find(speakers == speakers_sub(Ispeaker_sub));
            II_lang_gender_speaker = intersect(II_lang_gender,II_speaker{Ispeaker});
            vowels_sub = unique(GroupingIndices(II_lang_gender_speaker,5));
            num_vowels_sub = length(vowels_sub);
            Gvowel_sub = zeros(num_vowels_sub,1);
            
            for Ivowel_sub = 1:num_vowels_sub % vowel cat within speaker within gender within language
                II_vowel_sub = GroupingIndices(II_lang_gender_speaker,5) == vowels_sub(Ivowel_sub);
                Gvowel_sub(Ivowel_sub) = mean(Gtoken(II_lang_gender_speaker(II_vowel_sub)));
            end
            
            Gspeaker(Ispeaker) = mean(Gvowel_sub); % Gspeaker
            Gspeaker_sub(Ispeaker_sub) = Gspeaker(Ispeaker);
        end
        
        Glang_gender(Ilang,Igender)=mean(Gspeaker_sub); % Glang_gender (intermediate step to Glang)
    end 
end

Glang = mean(Glang_gender,2); % Glang


% NORMALISE FORMANT DATA
if iscell(RawLogFormants)
    NormLogFormants=cell(size(RawLogFormants));
else
    NormLogFormants=zeros(size(RawLogFormants));
end

for Ispeaker=1:num_speakers
    Ilang = langs == GroupingIndices(II_speaker{Ispeaker}(1),1);
    % Ntoken = Gtoken - Gspeaker + Glang
    if iscell(RawLogFormants)
        NormLogFormants(II_speaker{Ispeaker}) = cellfun(@(x) x - Gspeaker(Ispeaker) + Glang(Ilang), RawLogFormants(II_speaker{Ispeaker}), 'UniformOutput', 0);
    else
        NormLogFormants(II_speaker{Ispeaker},:) = RawLogFormants(II_speaker{Ispeaker},:) - Gspeaker(Ispeaker) + Glang(Ilang);
    end
end

return

