/* * Copyright 1999-2004 Carnegie Mellon University. * Portions Copyright 2002-2004 Sun Microsystems, Inc. * Portions Copyright 2002-2004 Mitsubishi Electric Research Laboratories. * All Rights Reserved. Use is subject to license terms. * * See the file "license.terms" for information on usage and * redistribution of this file, and for a DISCLAIMER OF ALL * WARRANTIES. * */ package edu.cmu.sphinx.tools.audio; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.Arrays; /** Provides an interface to view and play back various forms of an audio signal. */ @SuppressWarnings("serial") public class AudioPanel extends JPanel implements MouseMotionListener, MouseListener { private final AudioData audio; private float[] labelTimes; private String[] labels; private float xScale; private final float yScale; private final float originalXScale; private int xDragStart; private int xDragEnd; protected int selectionStart = -1; protected int selectionEnd = -1; /** * Creates a new AudioPanel. The scale factors represent how much to scale the audio. A scaleX factor of 1.0f * means one pixel per sample, and a scaleY factor of 1.0f means one pixel per resolution of the sample (e.g., a * scale of 1.0f would take 2**16 pixels). * * @param audioData the AudioData to draw * @param scaleX how much to scale the width of the audio * @param scaleY how much to scale the height */ public AudioPanel(AudioData audioData, float scaleX, float scaleY) { this.audio = audioData; labelTimes = new float[0]; labels = new String[0]; this.xScale = scaleX; this.yScale = scaleY; this.originalXScale = this.xScale; int width = (int) (audio.getAudioData().length * xScale); int height = (int) ((1 << 16) * yScale); setPreferredSize(new Dimension(width, height)); setBackground(Color.white); audio.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { int width = (int) (audio.getAudioData().length * xScale); int height = (int) ((1 << 16) * yScale); labelTimes = new float[0]; labels = new String[0]; setSelectionStart(-1); setSelectionEnd(-1); setPreferredSize(new Dimension(width, height)); Dimension sz = getSize(); revalidate(); repaint(0, 0, 0, sz.width, sz.height); } }); addMouseMotionListener(this); addMouseListener(this); setFocusable(true); requestFocus(); } /** Sets the labels to be used when drawing this panel. * @param labelTimes label times * @param labels labels */ public void setLabels(float[] labelTimes, String[] labels) { this.labelTimes = labelTimes; this.labels = labels; repaint(); } /** Sets the zoom, adjusting the scroll bar in the process. * @param zoom float zoom */ protected void zoomSet(float zoom) { xScale = originalXScale * zoom; int width = (int) (audio.getAudioData().length * xScale); int height = (int) ((1 << 16) * yScale); setPreferredSize(new Dimension(width, height)); revalidate(); repaint(); } /** * Repaints the component with the given Graphics. * * @param g the Graphics to use to repaint the component. */ @Override public void paintComponent(Graphics g) { int pos, index; int length; super.paintComponent(g); Dimension sz = getSize(); int gZero = sz.height / 2; short[] audioData = audio.getAudioData(); /** * Only draw what is in the viewport. */ JViewport viewport = getViewport(); if (viewport != null) { Rectangle r = viewport.getViewRect(); pos = (int) r.getX(); length = (int) r.getWidth(); } else { pos = 0; length = (int) (audioData.length * xScale); } /** * Fill in the whole image with white. */ g.setColor(Color.WHITE); g.fillRect(pos, 0, length, sz.height - 1); /** * Now fill in the audio selection area as gray. */ index = Math.max(0, getSelectionStart()); int start = (int) (index * xScale); index = getSelectionEnd(); if (index == -1) { index = audioData.length - 1; } int end = (int) (index * xScale); g.setColor(Color.LIGHT_GRAY); g.fillRect(start, 0, end - start, sz.height - 1); /* Now scale the audio data and draw it. */ int[] x = new int[length]; int[] y = new int[length]; for (int i = 0; i < length; i++) { x[i] = pos; index = (int) (pos / xScale); if (index < audioData.length) { y[i] = gZero - (int) (audioData[index] * yScale); } else { break; } pos++; } g.setColor(Color.RED); g.drawPolyline(x, y, length); /** * Now draw the labels. */ for (int i = 0; i < labelTimes.length; i++) { pos = (int) (xScale * labelTimes[i] * audio.getAudioFormat().getSampleRate()); g.drawLine(pos, 0, pos, sz.height - 1); g.drawString(labels[i], pos + 5, sz.height - 5); } } /** Finds the JViewport enclosing this component. */ private JViewport getViewport() { Container p = getParent(); if (p instanceof JViewport) { Container gp = p.getParent(); if (gp instanceof JScrollPane) { JScrollPane scroller = (JScrollPane) gp; JViewport viewport = scroller.getViewport(); if (viewport == null || viewport.getView() != this) { return null; } else { return viewport; } } } return null; } /** * Returns the index of the sample representing the start of the selection. -1 means the very beginning. * * @return the start of the selection * @see #crop * @see #getSelectionEnd */ public int getSelectionStart() { return selectionStart; } /** * Sets the index of the sample of representing the start of the selection. -1 means the very beginning. * * @param newStart the new selection start * @see #crop * @see #setSelectionEnd */ public void setSelectionStart(int newStart) { selectionStart = newStart; if (selectionEnd != -1) { if (selectionEnd < selectionStart) { selectionEnd = selectionStart; } } } /** * Returns the index of the sample representing the end of the selection. -1 means the very end. * * @return the end of the selection * @see #crop * @see #getSelectionStart */ public int getSelectionEnd() { return selectionEnd; } /** * Sets the index of the sample of representing the end of the selection. -1 means the very end. * * @param newEnd the new selection end * @see #crop * @see #setSelectionStart */ public void setSelectionEnd(int newEnd) { selectionEnd = newEnd; if (selectionEnd != -1) { if (selectionStart > selectionEnd) { selectionStart = selectionEnd; } } } /** * Crops the audio data between the start and end selections. All audio data outside the region will be permanently * lost. The selection will be reset to the very beginning and very end of the cropped clip. * * @see #getSelectionStart * @see #getSelectionEnd */ public void crop() { short[] shorts = audio.getAudioData(); int start = Math.max(0, getSelectionStart()); int end = getSelectionEnd(); if (end == -1) { end = shorts.length; } audio.setAudioData(Arrays.copyOfRange(shorts, start, end)); setSelectionStart(-1); setSelectionEnd(-1); } /** Clears the current selection. */ public void selectAll() { setSelectionStart(-1); setSelectionEnd(-1); repaint(); } /** * When the mouse is pressed, we update the selection in the audio. * * @param evt the mouse pressed event */ public void mousePressed(MouseEvent evt) { xDragStart = Math.max(0, evt.getX()); setSelectionStart((int) (xDragStart / xScale)); setSelectionEnd((int) (xDragStart / xScale)); repaint(); } /** * When the mouse is dragged, we update the selection in the audio. * * @param evt the mouse dragged event */ public void mouseDragged(MouseEvent evt) { xDragEnd = evt.getX(); if (xDragEnd < (int) (getSelectionStart() * xScale)) { setSelectionStart((int) (xDragEnd / xScale)); } else { setSelectionEnd((int) (xDragEnd / xScale)); } repaint(); } public void mouseReleased(MouseEvent evt) { } public void mouseMoved(MouseEvent evt) { } public void mouseEntered(MouseEvent evt) { } public void mouseExited(MouseEvent evt) { } public void mouseClicked(MouseEvent evt) { } }