/**********************************************************************************
* $URL$
* $Id$
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
/* AudioSamplePanel.java
* Originally based on code from CapturePlayback.java
* @(#)CapturePlayback.java 1.11 99/12/03
*
* portions Copyright (c) 1999 Sun Microsystems, Inc. All Rights Reserved.
*
* Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
* modify and redistribute this software in source and binary code form,
* provided that i) this copyright notice and license appear on all copies of
* the software; and ii) Licensee does not utilize the software in a manner
* which is disparaging to Sun.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* This software is not designed or intended for use in on-line control of
* aircraft, air traffic, aircraft navigation or aircraft communications; or in
* the design, construction, operation or maintenance of any nuclear
* facility. Licensee represents and warrants that it will not use or
* redistribute the Software for such purposes.
*/
package org.sakaiproject.tool.assessment.audio;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ResourceBundle;
import java.util.Vector;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Line2D;
import javax.swing.JPanel;
/**
* Render a WaveForm.
*/
public class AudioSampleGraphPanel
extends JPanel
{
private static final long serialVersionUID = 0L;
static ResourceBundle res = AudioUtil.getInstance().getResourceBundle();
static ColorModel colorModel= new ColorModel();
private static final Font font10 = new Font("serif", Font.PLAIN, 10);
private static final Font font12 = new Font("serif", Font.PLAIN, 12);
private static final Color graphColor = colorModel.getColor("graphColor");//new Color(0, 180, 20);
private static final Color currentPositionColor = colorModel.getColor("graphCurrentPositionColor");// Color(64, 200, 20);
private static final Color backgroundColor = colorModel.getColor("graphBackgroundColor");//new Color(0, 128, 20);
private static final Color gridColor = colorModel.getColor("graphGridColor");// new Color(0, 140, 20);
public AudioSampleGraphPanel()
{
setBackground(backgroundColor);
}
public void reportGraphStatus(String msg)
{
System.out.println("Status: " + msg);
}
public void createWaveForm(
byte[] audioBytes, Vector<Line2D> lines, AudioInputStream audioInputStream)
{
lines.removeAllElements(); // clear the old vector
AudioFormat format = audioInputStream.getFormat();
if (audioBytes == null)
{
try
{
audioBytes = new byte[
(int) (audioInputStream.getFrameLength()
* format.getFrameSize())];
audioInputStream.read(audioBytes);
}
catch (Exception ex)
{
reportGraphStatus(ex.toString());
return;
}
}
Dimension d = getParent().getSize();
int w = d.width;
int h = d.height - 15;
int[] audioData = null;
if (format.getSampleSizeInBits() == 16)
{
int nlengthInSamples = audioBytes.length / 2;
audioData = new int[nlengthInSamples];
if (format.isBigEndian())
{
for (int i = 0; i < nlengthInSamples; i++)
{
/* First byte is MSB (high order) */
int MSB = (int) audioBytes[2 * i];
/* Second byte is LSB (low order) */
int LSB = (int) audioBytes[2 * i + 1];
audioData[i] = MSB << 8 | (255 & LSB);
}
}
else
{
for (int i = 0; i < nlengthInSamples; i++)
{
/* First byte is LSB (low order) */
int LSB = (int) audioBytes[2 * i];
/* Second byte is MSB (high order) */
int MSB = (int) audioBytes[2 * i + 1];
audioData[i] = MSB << 8 | (255 & LSB);
}
}
}
else if (format.getSampleSizeInBits() == 8)
{
int nlengthInSamples = audioBytes.length;
audioData = new int[nlengthInSamples];
if (format.getEncoding().toString().startsWith(res.getString("PCM_SIGN")))
{
for (int i = 0; i < audioBytes.length; i++)
{
audioData[i] = audioBytes[i];
}
}
else
{
for (int i = 0; i < audioBytes.length; i++)
{
audioData[i] = audioBytes[i] - 128;
}
}
}
int frames_per_pixel = audioBytes.length / format.getFrameSize() / w;
byte my_byte = 0;
double y_last = 0;
int numChannels = format.getChannels();
// we can normalize the waveform in the display by finding the signal peak
// and then we calculate a scale factor to use when drawing
int signalPeak = 0;
for (double x = 0; x < w && audioData != null; x++)
{
int idx = (int) (frames_per_pixel * numChannels * x);
if (format.getSampleSizeInBits() == 8)
{
my_byte = (byte) audioData[idx];
}
else
{
my_byte = (byte) (128 * audioData[idx] / 32768);
}
if (Math.abs(my_byte) > signalPeak) {
signalPeak = Math.abs(my_byte);
}
}
double scaleFactor = 128 / (double)signalPeak;
for (double x = 0; x < w && audioData != null; x++)
{
int idx = (int) (frames_per_pixel * numChannels * x);
if (format.getSampleSizeInBits() == 8)
{
my_byte = (byte) audioData[idx];
}
else
{
my_byte = (byte) (128 * audioData[idx] / 32768);
}
double y_new = (double) (h * (128 - (my_byte * scaleFactor)) / 256);
lines.add(new Line2D.Double(x, y_last, x, y_new));
y_last = y_new;
}
repaint();
}
public void paintData(Graphics g, AudioSamplingData data)
{
Vector<Line2D> lines;
AudioInputStream audioInputStream = data.getAudioInputStream();
String errStr;
Runnable capture;
Thread captureThread;
double seconds;
String fileName;
double duration;
double maxSeconds;
lines = data.getLine();
errStr = data.getErrStr();
capture = data.getCapture();
captureThread = data.getCaptureThread();
seconds = data.getSeconds();
fileName = data.getFileName();
duration = data.getDuration();
maxSeconds = data.getMaxSeconds();
//System.out.println("*** seconds="+seconds);
//System.out.println("*** duration="+duration);
Dimension d = getSize();
int w = d.width;
int h = d.height;
int INFOPAD = 15;
Graphics2D g2 = (Graphics2D) g;
g2.setBackground(getBackground());
g2.clearRect(0, 0, w, h);
g2.setColor(colorModel.getColor("darkColor"));
g2.fillRect(0, h - INFOPAD, w, INFOPAD);
int gridHeight = h - INFOPAD - 2;
drawGrid(g2, w, gridHeight);
if (errStr != null)
{
drawErrorText(errStr, w, g2);
}
else if (captureThread != null)
{
if (seconds > maxSeconds)
drawLengthText(maxSeconds, h, g2);
else
drawLengthText(seconds, h, g2);
}
else
{
if (duration > maxSeconds)
drawFileLengthText(seconds, fileName, maxSeconds, h, g2);
else
drawFileLengthText(seconds, fileName, duration, h, g2);
if (audioInputStream != null)
{
drawSamplingGraph(lines, g2);
if (seconds != 0)
{
drawCurrentPosition(seconds, duration, w, h, INFOPAD, g2);
}
}
}
}
private void drawGrid(Graphics2D g2, int w, int h)
{
g2.setColor(gridColor);
for (int x = 0; x < w; x += 10)
{
g2.draw(new Line2D.Double(x, 0, x, h));
}
for (int y = 0; y < h; y += 10)
{
g2.draw(new Line2D.Double(0, y, w, y));
}
}
private void drawCurrentPosition(double seconds, double duration, int w,
int h, int INFOPAD, Graphics2D g2)
{
double loc = seconds / duration * w;
g2.setColor(currentPositionColor);
g2.setStroke(new BasicStroke(3));
g2.draw(new Line2D.Double(loc, 0, loc, h - INFOPAD - 2));
}
private void drawSamplingGraph(Vector<Line2D> lines, Graphics2D g2)
{
g2.setColor(graphColor);
for (int i = 1; i < lines.size(); i++)
{
g2.draw( (Line2D) lines.get(i));
}
}
private void drawFileLengthText(double seconds, String fileName, double duration,
int h, Graphics2D g2)
{
g2.setColor(graphColor);
g2.setFont(font12);
g2.drawString(res.getString("Length_1") +
String.valueOf(duration), 3, h - 4);
/*
g2.drawString(res.getString("File_") + fileName + " " +
res.getString("Length_1") +
String.valueOf(duration) + " " + res.getString("Position_") +
String.valueOf(seconds), 3, h - 4);
*/
}
private void drawLengthText(double seconds, int h, Graphics2D g2)
{
g2.setColor(graphColor);
g2.setFont(font12);
g2.drawString(res.getString("Length_") + String.valueOf(seconds), 3,
h - 4);
}
private void drawErrorText(String errStr, int w, Graphics2D g2)
{
g2.setColor(colorModel.getColor("alertColor"));
g2.setFont(new Font(res.getString("g2_Font"), Font.BOLD, 20));
g2.drawString(res.getString("ERROR"), 5, 20);
AttributedString as = new AttributedString(errStr);
as.addAttribute(TextAttribute.FONT, font12, 0, errStr.length());
AttributedCharacterIterator aci = as.getIterator();
FontRenderContext frc = g2.getFontRenderContext();
LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
float x = 5, y = 25;
lbm.setPosition(0);
while (lbm.getPosition() < errStr.length())
{
TextLayout tl = lbm.nextLayout(w - x - 5);
if (!tl.isLeftToRight())
{
x = w - tl.getAdvance();
}
tl.draw(g2, x, y += tl.getAscent());
y += tl.getDescent() + tl.getLeading();
}
}
} // End class SamplingGraph