/*
* Spectrogram.java
* (FScape)
*
* Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU General Public License v3+
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de
*/
package de.sciss.fscape.gui;
import de.sciss.fscape.spect.SpectFrame;
import de.sciss.fscape.spect.SpectStream;
import de.sciss.fscape.spect.SpectStreamSlot;
import de.sciss.fscape.util.Slots;
import javax.swing.*;
import java.awt.*;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
/**
* Optional window when using the spectral
* patcher, will output the current sonogram,
* however in a really cheesy way. Needs to be rewritten.
*/
public class Spectrogram
extends Frame
implements AdjustmentListener, MouseListener, MouseMotionListener, WindowListener {
// -------- public variables --------
public Image img = null;
public boolean pausing = false;
// -------- private variables --------
private static final int GG_STARTPAUSE = 1;
private static final int GG_ZOOMIN = 3;
private static final int GG_ZOOMOUT = 4;
private static final int GG_PANEL = 5;
protected GUISupport gui = null;
protected SpectPanel spectPanel;
protected JLabel lbFreq; // JLabel zum Anzeigen der Frequenz
private Cursor lastCursor = null; // Cursor previously used to dragging
private Graphics imgG = null;
private float rgb[] = { 0.0f, 0.0f, 0.0f };
private SpectStreamSlot owner;
private SpectStream stream = null;
private int width = 192;
private int height = 128;
private int x = 0;
private int[] xTime; // Zeitpunkt, den ein Pixel repraesentiert
private int zoom = 0; // 2^n vergroessert
private int maxZoom = 0;
private int lines = 0;
private int bottomLine = 0; // unterste angezeigte Zeile (bei Zoom)
private double freqSpacing; // bandwidth (Hz)
private Cursor dragCursor;
private int dragLastY;
// -------- public methods --------
// public void newStream( SpectStream stream );
// public void ownerTerminated();
// public void addFrame( float frame[] );
/**
* aktiviert automatisch setVisible() !
*/
public Spectrogram( SpectStreamSlot slot )
{
super( ((OpIcon) slot.getOwner().getIcon()).getName() +
((slot.toString() == Slots.SLOTS_DEFWRITER) ? "" : "(" + slot.toString() + ")") +
" - Spectrogram" );
this.owner = slot;
rgb = new float[ 3 ];
dragCursor = new Cursor( Cursor.N_RESIZE_CURSOR );
xTime = new int[ width ];
for( int i = 0; i < xTime.length; i++ ) {
xTime[ i ] = -1;
}
GridBagConstraints con;
ToolIcon ggStartPause;
ToolIcon ggZoomIn, ggZoomOut;
gui = new GUISupport();
// gui.setFont( Main.getFont( Main.FONT_GUI ));
con = gui.getGridBagConstraints();
con.insets = new Insets( 2, 2, 0, 2 );
con.anchor = GridBagConstraints.WEST;
ggStartPause = new ToolIcon( ToolIcon.ID_PAUSE, null );
gui.addCanvas( ggStartPause, GG_STARTPAUSE, this );
con.insets = new Insets( 2, 16, 0, 2 );
con.anchor = GridBagConstraints.WEST;
ggZoomIn = new ToolIcon( ToolIcon.ID_ZOOMIN, null );
ggZoomIn.setEnabled( false );
gui.addCanvas( ggZoomIn, GG_ZOOMIN, this );
con.insets = new Insets( 2, 0, 0, 2 );
ggZoomOut = new ToolIcon( ToolIcon.ID_ZOOMOUT, null );
ggZoomOut.setEnabled( false );
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addCanvas( ggZoomOut, GG_ZOOMOUT, this );
con.fill = GridBagConstraints.BOTH;
con.insets = new Insets( 2, 0, 0, 0 );
spectPanel = new SpectPanel( this, width, height );
spectPanel.addMouseListener( this );
spectPanel.addMouseMotionListener( this );
con.weightx = 1.0;
con.weighty = 1.0;
gui.addGadget( spectPanel, GG_PANEL );
con.fill = GridBagConstraints.HORIZONTAL;
con.insets = new Insets( 0, 2, 0, 0 );
lbFreq = new JLabel();
con.weighty = 0.0;
gui.addLabel( lbFreq );
add( gui );
pack();
addWindowListener( this );
setVisible( true );
img = createImage( width, height );
imgG = img.getGraphics();
imgG.setColor( Color.black );
imgG.fillRect( 0, 0, width, height );
}
/**
* Meldet einen Stream zum Monitoren an
*/
public void newStream( SpectStream strm )
{
synchronized( this ) {
stream = strm;
for( maxZoom = 0; (strm.bands >> maxZoom) > height; maxZoom++ );
zoom = Math.max( 0, maxZoom - 1 ); // i.d.R. 10000 Hz, vernuenftig
bottomLine = 0;
freqSpacing = (strm.hiFreq - strm.loFreq) / strm.bands;
updateZoomGG();
spectPanel.setCursor( new Cursor( Cursor.CROSSHAIR_CURSOR ));
}
}
/**
* Meldet einen Operator ab, weil sein run() zu Ende ist
*/
public void ownerTerminated()
{
Component gg;
synchronized( this ) {
owner = null;
updateZoomGG(); // disables Zoom-Gadgets
stream = null;
pausing = true;
gg = gui.getItemObj( GG_STARTPAUSE );
if( gg != null ) {
gg.setEnabled( false );
}
spectPanel.setCursor( null );
}
}
/**
* Zeichnet ein neues Frame
*/
public void addFrame( SpectFrame fr )
{
int i, j, y;
synchronized( this ) {
if( (stream == null) || pausing ) return; // "died" / Pause
if( lines < height ) { // nicht benutzten Raum loeschen
imgG.setColor( Color.black );
imgG.drawLine( x, lines, x, height - 1 );
}
if( stream.chanNum == 1 ) {
for( i = bottomLine, y = lines - 1; i < (bottomLine + lines); i++, y-- ) {
// 1.17 fuer ein wenig Overhead
rgb[ 0 ] = (float) Math.sqrt( fr.data[ 0 ][ i << (zoom + 1) + SpectFrame.AMP ]
* 1.17f );
if( rgb[ 0 ] < 0.0f ) rgb[ 0 ] = 0.0f;
if( rgb[ 0 ] > 1.0f ) rgb[ 0 ] = 1.0f;
imgG.setColor( new Color( rgb[ 0 ], rgb[ 0 ], rgb[ 0 ] ));
imgG.drawLine( x, y, x, y );
}
} else {
for( i = bottomLine, y = lines - 1; i < (bottomLine + lines); i++, y-- ) {
for( j = 0; j < Math.min( stream.chanNum, 3 ); j++ ) {
rgb[ j ] = (float) Math.sqrt( fr.data[ j ][ (i << (zoom + 1)) +
SpectFrame.AMP] * 1.17f );
if( rgb[ j ] < 0.0f ) rgb[ j ] = 0.0f;
if( rgb[ j ] > 1.0f ) rgb[ j ] = 1.0f;
}
imgG.setColor( new Color( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ]));
imgG.drawLine( x, y, x, y );
}
}
xTime[ x ] = (int) SpectStream.framesToMillis( stream, stream.framesWritten - 1 );
x = (x + 1) % width;
// Mittenfrequenz-Indikator (oben orange, unten rot)
y = Math.min( 4, bottomLine - (stream.bands >> (zoom + 1)) );
y = Math.max( y, 5 - lines );
imgG.setColor( OpIcon.progColor );
imgG.drawLine( x, 0, x, lines - 2 + y );
imgG.setColor( Color.red );
imgG.drawLine( x, lines - 1 + y, x, height );
spectPanel.repaint();
}
}
// -------- Adjustment methods --------
public void adjustmentValueChanged( AdjustmentEvent e ) {}
// -------- Mouse methods --------
public void mouseClicked( MouseEvent e )
{
int ID = gui.getItemID( e );
int ID2;
Component gg1;
int oldVal;
synchronized( this ) {
if( stream == null ) return; // "died"
switch( ID ) {
case GG_STARTPAUSE:
gg1 = gui.getItemObj( GG_STARTPAUSE );
if( gg1 != null ) {
ID2 = ((ToolIcon) gg1).getID();
pausing = (ID2 == ToolIcon.ID_PAUSE);
((ToolIcon) gg1).setID( pausing ? ToolIcon.ID_START : ToolIcon.ID_PAUSE ); // switch
}
break;
case GG_ZOOMIN:
case GG_ZOOMOUT:
synchronized( this ) {
oldVal = (bottomLine + (lines >> 1)) << zoom; // versuchen, alte Mitte zu behalten
if( ID == GG_ZOOMIN ) {
if( zoom > 0 ) {
zoom--;
}
} else {
if( zoom < maxZoom ) {
zoom++;
}
}
bottomLine = (oldVal >> zoom) - (lines >> 1);
updateZoomGG();
}
break;
default:
break;
}
}
}
public void mousePressed( MouseEvent e )
{
if( (e.getSource() == spectPanel) && (stream != null) && (height == lines) ) {
dragLastY = e.getY();
lastCursor = spectPanel.getCursor();
spectPanel.setCursor( dragCursor );
lbFreq.setText( null );
}
}
public void mouseReleased( MouseEvent e )
{
if( (e.getSource() == spectPanel) && (stream != null) ) {
spectPanel.setCursor( lastCursor );
}
}
public void mouseEntered( MouseEvent e ) {}
public void mouseExited( MouseEvent e ) {}
// -------- MouseMotion Listener methods (spectPanel) --------
public void mouseDragged( MouseEvent e )
{
int oldVal;
synchronized( this ) {
if( (stream == null) || (height > lines) || (e.getY() == dragLastY) ) return;
oldVal = bottomLine;
bottomLine = Math.max( 0, bottomLine + e.getY() - dragLastY );
bottomLine = Math.min( bottomLine, (stream.bands >> zoom) - lines );
if( oldVal != bottomLine ) {
imgG.copyArea( 0, 0, width, height, 0, bottomLine - oldVal );
dragLastY = e.getY();
imgG.setColor( Color.black );
if( oldVal < bottomLine ) {
imgG.fillRect( 0, 0, width, bottomLine - oldVal );
} else {
imgG.fillRect( 0, height - oldVal + bottomLine, width, height );
}
spectPanel.repaint();
}
}
}
public void mouseMoved( MouseEvent e )
{
int band = bottomLine + (lines - 1 - e.getY()) << zoom;
synchronized( this ) {
if( (e.getSource() == spectPanel) && (stream != null) &&
(band >= 0) && (band < stream.bands) ) {
lbFreq.setText( "" + (int) (band * freqSpacing + stream.loFreq) +
((e.getX() < xTime.length) && (xTime[ e.getX() ] != -1) ?
" Hz; " + xTime[ e.getX() ] + " ms"
: " Hz") );
}
}
}
// -------- Window methods --------
public void windowClosing( WindowEvent e )
{
setVisible( false );
dispose();
synchronized( this ) {
pausing = true;
if( img != null ) {
img.flush();
img = null;
}
if( imgG != null ) {
imgG.dispose();
imgG = null;
}
owner = null;
stream = null;
}
}
public void windowActivated( WindowEvent e ) {}
public void windowClosed( WindowEvent e ) {}
public void windowDeactivated( WindowEvent e ) {}
public void windowDeiconified( WindowEvent e ) {}
public void windowIconified( WindowEvent e ) {}
public void windowOpened( WindowEvent e ) {}
// -------- private methods --------
// nur innerhalb synchronized( this ) block aufrufen!
private void updateZoomGG()
{
int oldVal;
Component gg1, gg2;
gg1 = gui.getItemObj( GG_ZOOMIN );
gg2 = gui.getItemObj( GG_ZOOMOUT );
if( gg1 != null ) {
gg1.setEnabled( (zoom > 0) && (owner != null) );
}
if( gg2 != null ) {
gg2.setEnabled( (zoom < maxZoom) && (owner != null) );
}
if( stream != null ) {
lines = Math.min( height, stream.bands >> zoom );
oldVal = bottomLine;
bottomLine = Math.max( bottomLine, 0 );
bottomLine = Math.min( bottomLine, (stream.bands >> zoom) - lines );
if( oldVal != bottomLine ) {
imgG.copyArea( 0, 0, width, height, 0, bottomLine - oldVal );
imgG.setColor( Color.black );
if( oldVal < bottomLine ) {
imgG.fillRect( 0, 0, width, bottomLine - oldVal );
} else {
imgG.fillRect( 0, height - oldVal + bottomLine, width, height );
}
spectPanel.repaint();
}
}
}
}
// class Spectrogram
/*
* Hilfsklasse: Panel zeichnet BufferedImage
*/
class SpectPanel
extends Panel
{
private Spectrogram owner;
private int width, height;
public SpectPanel( Spectrogram owner, int width, int height )
{
super();
this.owner = owner;
this.width = width;
this.height = height;
setBackground( Color.black );
}
public void update( Graphics g )
{
paint( g );
}
public void paint( Graphics g )
{
if( owner.img != null ) {
g.drawImage( owner.img, 0, 0, owner );
}
}
public Dimension getPreferredSize()
{
return new Dimension( width, height );
}
}