/** * Copyright 2004-2010 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.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.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.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 Spectrogram extends FunctionGraph { 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(), }; protected List<double[]> 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 Spectrogram(AudioInputStream ais) { this(ais, DEFAULT_WIDTH, DEFAULT_HEIGHT); } public Spectrogram(AudioInputStream ais, int width, int height) { this(ais, Window.get(DEFAULT_WINDOW, DEFAULT_WINDOWSIZE), DEFAULT_WINDOWSHIFT, DEFAULT_FFTSIZE, width, height); } public Spectrogram(AudioInputStream ais, Window window, int windowShift, int fftSize) { this(ais, window, windowShift, fftSize, DEFAULT_WIDTH, DEFAULT_HEIGHT); } public Spectrogram(AudioInputStream ais, Window window, int windowShift, int fftSize, int width, int height) { super(); if (!ais.getFormat().getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) { ais = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, ais); } if (ais.getFormat().getChannels() > 1) { throw new IllegalArgumentException("Can only deal with mono audio signals"); } if (!MathUtils.isPowerOfTwo(fftSize)) throw new IllegalArgumentException("fftSize must be a power of two"); AudioDoubleDataSource signalSource = new AudioDoubleDataSource(ais); initialise(signalSource.getAllData(), signalSource.getSamplingRate(), window, windowShift, fftSize, width, height); } public Spectrogram(double[] signal, int samplingRate) { this(signal, samplingRate, DEFAULT_WIDTH, DEFAULT_HEIGHT); } public Spectrogram(double[] signal, int samplingRate, int width, int height) { this(signal, samplingRate, Window.get(DEFAULT_WINDOW, DEFAULT_WINDOWSIZE), DEFAULT_WINDOWSHIFT, DEFAULT_FFTSIZE, width, height); } public Spectrogram(double[] signal, int samplingRate, Window window, int windowShift, int fftSize, int width, int height) { initialise(signal, samplingRate, window, windowShift, fftSize, width, height); } 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 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 * @param data * data * @param currentGraphColor * currentGraphColor * @param currentGraphStyle * current graph style * @param currentDotStyle * current dot style */ @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 (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)); // 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 != Spectrogram.this.fftSize) { Spectrogram.this.fftSize = newFftSize; Spectrogram.this.window = Window.get(Spectrogram.this.window.type(), newFftSize / 4 + 1); Spectrogram.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 != Spectrogram.this.window.type()) { Spectrogram.this.window = Window.get(newWindowType, Spectrogram.this.window.getLength()); Spectrogram.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])); Spectrogram signalSpectrum = new Spectrogram(ais); signalSpectrum.showInJFrame(args[i], true, true); } } /** * Determine the next free location for a dependent and put the window there. * * @param jf * 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; } } }