/** * Copyright 2009 DFKI GmbH. * All Rights Reserved. Use is subject to license terms. * * This file is part of MARY TTS. * * 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. * * 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. * * 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 * 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 * 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.; } public String toString() { return String.format("%.3f+%.3f %.3f+%.3f", firstStart, firstDuration, secondStart, secondDuration); } } }