/**
* 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.process;
import java.io.File;
import java.util.Arrays;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import marytts.signalproc.display.FunctionGraph;
import marytts.signalproc.display.SignalGraph;
import marytts.util.data.BufferedDoubleDataSource;
import marytts.util.data.DoubleDataSource;
import marytts.util.data.audio.AudioDoubleDataSource;
import marytts.util.math.MathUtils;
/**
* Cut frames out of a given signal, and provide them one by one, optionally applying a processor to the frame. This base
* implementation provides frames of a fixed length with a fixed shift.
*
* @author Marc Schröder
* @see PitchFrameProvider
*/
public class FrameProvider {
protected DoubleDataSource signal;
protected InlineDataProcessor processor;
/**
* The sampling rate.
*/
protected int samplingRate;
/**
* The start time of the currently analysed frame.
*/
protected long frameStart;
protected long nextFrameStart;
protected int totalRead = 0;
protected double[] frame;
protected int validSamplesInFrame;
protected int frameShift;
protected int frameLength;
/**
* The part of the original signal to remember for the next overlapping frame.
*/
private double[] memory;
// If !stopWhenTouchingEnd, we must continue reading from memory,
// padding the respective frames with zeroes. This is the current
// reading position:
private int posInMemory;
private boolean memoryFilled;
/**
* Whether or not this frame provider stops when the first frame touches the last input sample.
*/
private boolean stopWhenTouchingEnd;
/**
* Initialise a FrameProvider.
*
* @param signal
* the signal source to read from
* @param processor
* an optional data processor to apply to the source signal. If null, the original data will be returned.
* @param frameLength
* the number of samples in one frame.
* @param frameShift
* the number of samples by which to shift the window from one frame analysis to the next; if this is smaller than
* window.getLength(), frames will overlap.
* @param samplingRate
* the number of samples in one second.
* @param stopWhenTouchingEnd
* whether or not this frame provider stops when the first frame touches the last input sample. When this is set to
* true, the last frame will be the first one including the last sample; when this is set to false, the last frame
* will be the last that still contains any data.
*/
public FrameProvider(DoubleDataSource signal, InlineDataProcessor processor, int frameLength, int frameShift,
int samplingRate, boolean stopWhenTouchingEnd) {
this.signal = signal;
this.processor = processor;
this.frameShift = frameShift;
this.frameLength = frameLength;
this.samplingRate = samplingRate;
this.frame = new double[frameLength];
this.frameStart = -1;
this.nextFrameStart = 0;
validSamplesInFrame = 0;
// We keep the previous frame in memory (we'll need this if frameShift < frameLength):
this.memory = new double[frameLength];
posInMemory = memory.length; // "empty"
memoryFilled = false;
this.stopWhenTouchingEnd = stopWhenTouchingEnd;
}
/**
* Start position of current frame, in seconds
*
* @return the start time of the last frame returned by getNextFrame(), or a small negative number if no frame has been served
* yet.
*/
public double getFrameStartTime() {
return (double) frameStart / samplingRate;
}
/**
* Start position of current frame, in samples
*
* @return the start position of the last frame returned by getNextFrame(), or -1 if no frame has been served yet.
*/
public long getFrameStartSamples() {
return frameStart;
}
public int getSamplingRate() {
return samplingRate;
}
/**
* The amount of time by which one frame is shifted against the next.
*
* @return frameShift / samplingRate
*/
public double getFrameShiftTime() {
return (double) frameShift / samplingRate;
}
/**
* The number of samples by which one frame is shifted against the next.
*
* @return frameShift
*/
public int getFrameShiftSamples() {
return frameShift;
}
/**
* The time length of a frame.
*
* @return getFrameLengthSamples / samplingRate
*/
public double getFrameLengthTime() {
return (double) getFrameLengthSamples() / samplingRate;
}
/**
* The number of samples in the current frame.
*
* @return frameLength
*/
public int getFrameLengthSamples() {
return frameLength;
}
/**
* Whether or not this frame provider stops when the first frame touches the last input sample. When this returns true, the
* last frame will be the first one including the last sample; when this returns false, the last frame will be the last that
* still contains any data. Defaults to true.
*
* @return stopWhenTouchingEnd
*/
public boolean stopWhenTouchingEnd() {
return stopWhenTouchingEnd;
}
/**
* Whether or not this frameprovider can provide another frame.
*
* @return signal.hasMoreData() or different from stopWhenTouchingEnd and memoryFilled and posInMemory < memory.length
*/
public boolean hasMoreData() {
return signal.hasMoreData() || !stopWhenTouchingEnd && memoryFilled && posInMemory < memory.length;
}
/**
* This tells how many valid samples have been read into the current frame (before applying the optional data processor!).
*
* @return validSamplesInFrame
*/
public int validSamplesInFrame() {
return validSamplesInFrame;
}
/**
* Fill the internal double array with the next frame of data. The last frame, if only partially filled with the rest of the
* signal, is filled up with zeroes. If stopWhenTouchingEnd() returns true, this method will provide not more than a single
* zero-padded frame at the end of the signal.
*
* @return the next frame on success, null on failure.
*/
public double[] getNextFrame() {
frameStart = nextFrameStart;
if (!hasMoreData()) {
validSamplesInFrame = 0;
return null;
}
// A frame is composed from two sources:
// 1. memory and 2. newly read signal.
int nFromMemory;
// 1. Prepend some memory?
if (memoryFilled && posInMemory < memory.length) {
nFromMemory = memory.length - posInMemory;
// System.err.println("Reusing " + nFromMemory + " samples from previous frame");
System.arraycopy(memory, posInMemory, frame, 0, nFromMemory);
} else {
nFromMemory = 0;
// System.err.println("No data to reuse from previous frame.");
}
// 2. Read new bit of signal:
int read = getData(nFromMemory);
totalRead += read;
// At end of input signal, we are unable to fill the frame completely:
if (nFromMemory + read < frameLength) { // zero-pad last frame(s)
assert !signal.hasMoreData();
// Pad with zeroes.
Arrays.fill(frame, nFromMemory + read, frame.length, 0);
}
validSamplesInFrame = nFromMemory + read; // = frame.length except for last frame
// OK, the frame is filled.
// For overlapping frames,
// remember the frame data in order to reuse part of it for next frame
int amountToRemember = frameLength - frameShift;
if (validSamplesInFrame < frameLength) {
amountToRemember = validSamplesInFrame - frameShift;
}
if (amountToRemember > 0) {
if (memory.length < amountToRemember) {
memory = new double[amountToRemember];
}
System.arraycopy(frame, validSamplesInFrame - amountToRemember, memory, memory.length - amountToRemember,
amountToRemember);
posInMemory = memory.length - amountToRemember;
memoryFilled = true;
} else {
posInMemory = memory.length;
memoryFilled = false;
}
// Apply the processor to the data:
if (processor != null)
processor.applyInline(frame, 0, frameLength);
nextFrameStart = frameStart + frameShift;
// System.err.println("FrameProvider: Frame "+" (" + frameStartTime+"-"+(frameStartTime+getFrameLengthTime()) + "): read "
// + read + "(total "+totalRead+"), posInMemory " + posInMemory + ", memory.length " + memory.length + ", valid " +
// validSamplesInFrame);
return frame;
}
public double[] getCurrentFrame() {
return frame;
}
/**
* Read data from input signal into current frame. This base implementation will attempt to fill the frame from the position
* given in nPrefilled onwards.
*
* @param nPrefilled
* number of valid values at the beginning of frame. These should not be lost or overwritten.
* @return the number of new values read into frame at position nPrefilled.
*/
protected int getData(int nPrefilled) {
return signal.getData(frame, nPrefilled, frame.length - nPrefilled);
}
/**
* Reset the internal time stamp to 0.
*/
public void resetInternalTimer() {
this.frameStart = -1;
this.nextFrameStart = 0;
this.totalRead = 0;
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < args.length; i++) {
AudioInputStream inputAudio = AudioSystem.getAudioInputStream(new File(args[i]));
int samplingRate = (int) inputAudio.getFormat().getSampleRate();
double[] signal = new AudioDoubleDataSource(inputAudio).getAllData();
FrameProvider fp = new FrameProvider(new BufferedDoubleDataSource(signal), null, 2048, 512, samplingRate, false);
double[] result = new double[signal.length];
int resultPos = 0;
while (fp.hasMoreData()) {
double[] frame = fp.getNextFrame();
if (fp.validSamplesInFrame() >= fp.getFrameShiftSamples()) {
System.arraycopy(frame, 0, result, resultPos, fp.getFrameShiftSamples());
resultPos += fp.getFrameShiftSamples();
} else {
System.arraycopy(frame, 0, result, resultPos, fp.validSamplesInFrame());
resultPos += fp.validSamplesInFrame();
}
}
System.err.println("Signal has length " + signal.length + ", result " + resultPos);
double err = MathUtils.sumSquaredError(signal, result);
System.err.println("Sum squared error: " + err);
if (err > 0.000001) {
double[] difference = MathUtils.subtract(signal, result);
FunctionGraph diffGraph = new SignalGraph(difference, samplingRate);
diffGraph.showInJFrame("difference", true, true);
}
}
}
}