/**
* Copyright 2007 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.tools.redstart;
import java.io.File;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.AudioFormat.Encoding;
import marytts.signalproc.analysis.EnergyAnalyser;
import marytts.signalproc.analysis.EnergyAnalyser_dB;
import marytts.signalproc.analysis.FrameBasedAnalyser.FrameAnalysisResult;
import marytts.util.data.BufferedDoubleDataSource;
import marytts.util.data.audio.AudioDoubleDataSource;
import marytts.util.data.audio.AudioProcessor;
import marytts.util.data.audio.AudioRecorder;
import marytts.util.math.MathUtils;
/**
*
* @author Mat Wilson <mwilson@dfki.de>
*/
public class Recording extends Speech {
// ______________________________________________________________________
// Instance fields
// ______________________________________________________________________
// Class fields
public static final AudioFormat audioFormat = new AudioFormat(Encoding.PCM_SIGNED, 44100, 16, 1, 2, 44100, false);
public AudioRecorder.BufferingRecorder recorder = null;
public boolean isAmpClipped = false; // Boolean flag to indicate if recording is saturated (amplitude clipping occurred)
public boolean isTempClipped = false; // Boolean flag to indicate if temporal clipping occurred (no silence at either end)
public boolean isAmpWarning = false; // Boolean flag to indicate recording is close to being saturated
public static final double ampYellowThreshold = -1.5;
public static final double ampRedThreshold = -0.5;
// ______________________________________________________________________
// Instance methods
// ______________________________________________________________________
// Class methods
/**
* Record for a given number of milliseconds and save as a wav file
*
* @param line
* line
* @param inlineFilter
* inlineFilter
* @param millis
* millis
*/
public void timedRecord(TargetDataLine line, AudioProcessor inlineFilter, int millis) {
AudioFileFormat.Type targetType = AudioFileFormat.Type.WAVE;
recorder = new AudioRecorder.BufferingRecorder(line, targetType, getFile(), millis);
if (inlineFilter != null) {
recorder.setAudioProcessor(inlineFilter);
}
recorder.start();
try {
recorder.join();
recorder = null;
} catch (InterruptedException ie) {
}
}
/**
* Stop an ongoing recording before the time is up. This may be useful when the user presses the stop button.
*
* @return true when a recording was stopped, false if no recording was going on.
*/
public boolean stopRecording() {
if (recorder != null) {
recorder.stopRecordingNOW();
return true;
}
return false;
}
/**
* Rename the file (basename).wav by appending a suffix. Example: If spike0003 has three recordings already, then the
* assumption is that spike0003.wav, spike0003a.wav and spike0003b.wav are the names of the existing files. The new name for
* the most recent recording (spike0003.wav) will be spike0003c.wav. The newly recorded file will then take the spike0003.wav
* name.
*
*/
public void archiveLatestRecording() {
if (fileCount == 0)
return;
File latest = new File(filePath, basename + ".wav");
if (!latest.exists())
return;
int suffixCodeBase = 96; // Code point for the character before 'a'
// Need an upper boundary
int suffixCode = suffixCodeBase + fileCount;
if (fileCount > 26)
suffixCode = suffixCodeBase + 26;
File newName = new File(filePath, basename + ((char) suffixCode) + ".wav");
latest.renameTo(newName);
// TESTCODE
Test.output("|Recording.getRename| Renamed " + basename + " to " + newName.getPath());
}
public void checkForAmpClipping() {
File f = getFile();
if (!f.exists())
return;
double amplitude = getPeakAmplitude();
// System.err.println("Peak amplitude: "+amplitude+" dB");
if (amplitude >= ampRedThreshold) {
this.isAmpClipped = true;
this.isAmpWarning = false;
} else if (amplitude >= ampYellowThreshold) {
this.isAmpWarning = true;
this.isAmpClipped = false;
} else {
this.isAmpClipped = false;
this.isAmpWarning = false;
}
}
public void checkForTempClipping() {
File f = getFile();
if (!f.exists())
return;
this.isTempClipped = false;
try {
AudioInputStream ais = AudioSystem.getAudioInputStream(f);
double[] audio = new AudioDoubleDataSource(ais).getAllData();
int samplingRate = (int) ais.getFormat().getSampleRate();
int frameLength = (int) (0.005 * samplingRate); // each frame is 5 ms long
EnergyAnalyser silenceFinder = new EnergyAnalyser_dB(new BufferedDoubleDataSource(audio), frameLength, samplingRate);
FrameAnalysisResult[] energies = silenceFinder.analyseAllFrames();
if (energies.length < 20) { // too short anyway
this.isTempClipped = true;
return; // temporal clipping
}
double silenceCutoff = silenceFinder.getSilenceCutoffFromKMeansClustering(0.5, 4);
System.out.println("Silence cutoff: " + silenceCutoff);
// Need at least 100 ms of silence at the beginning and at the end:
double energy = 0;
for (int i = 0; i < 20; i++) {
energy += ((Double) energies[i].get()).doubleValue();
}
energy /= 20;
if (energy >= silenceCutoff) {
System.out.println("Initial clipping");
this.isTempClipped = true;
}
energy = 0;
for (int i = 1, len = energies.length; i <= 20; i++) {
energy += ((Double) energies[len - i].get()).doubleValue();
}
energy /= 20;
if (energy >= silenceCutoff) {
System.out.println("Final clipping");
this.isTempClipped = true;
}
} catch (Exception e) {
e.printStackTrace();
return;
}
}
public double getPeakAmplitude() {
File f = getFile();
assert f.exists();
try {
AudioInputStream ais = AudioSystem.getAudioInputStream(f);
double[] audio = new AudioDoubleDataSource(ais).getAllData();
double max = MathUtils.absMax(audio);
int bits = ais.getFormat().getSampleSizeInBits();
double possibleMax = 1.; // normalised scale
return MathUtils.db((max * max) / (possibleMax * possibleMax));
} catch (Exception e) {
e.printStackTrace();
return -30;
}
}
// ______________________________________________________________________
// Constructors
/**
* Creates a new instance of Recording
*
* @param filePath
* The file path for the wav recordings
* @param basename
* The basename for the currently selected prompt
*/
public Recording(File filePath, String basename) {
super(filePath, basename);
}
}