/** * Copyright 2004-2006 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.Arrays; import marytts.signalproc.window.BlackmanWindow; import marytts.signalproc.window.Window; import marytts.util.data.BufferedDoubleDataSource; import marytts.util.data.DoubleDataSource; import marytts.util.math.FFT; import marytts.util.math.MathUtils; /** * * @author Marc Schröder * * Autocorrelation based F0 tracker with dynamic programming * */ public class F0TrackerAutocorrelationDP extends F0Tracker { protected DoubleDataSource preprocess(DoubleDataSource signal) { double CENTERCLIPPING_THRESHOLD = 0.2; double[] signalData = signal.getAllData(); // Remove DC component: double mean = MathUtils.mean(signalData); // System.err.println("Removing DC component: " + mean); for (int i = 0; i < signalData.length; i++) { signalData[i] -= mean; } // Center clipping double maxAmplitude = MathUtils.absMax(signalData); double cutoff = maxAmplitude * CENTERCLIPPING_THRESHOLD; for (int i = 0; i < signalData.length; i++) { if (Math.abs(signalData[i]) < cutoff) signalData[i] = 0; } return new BufferedDoubleDataSource(signalData); } protected FrameBasedAnalyser getCandidateEstimator(DoubleDataSource preprocessedSignal, int samplingRate) { // Window length should be about 3 periods at minimum F0: int windowLength = 3 * samplingRate / DEFAULT_MINF0; // make sure it is an odd number: if (windowLength % 2 == 0) windowLength++; Window window = new BlackmanWindow(windowLength); // System.err.println("Window length: " + window.getLength()); int windowShift = windowLength / 2; return new CandidateEstimator(preprocessedSignal, window, windowShift, samplingRate); } protected TransitionCost getTransitionCost() { return null; } public class CandidateEstimator extends F0Tracker.CandidateEstimator { public static final int NCANDIDATES = 15; protected int minF0; protected int maxF0; protected double[] correlationInput; /** * Track the F0 contour, using the Autocorrelation method. * * @param signal * the signal for which to track the F0 contour * @param window * the Window to use for cutting out frames * @param frameShift * the number of samples to shift between frames * @param samplingRate * the sampling rate of the signal, in samples per second * @param minF0 * minF0 * @param maxF0 * maxF0 */ public CandidateEstimator(DoubleDataSource signal, Window window, int frameShift, int samplingRate, int minF0, int maxF0) { this(signal, window, frameShift, samplingRate); this.minF0 = minF0; this.maxF0 = maxF0; } /** * Track the F0 contour, using the Autocorrelation method. * * @param signal * the signal for which to track the F0 contour * @param window * the Window to use for cutting out frames * @param frameShift * the number of samples to shift between frames * @param samplingRate * the sampling rate of the signal, in samples per second */ public CandidateEstimator(DoubleDataSource signal, Window window, int frameShift, int samplingRate) { super(signal, window, frameShift, samplingRate, NCANDIDATES); this.correlationInput = new double[MathUtils.closestPowerOfTwoAbove(2 * window.getLength())]; this.minF0 = DEFAULT_MINF0; this.maxF0 = DEFAULT_MAXF0; } protected void findCandidates(F0Candidate[] candidates, double[] frame) { System.arraycopy(frame, 0, correlationInput, 0, frame.length); Arrays.fill(correlationInput, frame.length, correlationInput.length, 0); double[] acf = FFT.autoCorrelate(correlationInput); // Simple model: take all the peaks, rate them by their height. int valley = MathUtils.findNextValleyLocation(acf, 0); int peak; double deltaT = 1. / samplingRate; while ((peak = MathUtils.findNextPeakLocation(acf, valley)) != acf.length - 1) { double f0 = 1 / (peak * deltaT); // Ignore implausible f0 values: if (f0 >= minF0 && f0 <= maxF0) { addCandidate(candidates, new F0Candidate(f0, acf[peak])); } valley = MathUtils.findNextValleyLocation(acf, peak); } } } }