From 06374a28b77928463fabbefe320637b6cccfd8af Mon Sep 17 00:00:00 2001 From: Gianpaolo Coro Date: Tue, 30 Apr 2013 14:14:23 +0000 Subject: [PATCH] git-svn-id: https://svn.d4science.research-infrastructures.eu/gcube/trunk/data-analysis/EcologicalEngine@74267 82a268e6-3cf1-43bd-a215-b396298e98cf --- .../display/FunctionGraphCustom.java | 820 ++++++++++++++++++ .../signalproc/display/SpectrogramCustom.java | 742 ++++++++++++++++ 2 files changed, 1562 insertions(+) create mode 100644 src/main/java/marytts/signalproc/display/FunctionGraphCustom.java create mode 100644 src/main/java/marytts/signalproc/display/SpectrogramCustom.java diff --git a/src/main/java/marytts/signalproc/display/FunctionGraphCustom.java b/src/main/java/marytts/signalproc/display/FunctionGraphCustom.java new file mode 100644 index 0000000..536b604 --- /dev/null +++ b/src/main/java/marytts/signalproc/display/FunctionGraphCustom.java @@ -0,0 +1,820 @@ +/** + * 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 . + * + */ +package marytts.signalproc.display; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; + +import marytts.util.string.PrintfFormat; + + +/** + * @author Marc Schröder + * + */ +public class FunctionGraphCustom extends FunctionGraph +{ + public static final int DEFAULT_WIDTH=640; + public static final int DEFAULT_HEIGHT=480; + public static final int DRAW_LINE = 1; + public static final int DRAW_DOTS = 2; + public static final int DRAW_LINEWITHDOTS = 3; + public static final int DRAW_HISTOGRAM = 4; + public static final int DOT_FULLCIRCLE = 1; + public static final int DOT_FULLSQUARE = 2; + public static final int DOT_FULLDIAMOND = 3; + public static final int DOT_EMPTYCIRCLE = 11; + public static final int DOT_EMPTYSQUARE = 12; + public static final int DOT_EMPTYDIAMOND = 13; + + protected int paddingLeft = 40; + protected int paddingRight = 10; + protected int paddingTop = 10; + protected int paddingBottom = 40; + protected double x0; + protected double xStep; + protected List dataseries = new ArrayList(); + protected double ymin; + protected double ymax; + protected boolean showXAxis = true; + protected boolean showYAxis = true; + protected BufferedImage graphImage = null; + protected Color backgroundColor = Color.WHITE; + protected Color axisColor = Color.BLACK; + protected List graphColor = new ArrayList(); + protected Color histogramBorderColor = Color.BLACK; + protected List graphStyle = new ArrayList(); + protected List dotStyle = new ArrayList(); + protected int dotSize = 6; + protected int histogramWidth = 10; + protected boolean autoYMinMax = true; // automatically determine ymin and ymax + + // data to be used for drawing cursor et al on the GlassPane: + // x and y coordinates, in data space + protected DoublePoint positionCursor = new DoublePoint(); + protected DoublePoint rangeCursor = new DoublePoint(); + protected List cursorListeners = new ArrayList(); + + /** + * Display a 2d graph showing y(x), with labelled scales. + * This constructor is for subclasses only, which may need + * to perform some operations before calling initialise(). + */ + protected FunctionGraphCustom() + { + super(); + } + + /** + * Display a 2d graph showing y(x), with labelled scales. + */ + public FunctionGraphCustom(double x0, double xStep, double[] y) { + this(DEFAULT_WIDTH, DEFAULT_HEIGHT, x0, xStep, y); + } + + /** + * Display a 2d graph showing y(x), with labelled scales. + */ + public FunctionGraphCustom(int width, int height, + double x0, double xStep, double[] y) { + super(); + initialise(width, height, x0, xStep, y); + } + + public void initialise(int width, int height, + double newX0, double newXStep, double[] data) + { + setPreferredSize(new Dimension(width, height)); + setOpaque(true); + this.addMouseListener(new MouseListener() { + public void mouseClicked(MouseEvent e) { + //System.err.println("Mouse clicked"); + if (e.getButton() == MouseEvent.BUTTON1) { // left mouse button + // set position cursor; if we are to the right of rangeCursor, + // delete rangeCursor. + positionCursor.x = imageX2X(e.getX()-paddingLeft); + positionCursor.y = imageY2Y(getHeight()-paddingBottom-e.getY()); + if (!Double.isNaN(rangeCursor.x) && positionCursor.x > rangeCursor.x) { + rangeCursor.x = Double.NaN; + } + } else if (e.getButton() == MouseEvent.BUTTON3) { // right mouse button + // set range cursor, but only if we are to the right of positionCursor + rangeCursor.x = imageX2X(e.getX()-paddingLeft); + rangeCursor.y = imageY2Y(getHeight()-paddingBottom-e.getY()); + if (positionCursor.x > rangeCursor.x) { + rangeCursor.x = Double.NaN; + } + } + FunctionGraphCustom.this.notifyCursorListeners(); + FunctionGraphCustom.this.requestFocusInWindow(); + } + public void mousePressed(MouseEvent e) {} + public void mouseReleased(MouseEvent e) {} + public void mouseEntered(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + }); + updateData(newX0, newXStep, data); + // set styles for primary data series: + graphColor.add(Color.BLUE); + graphStyle.add(DRAW_LINE); + dotStyle.add(DOT_FULLCIRCLE); + } + + /** + * Replace the previous data with the given new data. + * Any secondary data series added using {{@link #addDataSeries(double[], Color, int, int)} are removed. + * @param newX0 x position of first data point + * @param newXStep distance between data points on X axis + * @param data all data points + */ + public void updateData(double newX0, double newXStep, double[] data) + { + if (newXStep <= 0) { + throw new IllegalArgumentException("newXStep must be >0"); + } + if (data == null || data.length == 0) { + throw new IllegalArgumentException("No data"); + } + this.x0 = newX0; + this.xStep = newXStep; + double[] series = new double[data.length]; + System.arraycopy(data, 0, series, 0, data.length); + // Do not allow old secondary data sets with a new primary one: + while (dataseries.size() > 0) { + dataseries.remove(0); + } + // Also remove the styles of the secondary data sets: + while (graphColor.size() > 1) { + graphColor.remove(1); + } + while (graphStyle.size() > 1) { + graphStyle.remove(1); + } + while (dotStyle.size() > 1) { + dotStyle.remove(1); + } + this.dataseries.add(0, series); + if (autoYMinMax) { + ymin = Double.NaN; + ymax = Double.NaN; + for (int i=0; i ymax) ymax = data[i]; + } + // If the x axis is painted in the middle (ymin << 0), + // we need much less paddingBottom: + if (ymin < 0) { + paddingBottom = paddingTop; + } + } + + // And invalidate any previous graph image: + graphImage = null; + } + + public void setPrimaryDataSeriesStyle(Color newGraphColor, int newGraphStyle, int newDotStyle) + { + graphColor.remove(0); + graphColor.add(0, newGraphColor); + graphStyle.remove(0); + graphStyle.add(0, newGraphStyle); + dotStyle.remove(0); + dotStyle.add(0, newDotStyle); + } + + /** + * Manually set the min and max values for the y axis. + * @param theYMin + * @param theYMax + */ + public void setYMinMax(double theYMin, double theYMax) + { + autoYMinMax = false; + ymin = theYMin; + ymax = theYMax; + // If the x axis is painted in the middle (ymin << 0), + // we need much less paddingBottom: + if (ymin < 0) { + paddingBottom = paddingTop; + } + } + + /** + * Add a secondary data series to this graph. + * @param data the function data, which must be of same length as the original data. {@link #updateData(double, double, double[])} + * @param newGraphColor a colour + * @param newGraphStyle the style for painting this data series. One of {@link #DRAW_LINE}, {@link #DRAW_DOTS}, {@value #DRAW_LINEWITHDOTS}, {@link #DRAW_HISTOGRAM}. + * @param newDotStyle the shape of any dots to use (meaningful only with newGraphStyle == {@link #DRAW_DOTS} or {@link #DRAW_LINEWITHDOTS}). + * One of {@link #DOT_EMPTYCIRCLE}, {@link #DOT_EMPTYDIAMOND}, {@link #DOT_EMPTYSQUARE}, {@link #DOT_FULLCIRCLE}, {@link #DOT_FULLDIAMOND}, {@link #DOT_FULLSQUARE}. + * For other graph styles, this is ignored, and it is recommended to set it to -1 for clarity. + */ + public void addDataSeries(double[] data, Color newGraphColor, int newGraphStyle, int newDotStyle) + { + if (data == null) throw new NullPointerException("Cannot add null data"); + if (dataseries.get(0).length != data.length) + throw new IllegalArgumentException("Can only add data of the exact same length as the original data series; len(orig)=" + +dataseries.get(0).length+", len(data)="+data.length); + double[] series = new double[data.length]; + System.arraycopy(data, 0, series, 0, data.length); + dataseries.add(series); + graphColor.add(newGraphColor); + graphStyle.add(newGraphStyle); + dotStyle.add(newDotStyle); + if (autoYMinMax) { + for (int i=0; i ymax) ymax = data[i]; + } + // If the x axis is painted in the middle (ymin << 0), + // we need much less paddingBottom: + if (ymin < 0) { + paddingBottom = paddingTop; + } + } + + // And invalidate any previous graph image: + graphImage = null; + } + + + public double getZoomX() + { + double[] data = dataseries.get(0); + double zoom = ((double)getPreferredSize().width-paddingLeft-paddingRight) / data.length; + //System.err.println("Current Zoom: " + zoom + "(pref. size: " + getPreferredSize().width + "x" + getPreferredSize().height + ")"); + return zoom; + } + + /** + * Set the zoom of the X + * @param factor the zoom factor for X; 1 means that each data point corresponds to one pixel; + * 0.5 means that 2 data points are mapped onto one pixel; etc. + */ + public void setZoomX(double factor) + { + //System.err.println("New zoom factor requested: " + factor); + // Old visible rectangle: + Rectangle r = getVisibleRect(); + int oldWidth = getPreferredSize().width; + double[] data = dataseries.get(0); + int newWidth = (int)(data.length*factor)+paddingLeft+paddingRight; + if (isVisible()) { + setVisible(false); + setPreferredSize(new Dimension(newWidth, getPreferredSize().height)); + // Only scroll to center of what was previous visible if not at left end: + if (r.x != 0) { + Rectangle newVisibleRect = new Rectangle((r.x+r.width/2-paddingLeft)*newWidth/oldWidth-r.width/2+paddingLeft, r.y, r.width, r.height); + scrollRectToVisible(newVisibleRect); + } + setVisible(true); + } else { + setPreferredSize(new Dimension(newWidth, getPreferredSize().height)); + createGraphImage(); + } + //System.err.print("updated "); + getZoomX(); + } + + protected void createGraphImage() + { + graphImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + if (graphImage == null) { + throw new NullPointerException("Cannot create image for drawing graph"); + } + Graphics2D g = (Graphics2D) graphImage.createGraphics(); + double width = getWidth(); + double height = getHeight(); + + int image_fromX = 0; + int image_toX = (int) width; + + g.setBackground(backgroundColor); + g.clearRect(0, 0, (int) width, (int) height); + g.setFont(new Font("Courier", 0, 10)); + // Now reduce the drawing area: + int startX = paddingLeft; + int startY = (int)height - paddingBottom; + width -= paddingLeft + paddingRight; + height -= paddingTop + paddingBottom; + // Make sure we are not trying to draw the function outside its area: + if (image_fromX < startX) image_fromX = startX; + if (image_toX > startX + width) image_toX = (int) (startX + width); + + + int image_y_origin; + if (getYRange() == 0) image_y_origin = startY; + else image_y_origin = startY - (int) ((-ymin/getYRange()) * height); + int image_x_origin = startX + (int) ((-x0/getXRange()) * width); + + // Draw the function itself: + if (getYRange() > 0) { + for (int s=0; s= image_y_origin && image_y_origin >= startY-height) { + drawXAxis(g, width, startX, startY, image_y_origin); + } else { // draw x axis at the bottom, even if that is not 0: + drawXAxis(g, width, startX, startY, startY); + } + } + + // Draw the y axis, if requested: + if (showYAxis) { + if (image_fromX <= image_x_origin && image_x_origin <= image_toX) { + drawYAxis(g, height, startX, startY, image_x_origin); + } else { // draw y axis at the left, even if that is not 0: + drawYAxis(g, height, startX, startY, startX); + } + } + } + + /** + * 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 + */ + 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); + if (index_fromX < 0) index_fromX = 0; + int index_toX = imageX2indexX(image_toX); + if (index_toX < data.length) index_toX += 20; + if (index_toX > data.length) index_toX = data.length; + //System.err.println("Drawing values " + index_fromX + " to " + index_toX + " of " + y.length); + double xo = 0.0; + double yo = 0.0; + double xp = 0.0; + double yp = 0.0; + g.setColor(currentGraphColor); + for (int i = index_fromX; i < index_toX; i++) { + if (!Double.isNaN(data[i])) { + xp = indexX2imageX(i); + yp = y2imageY(data[i]); + //System.err.println("Point "+i+": ("+(image_refX+(int)xp)+","+(image_refY-(int)yp)+")"); + if (currentGraphStyle == DRAW_LINE || currentGraphStyle == DRAW_LINEWITHDOTS) { + g.drawLine(image_refX+(int)xo, image_refY-(int)yo, image_refX+(int)xp, image_refY-(int)yp); + } + if (currentGraphStyle == DRAW_DOTS || currentGraphStyle == DRAW_LINEWITHDOTS) { + drawDot(g, image_refX+(int)xp, image_refY-(int)yp, currentDotStyle); + } + if (currentGraphStyle == DRAW_HISTOGRAM) { + int topY = image_refY; + if (yp>0) topY = image_refY-(int)yp; + int histHeight = (int) Math.abs(yp); + // cut to drawing area if x axis not at y==0: + if (topY+histHeight>startY) { + histHeight = startY-topY; + } + g.setColor(currentGraphColor); + g.fillRect(image_refX+(int)xp-histogramWidth/2, topY, histogramWidth, histHeight); + g.setColor(histogramBorderColor); + g.drawRect(image_refX+(int)xp-histogramWidth/2, topY, histogramWidth, histHeight); + } + xo = xp; + yo = yp; + } + } + } + + protected void drawDot(Graphics2D g, int x, int y, int currentDotStyle) + { + switch(currentDotStyle) { + case DOT_FULLCIRCLE: + g.fillOval(x-dotSize/2, y-dotSize/2, dotSize, dotSize); + break; + case DOT_FULLSQUARE: + g.fillRect(x-dotSize/2, y-dotSize/2, dotSize, dotSize); + break; + case DOT_FULLDIAMOND: + g.fillPolygon(new int[]{x-dotSize/2,x, x+dotSize/2, x}, + new int[]{y, y-dotSize/2, y, y+dotSize/2}, 4); + break; + case DOT_EMPTYCIRCLE: + g.drawOval(x-dotSize/2, y-dotSize/2, dotSize, dotSize); + break; + case DOT_EMPTYSQUARE: + g.drawRect(x-dotSize/2, y-dotSize/2, dotSize, dotSize); + break; + case DOT_EMPTYDIAMOND: + g.drawPolygon(new int[]{x-dotSize/2,x, x+dotSize/2, x}, + new int[]{y, y-dotSize/2, y, y+dotSize/2}, 4); + break; + default: + break; + } + } + + protected void drawYAxis(Graphics2D g, double height, int startX, int startY, int image_x_origin) { + g.setColor(axisColor); + double yRange = getYRange(); + g.drawLine(image_x_origin, startY, image_x_origin, startY-(int)height); + // Do not try to draw units if yRange is 0: + if (yRange == 0) return; + // Units on the y axis: + // major units with labels every 50-100 pixels + int unitOrder = (int)Math.floor(Math.log(yRange/5)/Math.log(10)); + double unitDistance = Math.pow(10, unitOrder); + double image_unitDistance = unitDistance/yRange * height; + if (image_unitDistance < 20) { + unitDistance *= 5; + } else if (image_unitDistance < 50) { + unitDistance *= 2; + } + double unitStart = ymin; + double modulo = ymin%unitDistance; + if (modulo != 0) { + if (modulo > 0) + unitStart += unitDistance - modulo; + else // < 0 + unitStart += Math.abs(modulo); + } + PrintfFormat labelFormat; + if (unitOrder > 0) { + labelFormat = new PrintfFormat("%.0f"); + } else { + labelFormat = new PrintfFormat("%." + (-unitOrder) + "f"); + } + boolean intLabels = ((int)unitDistance == (int)Math.ceil(unitDistance)); + //System.err.println("y axis: yRange=" + yRange + ", unitDistance=" + unitDistance + ", unitStart=" + unitStart + ", ymin=" + ymin + ", ymin%unitDistance=" + (ymin%unitDistance)); + for (double i=unitStart; i. + * + */ + +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.AudioFormat; +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.display.FunctionGraph; +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.data.audio.AudioDoubleDataSource; +import marytts.util.math.ArrayUtils; +import marytts.util.math.FFT; +import marytts.util.math.MathUtils; +import marytts.util.string.PrintfFormat; + + + +/** + * @author Marc Schröder + * + */ +public class SpectrogramCustom extends FunctionGraphCustom +{ + 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 spectra; + protected double spectra_max = 0.; + protected double spectra_min = 0.; + protected double deltaF = 0.; // distance in Hz between two spectrum samples + protected 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(); + for (int i=0;i(); + // Frequency resolution of the FFT: + deltaF = spectrumAnalyser.getFrequencyResolution(); + long startTime = System.currentTimeMillis(); + spectra_max = Double.NaN; + spectra_min = Double.NaN; + FrameBasedAnalyser.FrameAnalysisResult[] results = spectrumAnalyser.analyseAllFrames(); + for (int i=0; i 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= 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 labelTable = new Hashtable(); + for (int i=min; i<=max; i++) { + int twoPowI = 1< 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<= 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= 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; + } + } + +} + + + +