718 lines
31 KiB
Java
718 lines
31 KiB
Java
package marytts.signalproc.display;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.event.ItemEvent;
|
|
import java.awt.event.ItemListener;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseListener;
|
|
import java.io.File;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Hashtable;
|
|
import java.util.List;
|
|
|
|
import javax.sound.sampled.AudioInputStream;
|
|
import javax.sound.sampled.AudioSystem;
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.JCheckBox;
|
|
import javax.swing.JComboBox;
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JSlider;
|
|
import javax.swing.SwingUtilities;
|
|
import javax.swing.event.ChangeEvent;
|
|
import javax.swing.event.ChangeListener;
|
|
|
|
import marytts.signalproc.analysis.CepstrumSpeechAnalyser;
|
|
import marytts.signalproc.analysis.FrameBasedAnalyser;
|
|
import marytts.signalproc.analysis.LpcAnalyser;
|
|
import marytts.signalproc.analysis.ShortTermLogSpectrumAnalyser;
|
|
import marytts.signalproc.filter.FIRFilter;
|
|
import marytts.signalproc.window.HammingWindow;
|
|
import marytts.signalproc.window.RectWindow;
|
|
import marytts.signalproc.window.Window;
|
|
import marytts.util.data.BufferedDoubleDataSource;
|
|
import marytts.util.math.ArrayUtils;
|
|
import marytts.util.math.FFT;
|
|
import marytts.util.math.MathUtils;
|
|
import marytts.util.string.PrintfFormat;
|
|
|
|
|
|
|
|
public class SpectrogramCustom extends FunctionGraphCustom
|
|
{
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
public static final int DEFAULT_WINDOWSIZE = 65;
|
|
public static final int DEFAULT_WINDOW = Window.HAMMING;
|
|
public static final int DEFAULT_WINDOWSHIFT = 32;
|
|
public static final int DEFAULT_FFTSIZE = 256;
|
|
protected static final double PREEMPHASIS = 6.0; // dB per Octave
|
|
protected static final double DYNAMIC_RANGE = 40.0; // dB below global maximum to show
|
|
protected static final double FREQ_MAX = 8000.0; // Hz of upper limit frequency to show
|
|
|
|
protected double[] signal;
|
|
protected int samplingRate;
|
|
protected Window window;
|
|
protected int windowShift;
|
|
protected int fftSize;
|
|
protected GraphAtCursor[] graphsAtCursor = new GraphAtCursor[] {
|
|
new SpectrumAtCursor(),
|
|
new PhasogramAtCursor(),
|
|
new LPCAtCursor(),
|
|
new CepstrumAtCursor(),
|
|
};
|
|
|
|
public List<double[]> spectra;
|
|
protected double spectra_max = 0.;
|
|
protected double spectra_min = 0.;
|
|
protected double deltaF = 0.; // distance in Hz between two spectrum samples
|
|
public int spectra_indexmax = 0; // index in each spectrum corresponding to FREQ_MAX
|
|
|
|
|
|
|
|
public SpectrogramCustom(double[] signal, int samplingRate)
|
|
{
|
|
this(signal, samplingRate, DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
|
}
|
|
|
|
public SpectrogramCustom(double[] signal, int samplingRate, int width, int height)
|
|
{
|
|
this(signal, samplingRate, Window.get(DEFAULT_WINDOW, DEFAULT_WINDOWSIZE), DEFAULT_WINDOWSHIFT, DEFAULT_FFTSIZE, width, height);
|
|
}
|
|
|
|
public SpectrogramCustom(double[] signal, int samplingRate, Window window, int windowShift, int fftSize, int width, int height)
|
|
{
|
|
initialise(signal, samplingRate, window, windowShift, fftSize, width, height);
|
|
}
|
|
|
|
public SpectrogramCustom(double[][] spectrum, int samplingRate, int windowShift)
|
|
{
|
|
spectra = new ArrayList<double[]>();
|
|
for (int i=0;i<spectrum.length;i++){
|
|
spectra.add(spectrum[i]);
|
|
}
|
|
this.samplingRate = samplingRate;
|
|
this.fftSize = spectrum[0].length;
|
|
|
|
// spectra_indexmax = fftSize/2; // == spectra[i].length
|
|
super.dataseries=spectra;
|
|
// super.updateData(0, (double)windowShift/samplingRate, new double[spectra.size()]);
|
|
// correct y axis boundaries, for graph:
|
|
ymin = 0.;
|
|
ymax = fftSize;
|
|
|
|
repaint();
|
|
initialiseDependentWindows();
|
|
}
|
|
|
|
|
|
protected void initialise(double[] aSignal, int aSamplingRate, Window aWindow, int aWindowShift, int aFftSize, int width, int height)
|
|
{
|
|
this.signal = aSignal;
|
|
this.samplingRate = aSamplingRate;
|
|
this.window = aWindow;
|
|
this.windowShift = aWindowShift;
|
|
this.fftSize = aFftSize;
|
|
super.initialise(width, height, 0, (double)aWindowShift/aSamplingRate, new double[10]);
|
|
update();
|
|
initialiseDependentWindows();
|
|
}
|
|
|
|
|
|
protected void update()
|
|
{
|
|
ShortTermLogSpectrumAnalyser spectrumAnalyser = new ShortTermLogSpectrumAnalyser
|
|
(new BufferedDoubleDataSource(signal), fftSize, window, windowShift, samplingRate);
|
|
spectra = new ArrayList<double[]>();
|
|
// Frequency resolution of the FFT:
|
|
deltaF = spectrumAnalyser.getFrequencyResolution();
|
|
long startTime = System.currentTimeMillis();
|
|
spectra_max = Double.NaN;
|
|
spectra_min = Double.NaN;
|
|
FrameBasedAnalyser.FrameAnalysisResult<double[]>[] results = spectrumAnalyser.analyseAllFrames();
|
|
for (int i=0; i<results.length; i++) {
|
|
double[] spectrum = (double[]) results[i].get();
|
|
spectra.add(spectrum);
|
|
// Still do the preemphasis inline:
|
|
for (int j=0; j<spectrum.length; j++) {
|
|
double freqPreemphasis = PREEMPHASIS / Math.log(2) * Math.log((j+1)*deltaF/1000.);
|
|
spectrum[j] += freqPreemphasis;
|
|
if (Double.isNaN(spectra_min) || spectrum[j] < spectra_min) {
|
|
spectra_min = spectrum[j];
|
|
}
|
|
if (Double.isNaN(spectra_max) || spectrum[j] > spectra_max) {
|
|
spectra_max = spectrum[j];
|
|
}
|
|
}
|
|
}
|
|
long endTime = System.currentTimeMillis();
|
|
System.err.println("Computed " + spectra.size() + " spectra in " + (endTime-startTime) + " ms.");
|
|
|
|
spectra_indexmax = (int) (FREQ_MAX / deltaF);
|
|
if (spectra_indexmax > fftSize/2)
|
|
spectra_indexmax = fftSize/2; // == spectra[i].length
|
|
super.updateData(0, (double)windowShift/samplingRate, new double[spectra.size()]);
|
|
// correct y axis boundaries, for graph:
|
|
ymin = 0.;
|
|
ymax = spectra_indexmax * deltaF;
|
|
repaint();
|
|
}
|
|
|
|
protected void initialiseDependentWindows()
|
|
{
|
|
addMouseListener(new MouseListener() {
|
|
public void mouseClicked(MouseEvent e) {
|
|
int imageX = e.getX()-paddingLeft;
|
|
double x = imageX2X(imageX);
|
|
for (int i=0; i<graphsAtCursor.length; i++) {
|
|
if (graphsAtCursor[i].show) {
|
|
graphsAtCursor[i].update(x);
|
|
}
|
|
}
|
|
}
|
|
public void mousePressed(MouseEvent e) {}
|
|
public void mouseReleased(MouseEvent e) {}
|
|
public void mouseEntered(MouseEvent e) {}
|
|
public void mouseExited(MouseEvent e) {}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* While painting the graph, draw the actual function data.
|
|
* @param g the graphics2d object to paint in
|
|
* @param image_fromX first visible X coordinate of the Graph display area (= after subtracting space reserved for Y axis)
|
|
* @param image_toX last visible X coordinate of the Graph display area (= after subtracting space reserved for Y axis)
|
|
* @param image_refX X coordinate of the origin, in the display area
|
|
* @param image_refY Y coordinate of the origin, in the display area
|
|
* @param xScaleFactor conversion factor between data space and image space, image_x = xScaleFactor * data_x
|
|
* @param yScaleFactor conversion factor between data space and image space, image_y = yScaleFactor * data_y
|
|
* @param startY the start position on the Y axis (= the lower bound of the drawing area)
|
|
* @param image_height the height of the drawable region for the y values
|
|
*/
|
|
@Override
|
|
protected void drawData(Graphics2D g,
|
|
int image_fromX, int image_toX,
|
|
int image_refX, int image_refY,
|
|
int startY, int image_height,
|
|
double[] data, Color currentGraphColor, int currentGraphStyle, int currentDotStyle)
|
|
{
|
|
int index_fromX = imageX2indexX(image_fromX);
|
|
int index_toX = imageX2indexX(image_toX);
|
|
//System.err.println("Drawing spectra from image " + image_fromX + " to " + image_toX);
|
|
for (int i=index_fromX; i<index_toX; i++) {
|
|
//System.err.println("Drawing spectrum " + i);
|
|
int spectrumWidth = indexX2imageX(1);
|
|
if (spectrumWidth == 0) spectrumWidth = 1;
|
|
drawSpectrum(g, (double[])spectra.get(i), image_refX + indexX2imageX(i), spectrumWidth, image_refY, image_height);
|
|
}
|
|
}
|
|
|
|
protected void drawSpectrum(Graphics2D g, double[] spectrum, int image_X, int image_width, int image_refY, int image_height)
|
|
{
|
|
double yScaleFactor = (double) image_height / spectra_indexmax;
|
|
if (image_width < 2) image_width = 2;
|
|
int rect_height = (int) Math.ceil(yScaleFactor);
|
|
if (rect_height < 2) rect_height = 2;
|
|
for (int i=0; i<spectra_indexmax; i++) {
|
|
int color;
|
|
if (spectrum.length<=i || Double.isNaN(spectrum[i]) || spectrum[i] < spectra_max - DYNAMIC_RANGE) {
|
|
color = 255; // white
|
|
} else {
|
|
color = (int) (255 * (spectra_max-spectrum[i])/DYNAMIC_RANGE);
|
|
|
|
}
|
|
g.setColor(new Color(color, color, color));
|
|
g.fillRect(image_X, image_refY-(int)(i*yScaleFactor), image_width, rect_height);
|
|
}
|
|
}
|
|
|
|
public double[] getSpectrumAtTime(double t)
|
|
{
|
|
int index = (int) ((t-x0)/xStep);
|
|
if (index < 0 || index >= spectra.size()) {
|
|
return null;
|
|
}
|
|
return (double[]) spectra.get(index);
|
|
}
|
|
|
|
protected String getLabel(double x, double y)
|
|
{
|
|
int precisionX = -(int)(Math.log(getXRange())/Math.log(10)) + 2;
|
|
if (precisionX < 0) precisionX = 0;
|
|
int indexX = X2indexX(x);
|
|
double[] spectrum = (double[])spectra.get(indexX);
|
|
int precisionY = -(int)(Math.log(getYRange())/Math.log(10)) + 2;
|
|
if (precisionY < 0) precisionY = 0;
|
|
double E = spectrum[Y2indexY(y)];
|
|
int precisionE = 1;
|
|
return "E(" + new PrintfFormat("%."+precisionX+"f").sprintf(x)
|
|
+ "," + new PrintfFormat("%."+precisionY+"f").sprintf(y)
|
|
+ ")=" + new PrintfFormat("%."+precisionE+"f").sprintf(E);
|
|
|
|
}
|
|
|
|
protected int imageY2indexY(int imageY)
|
|
{
|
|
double y = imageY2Y(imageY);
|
|
return Y2indexY(y);
|
|
}
|
|
|
|
protected int Y2indexY(double y)
|
|
{
|
|
assert ymin == 0; // or we would have to write (ymax-ymin) or so below
|
|
return (int) (spectra_indexmax * y / ymax);
|
|
}
|
|
|
|
|
|
protected JPanel getControls()
|
|
{
|
|
/*
|
|
JPanel controls = new JPanel();
|
|
controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
|
|
|
|
// Controls for graphs at cursor:
|
|
for (int i=0; i<graphsAtCursor.length; i++) {
|
|
controls.add(graphsAtCursor[i].getControls());
|
|
}
|
|
|
|
return controls;
|
|
*/
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
protected JPanel getControls1()
|
|
{
|
|
JPanel controls = new JPanel();
|
|
controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
|
|
|
|
// FFT size slider:
|
|
JLabel fftLabel = new JLabel("FFT size:");
|
|
fftLabel.setAlignmentX(CENTER_ALIGNMENT);
|
|
controls.add(fftLabel);
|
|
int min = 5;
|
|
int max = 13;
|
|
int deflt = (int) (Math.log(this.fftSize) / Math.log(2));
|
|
JSlider fftSizeSlider = new JSlider(JSlider.VERTICAL, min, max, deflt);
|
|
fftSizeSlider.setAlignmentX(CENTER_ALIGNMENT);
|
|
fftSizeSlider.setMajorTickSpacing(1);
|
|
fftSizeSlider.setPaintTicks(true);
|
|
fftSizeSlider.setSnapToTicks(true);
|
|
Hashtable<Integer, JLabel> labelTable = new Hashtable<Integer, JLabel>();
|
|
for (int i=min; i<=max; i++) {
|
|
int twoPowI = 1<<i; // 2^i, e.g. i==8 => twoPowI==256
|
|
labelTable.put(new Integer(i), new JLabel(String.valueOf(twoPowI)));
|
|
}
|
|
fftSizeSlider.setLabelTable(labelTable);
|
|
fftSizeSlider.setPaintLabels(true);
|
|
fftSizeSlider.addChangeListener(new ChangeListener() {
|
|
public void stateChanged(ChangeEvent ce)
|
|
{
|
|
JSlider source = (JSlider)ce.getSource();
|
|
if (!source.getValueIsAdjusting()) {
|
|
int logfftSize = (int)source.getValue();
|
|
int newFftSize = 1<<logfftSize;
|
|
if (newFftSize != SpectrogramCustom.this.fftSize) {
|
|
SpectrogramCustom.this.fftSize = newFftSize;
|
|
SpectrogramCustom.this.window = Window.get(SpectrogramCustom.this.window.type(), newFftSize/4+1);
|
|
SpectrogramCustom.this.update();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
controls.add(fftSizeSlider);
|
|
|
|
// Window type:
|
|
JLabel windowTypeLabel = new JLabel("Window type:");
|
|
windowTypeLabel.setAlignmentX(CENTER_ALIGNMENT);
|
|
controls.add(windowTypeLabel);
|
|
int[] windowTypes = Window.getAvailableTypes();
|
|
Window[] windows = new Window[windowTypes.length];
|
|
int selected = 0;
|
|
for (int i=0; i<windowTypes.length; i++) {
|
|
windows[i] = Window.get(windowTypes[i], 1);
|
|
if (windowTypes[i] == this.window.type()) selected = i;
|
|
}
|
|
JComboBox windowList = new JComboBox(windows);
|
|
windowList.setAlignmentX(CENTER_ALIGNMENT);
|
|
windowList.setSelectedIndex(selected);
|
|
windowList.setMaximumSize(windowList.getPreferredSize());
|
|
windowList.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
JComboBox cb = (JComboBox)e.getSource();
|
|
int newWindowType = ((Window)cb.getSelectedItem()).type();
|
|
if (newWindowType != SpectrogramCustom.this.window.type()) {
|
|
SpectrogramCustom.this.window = Window.get(newWindowType, SpectrogramCustom.this.window.getLength());
|
|
// Spectrogram2.this.update();
|
|
}
|
|
}
|
|
});
|
|
controls.add(windowList);
|
|
|
|
// Controls for graphs at cursor:
|
|
for (int i=0; i<graphsAtCursor.length; i++) {
|
|
controls.add(graphsAtCursor[i].getControls());
|
|
}
|
|
|
|
return controls;
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception
|
|
{
|
|
for (int i=0; i<args.length; i++) {
|
|
AudioInputStream ais = AudioSystem.getAudioInputStream(new File(args[i]));
|
|
// Spectrogram2 signalSpectrum = new Spectrogram2(ais);
|
|
// signalSpectrum.showInJFrame(args[i], true, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine the next free location for a dependent and put the window there.
|
|
* @param jf
|
|
*/
|
|
protected void setDependentWindowLocation(JFrame jf)
|
|
{
|
|
if (nextDependentWindowX == 0 && nextDependentWindowY == 0) {
|
|
// first dependent window:
|
|
nextDependentWindowX = getTopLevelAncestor().getWidth();
|
|
}
|
|
jf.setLocationRelativeTo(this);
|
|
jf.setLocation(nextDependentWindowX, nextDependentWindowY);
|
|
nextDependentWindowY += jf.getHeight();
|
|
}
|
|
private static int nextDependentWindowX;
|
|
private static int nextDependentWindowY;
|
|
|
|
public abstract class GraphAtCursor
|
|
{
|
|
private JPanel controls;
|
|
protected FunctionGraph graph;
|
|
|
|
protected boolean show = false;
|
|
|
|
public abstract void update(double x);
|
|
|
|
public JPanel getControls()
|
|
{
|
|
if (controls == null) {
|
|
controls = createControls();
|
|
}
|
|
return controls;
|
|
}
|
|
protected abstract JPanel createControls();
|
|
protected void updateGraph(FunctionGraph someGraph, String title) {
|
|
if (someGraph.getParent() == null) {
|
|
JFrame jf = someGraph.showInJFrame(title, 400, 250, false, false);
|
|
setDependentWindowLocation(jf);
|
|
} else {
|
|
JFrame jf = (JFrame) SwingUtilities.getWindowAncestor(someGraph);
|
|
jf.setTitle(title);
|
|
jf.setVisible(true); // just to be sure
|
|
someGraph.repaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class SpectrumAtCursor extends GraphAtCursor
|
|
{
|
|
public void update(double x)
|
|
{
|
|
if (Double.isNaN(x)) return;
|
|
int centerIndex = (int) (x * samplingRate);
|
|
assert centerIndex >= 0 && centerIndex < signal.length;
|
|
int windowLength = 1024;
|
|
int leftIndex = centerIndex - windowLength/2;
|
|
if (leftIndex < 0) leftIndex = 0;
|
|
double[] signalExcerpt = new HammingWindow(windowLength).apply(signal, leftIndex);
|
|
double[] spectrum = FFT.computeLogPowerSpectrum(signalExcerpt);
|
|
if (graph == null) {
|
|
graph = new FunctionGraph(300, 200, 0, samplingRate/windowLength, spectrum);
|
|
} else {
|
|
graph.updateData(0, samplingRate/windowLength, spectrum);
|
|
}
|
|
super.updateGraph(graph, "Spectrum at "+new PrintfFormat("%.3f").sprintf(x)+" s");
|
|
}
|
|
|
|
protected JPanel createControls()
|
|
{
|
|
JPanel controls = new JPanel();
|
|
JCheckBox checkSpectrum = new JCheckBox("Show spectrum");
|
|
checkSpectrum.setAlignmentX(CENTER_ALIGNMENT);
|
|
checkSpectrum.setSelected(show);
|
|
checkSpectrum.addItemListener(new ItemListener() {
|
|
public void itemStateChanged(ItemEvent e) {
|
|
if (e.getStateChange() == ItemEvent.DESELECTED) {
|
|
show = false;
|
|
if (graph != null)
|
|
graph.getTopLevelAncestor().setVisible(false);
|
|
} else if (e.getStateChange() == ItemEvent.SELECTED) {
|
|
show = true;
|
|
update(positionCursor.x);
|
|
if (graph != null) {
|
|
graph.getTopLevelAncestor().setVisible(true);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
controls.add(checkSpectrum);
|
|
return controls;
|
|
}
|
|
}
|
|
|
|
public class PhasogramAtCursor extends GraphAtCursor
|
|
{
|
|
public void update(double x)
|
|
{
|
|
if (Double.isNaN(x)) return;
|
|
int centerIndex = (int) (x * samplingRate);
|
|
assert centerIndex >= 0 && centerIndex < signal.length;
|
|
// Want to show a phasogram of 10 ms centered around cursor position:
|
|
int halfWindowLength = samplingRate / 200;
|
|
double[] signalExcerpt;
|
|
if (graph == null) {
|
|
signalExcerpt = new double[2*halfWindowLength+Phasogram.DEFAULT_FFTSIZE];
|
|
} else {
|
|
assert graph instanceof Phasogram;
|
|
signalExcerpt = ((Phasogram)graph).signal;
|
|
}
|
|
int leftIndex = centerIndex - halfWindowLength;
|
|
if (leftIndex < 0) leftIndex = 0;
|
|
int len = signalExcerpt.length;
|
|
if (leftIndex + len >= signal.length)
|
|
len = signal.length - leftIndex;
|
|
System.arraycopy(signal, leftIndex, signalExcerpt, 0, len);
|
|
//System.err.println("Copied excerpt from signal pos " + leftIndex + ", len " + len);
|
|
if (len < signalExcerpt.length) {
|
|
Arrays.fill(signalExcerpt, len, signalExcerpt.length, 0);
|
|
}
|
|
|
|
if (graph == null) {
|
|
graph = new Phasogram(signalExcerpt, samplingRate, 300, 200);
|
|
} else {
|
|
((Phasogram)graph).update();
|
|
}
|
|
super.updateGraph(graph, "Phasogram at "+new PrintfFormat("%.3f").sprintf(x)+" s");
|
|
}
|
|
|
|
protected JPanel createControls()
|
|
{
|
|
JPanel controls = new JPanel();
|
|
JCheckBox checkPhasogram = new JCheckBox("Show phasogram");
|
|
checkPhasogram.setAlignmentX(CENTER_ALIGNMENT);
|
|
checkPhasogram.setSelected(show);
|
|
checkPhasogram.addItemListener(new ItemListener() {
|
|
public void itemStateChanged(ItemEvent e) {
|
|
if (e.getStateChange() == ItemEvent.DESELECTED) {
|
|
show = false;
|
|
if (graph != null)
|
|
graph.getTopLevelAncestor().setVisible(false);
|
|
} else if (e.getStateChange() == ItemEvent.SELECTED) {
|
|
show= true;
|
|
update(positionCursor.x);
|
|
if (graph != null)
|
|
graph.getTopLevelAncestor().setVisible(true);
|
|
}
|
|
}
|
|
});
|
|
controls.add(checkPhasogram);
|
|
return controls;
|
|
}
|
|
}
|
|
|
|
public class LPCAtCursor extends GraphAtCursor
|
|
{
|
|
protected int lpcOrder = 50;
|
|
protected SignalGraph lpcResidueAtCursor = null;
|
|
public void update(double x)
|
|
{
|
|
if (Double.isNaN(x)) return;
|
|
int centerIndex = (int) (x * samplingRate);
|
|
assert centerIndex >= 0 && centerIndex < signal.length;
|
|
int windowLength = 1024;
|
|
int leftIndex = centerIndex - windowLength/2;
|
|
if (leftIndex < 0) leftIndex = 0;
|
|
double[] signalExcerpt = new HammingWindow(windowLength).apply(signal, leftIndex);
|
|
LpcAnalyser.LpCoeffs lpc = LpcAnalyser.calcLPC(signalExcerpt, lpcOrder);
|
|
double[] coeffs = lpc.getOneMinusA();
|
|
double g_db = 2*MathUtils.db(lpc.getGain()); // *2 because g is signal, not energy
|
|
double[] fftCoeffs = new double[windowLength];
|
|
System.arraycopy(coeffs, 0, fftCoeffs, 0, coeffs.length);
|
|
double[] lpcSpectrum = FFT.computeLogPowerSpectrum(fftCoeffs);
|
|
for (int i=0; i<lpcSpectrum.length; i++) {
|
|
lpcSpectrum[i] = -lpcSpectrum[i] + g_db;
|
|
}
|
|
|
|
if (graph == null) {
|
|
graph = new FunctionGraph(300, 200, 0, samplingRate/windowLength, lpcSpectrum);
|
|
} else {
|
|
graph.updateData(0, samplingRate/windowLength, lpcSpectrum);
|
|
}
|
|
updateGraph(graph, "LPC spectrum (order "+lpcOrder+") at "+new PrintfFormat("%.3f").sprintf(x)+" s");
|
|
|
|
// And the residue:
|
|
FIRFilter whiteningFilter = new FIRFilter(coeffs);
|
|
double[] signalExcerpt2 = new RectWindow(lpcOrder+windowLength).apply(signal, leftIndex-lpcOrder);
|
|
double[] residue = whiteningFilter.apply(signalExcerpt2);
|
|
double[] usableSignal = ArrayUtils.subarray(signalExcerpt2, lpcOrder, windowLength);
|
|
double[] usableResidue = ArrayUtils.subarray(residue, lpcOrder, windowLength);
|
|
double predictionGain = MathUtils.db(MathUtils.sum(MathUtils.multiply(usableSignal, usableSignal))
|
|
/ MathUtils.sum(MathUtils.multiply(usableResidue, usableResidue)));
|
|
System.err.println("LPC prediction gain: " + predictionGain + " dB");
|
|
if (lpcResidueAtCursor == null) {
|
|
lpcResidueAtCursor = new SignalGraph(usableResidue, samplingRate, 300, 200);
|
|
} else {
|
|
lpcResidueAtCursor.update(usableResidue, samplingRate);
|
|
}
|
|
super.updateGraph(lpcResidueAtCursor, "LPC residue at "+new PrintfFormat("%.3f").sprintf(x)+" s");
|
|
}
|
|
|
|
protected JPanel createControls()
|
|
{
|
|
JPanel controls = new JPanel();
|
|
controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
|
|
JCheckBox checkLPC = new JCheckBox("Show LPC");
|
|
checkLPC.setAlignmentX(CENTER_ALIGNMENT);
|
|
checkLPC.setSelected(show);
|
|
checkLPC.addItemListener(new ItemListener() {
|
|
public void itemStateChanged(ItemEvent e) {
|
|
if (e.getStateChange() == ItemEvent.DESELECTED) {
|
|
show = false;
|
|
if (graph != null)
|
|
graph.getTopLevelAncestor().setVisible(false);
|
|
if (lpcResidueAtCursor != null)
|
|
lpcResidueAtCursor.getTopLevelAncestor().setVisible(false);
|
|
} else if (e.getStateChange() == ItemEvent.SELECTED) {
|
|
show = true;
|
|
update(positionCursor.x);
|
|
if (graph != null)
|
|
graph.getTopLevelAncestor().setVisible(true);
|
|
if (lpcResidueAtCursor != null)
|
|
lpcResidueAtCursor.getTopLevelAncestor().setVisible(true);
|
|
}
|
|
}
|
|
});
|
|
controls.add(checkLPC);
|
|
// LPC order slider:
|
|
JLabel lpcLabel = new JLabel("LPC order:");
|
|
lpcLabel.setAlignmentX(CENTER_ALIGNMENT);
|
|
controls.add(lpcLabel);
|
|
int min = 1;
|
|
int max = 100;
|
|
JSlider lpcSlider = new JSlider(JSlider.HORIZONTAL, min, max, lpcOrder);
|
|
lpcSlider.setAlignmentX(CENTER_ALIGNMENT);
|
|
lpcSlider.addChangeListener(new ChangeListener() {
|
|
public void stateChanged(ChangeEvent ce)
|
|
{
|
|
JSlider source = (JSlider)ce.getSource();
|
|
if (!source.getValueIsAdjusting()) {
|
|
lpcOrder = (int)source.getValue();
|
|
System.err.println("Adjusted lpc order to " + lpcOrder);
|
|
if (show) update(positionCursor.x);
|
|
}
|
|
}
|
|
});
|
|
controls.add(lpcSlider);
|
|
return controls;
|
|
}
|
|
}
|
|
|
|
public class CepstrumAtCursor extends GraphAtCursor
|
|
{
|
|
protected int cepstrumCutoff = 50;
|
|
protected FunctionGraph cepstrumSpectrumAtCursor = null;
|
|
|
|
public void update(double x)
|
|
{
|
|
if (Double.isNaN(x)) return;
|
|
int centerIndex = (int) (x * samplingRate);
|
|
assert centerIndex >= 0 && centerIndex < signal.length;
|
|
int windowLength = 1024;
|
|
int leftIndex = centerIndex - windowLength/2;
|
|
if (leftIndex < 0) leftIndex = 0;
|
|
// Create a zero-padded version of the signal excerpt:
|
|
double[] signalExcerpt = new double[2*windowLength];
|
|
new HammingWindow(windowLength).apply(signal, leftIndex, signalExcerpt, 0);
|
|
double[] realCepstrum = CepstrumSpeechAnalyser.realCepstrum(signalExcerpt);
|
|
if (graph == null) {
|
|
graph = new FunctionGraph(300, 200, 0, samplingRate, realCepstrum);
|
|
} else {
|
|
graph.updateData(0, samplingRate, realCepstrum);
|
|
}
|
|
super.updateGraph(graph, "Cepstrum at "+new PrintfFormat("%.3f").sprintf(x)+" s");
|
|
|
|
// And the spectral envelope computed from a low-pass cut-off version of the cepstrum:
|
|
double[] lowCepstrum = CepstrumSpeechAnalyser.filterLowPass(realCepstrum, cepstrumCutoff);
|
|
double[] real = lowCepstrum;
|
|
double[] imag = new double[real.length];
|
|
FFT.transform(real, imag, false);
|
|
double[] cepstrumSpectrum = ArrayUtils.subarray(real, 0, real.length/2);
|
|
if (cepstrumSpectrumAtCursor == null) {
|
|
cepstrumSpectrumAtCursor = new FunctionGraph(300, 200, 0, samplingRate/real.length, cepstrumSpectrum);
|
|
} else {
|
|
cepstrumSpectrumAtCursor.updateData(0, samplingRate/real.length, cepstrumSpectrum);
|
|
}
|
|
super.updateGraph(cepstrumSpectrumAtCursor, "Cepstrum spectrum (cutoff "+cepstrumCutoff+") at "+new PrintfFormat("%.3f").sprintf(x)+" s");
|
|
}
|
|
|
|
protected JPanel createControls()
|
|
{
|
|
JPanel controls = new JPanel();
|
|
controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
|
|
JCheckBox checkCepstrum = new JCheckBox("Show Cepstrum");
|
|
checkCepstrum.setAlignmentX(CENTER_ALIGNMENT);
|
|
checkCepstrum.setSelected(show);
|
|
checkCepstrum.addItemListener(new ItemListener() {
|
|
public void itemStateChanged(ItemEvent e) {
|
|
if (e.getStateChange() == ItemEvent.DESELECTED) {
|
|
show = false;
|
|
if (graph != null)
|
|
graph.getTopLevelAncestor().setVisible(false);
|
|
if (cepstrumSpectrumAtCursor != null)
|
|
cepstrumSpectrumAtCursor.getTopLevelAncestor().setVisible(false);
|
|
} else if (e.getStateChange() == ItemEvent.SELECTED) {
|
|
show = true;
|
|
update(positionCursor.x);
|
|
if (graph != null)
|
|
graph.getTopLevelAncestor().setVisible(true);
|
|
if (cepstrumSpectrumAtCursor != null)
|
|
cepstrumSpectrumAtCursor.getTopLevelAncestor().setVisible(true);
|
|
}
|
|
}
|
|
});
|
|
controls.add(checkCepstrum);
|
|
// Cepstrum cutoff slider:
|
|
JLabel cepstrumLabel = new JLabel("Cepstrum cutoff:");
|
|
cepstrumLabel.setAlignmentX(CENTER_ALIGNMENT);
|
|
controls.add(cepstrumLabel);
|
|
int min = 1;
|
|
int max = 256;
|
|
JSlider cepstrumSlider = new JSlider(JSlider.HORIZONTAL, min, max, cepstrumCutoff);
|
|
cepstrumSlider.setAlignmentX(CENTER_ALIGNMENT);
|
|
cepstrumSlider.addChangeListener(new ChangeListener() {
|
|
public void stateChanged(ChangeEvent ce)
|
|
{
|
|
JSlider source = (JSlider)ce.getSource();
|
|
if (!source.getValueIsAdjusting()) {
|
|
cepstrumCutoff = (int)source.getValue();
|
|
System.err.println("Adjusted cepstrum cutoff to " + cepstrumCutoff);
|
|
if (show) update(positionCursor.x);
|
|
}
|
|
}
|
|
});
|
|
controls.add(cepstrumSlider);
|
|
return controls;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|