content: /**
   * Copyright 2009 DFKI GmbH.
   * All Rights Reserved.  Use is subject to license terms.
   * <p/>
   * This file is part of MARY TTS.
   * <p/>
   * MARY TTS is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation, version 3 of the License.
   * <p/>
   * 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 Lesser General Public License for more details.
   * <p/>
   * You should have received a copy of the GNU Lesser General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package marytts.signalproc.analysis;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * @author marc
   */
  public class AlignedLabels {
      private Labels first;
      private Labels second;
      private int[] indexMap;
      private List<AlignedTimeStretch> stretches;
  
      public AlignedLabels(Labels first, Labels second, int[] indexMap) {
          this.first = first;
          this.second = second;
          this.indexMap = indexMap;
          // Convert index numbers into time codes of stretches mapped onto one another.
          stretches = new ArrayList<AlignedTimeStretch>();
          // For mapping stretches, there are three possibilities:
          // 1. a one-to-one mapping, i.e. indexMap[i] == indexMap[i-1] + 1;
          // 2. a one-to-many mapping, i.e. indexMap[i] == indexMap[i-1] + n, where n > 1;
          // 3. a many-to-one mapping, i.e. indexMap[i] == indexMap[i-n], where n >= 1.
  
          // Index numbers in label files, representing start/end of stretches to map:
          int iFirstStart = 0;
          int iFirstEnd = 0;
          int iSecondStart = 0;
          int iSecondEnd = 0;
          while (iFirstStart < first.items.length) {
              iFirstEnd = iFirstStart;
              iSecondEnd = indexMap[iFirstStart];
              // check for case 3 (many-to-one mapping):
              while (iFirstEnd < first.items.length && indexMap[iFirstEnd] == iSecondEnd) {
                  iFirstEnd++;
              }
              iFirstEnd--; // we went one too far
              // Now all first labels from iFirstStart to iFirstEnd map to the interval from iSecondStart to iSecondEnd
              // Now, the start times are the end times of the previous label (or 0 if this is the first label):
              double tFirstStart = (iFirstStart == 0) ? 0. : first.items[iFirstStart - 1].time;
              double tFirstEnd = first.items[iFirstEnd].time;
              double tSecondStart = (iSecondStart == 0) ? 0. : second.items[iSecondStart - 1].time;
              double tSecondEnd = second.items[iSecondEnd].time;
              stretches.add(new AlignedTimeStretch(tFirstStart, tFirstEnd, tSecondStart, tSecondEnd));
              // For next round:
              iFirstStart = iFirstEnd + 1;
              iSecondStart = iSecondEnd + 1;
          }
      }
  
      public Labels getFirst() {
          return first;
      }
  
      public Labels getSecond() {
          return second;
      }
  
      public int[] getIndexMap() {
          return indexMap;
      }
  
      /**
       * Given the label sequences and their alignment, map a time in the first sequence to
       * the corresponding time in the second sequence.
       *
       * @param time1
       * @return the corresponding time, or a negative value if no corresponding time could be determined
       */
      public double mapTimeFromFirstToSecond(double time1) {
          // Look up the corresponding time stretch, and map linearly within the time stretch:
          for (AlignedTimeStretch t : stretches) {
              if (time1 >= t.firstStart && time1 <= t.firstStart + t.firstDuration) {
                  if (t.firstDuration == 0.) {
                      return t.secondStart;
                  } else {
                      return t.secondStart + (time1 - t.firstStart) / t.firstDuration * t.secondDuration;
                  }
              }
          }
          // not found
          return -1;
      }
  
      /**
       * Given the label sequences and their alignment, map a time in the second sequence to
       * the corresponding time in the first sequence.
       *
       * @param time2
       * @return the corresponding time, or a negative value if no corresponding time could be determined
       */
      public double mapTimeFromSecondToFirst(double time2) {
          // Look up the corresponding time stretch, and map linearly within the time stretch:
          for (AlignedTimeStretch t : stretches) {
              if (time2 >= t.secondStart && time2 <= t.secondStart + t.secondDuration) {
                  if (t.secondDuration == 0.) {
                      return t.firstStart;
                  } else {
                      return t.firstStart + (time2 - t.secondStart) / t.secondDuration * t.firstDuration;
                  }
              }
          }
          // not found
          return -1;
      }
  
      public List<AlignedTimeStretch> getAlignedTimeStretches() {
          return stretches;
      }
  
      public static class AlignedTimeStretch {
          public final double firstStart, firstDuration, secondStart, secondDuration;
  
          public AlignedTimeStretch(double tFirstStart, double tFirstEnd, double tSecondStart, double tSecondEnd) {
              this.firstStart = tFirstStart;
              this.firstDuration = tFirstEnd - tFirstStart;
              this.secondStart = tSecondStart;
              this.secondDuration = tSecondEnd - tSecondStart;
              assert firstDuration >= 0.;
              assert secondDuration >= 0.;
          }
  
          @Override
          public String toString() {
              return String.format("%.3f+%.3f %.3f+%.3f", firstStart, firstDuration, secondStart, secondDuration);
          }
      }
  }
  