diff --git a/.classpath b/.classpath index f5d9ad92..4d902b0c 100644 --- a/.classpath +++ b/.classpath @@ -5,7 +5,6 @@ - @@ -18,5 +17,8 @@ + + + diff --git a/imports/JLargeArrays-1.2.jar b/imports/JLargeArrays-1.2.jar new file mode 100644 index 00000000..7be7a5da Binary files /dev/null and b/imports/JLargeArrays-1.2.jar differ diff --git a/imports/JTransforms-3.0.jar b/imports/JTransforms-3.0.jar new file mode 100644 index 00000000..9cc2e3f3 Binary files /dev/null and b/imports/JTransforms-3.0.jar differ diff --git a/imports/jtransforms-2.4.jar b/imports/jtransforms-2.4.jar deleted file mode 100644 index 01bd519c..00000000 Binary files a/imports/jtransforms-2.4.jar and /dev/null differ diff --git a/src/controller/ConfigurationControllerModel.java b/src/controller/ConfigurationControllerModel.java index ab990a0b..bc24d0c2 100644 --- a/src/controller/ConfigurationControllerModel.java +++ b/src/controller/ConfigurationControllerModel.java @@ -37,7 +37,7 @@ import source.tuner.TunerGroupNode; import source.tuner.TunerNode; import source.tuner.TunerSelectionListener; import controller.channel.Channel; -import controller.channel.ChannelListener; +import controller.channel.ChannelEventListener; import controller.channel.ChannelNode; import controller.site.Site; import controller.system.System; @@ -54,8 +54,8 @@ public class ConfigurationControllerModel extends DefaultTreeModel private ResourceManager mResourceManager; - private ArrayList mChannelConfigChangeListeners = - new ArrayList(); + private ArrayList mChannelConfigChangeListeners = + new ArrayList(); private ArrayList mTunerSelectionListeners = new ArrayList(); diff --git a/src/controller/ControllerPanel.java b/src/controller/ControllerPanel.java index 3ddbc439..a0eaf0a2 100644 --- a/src/controller/ControllerPanel.java +++ b/src/controller/ControllerPanel.java @@ -18,22 +18,27 @@ package controller; import java.awt.Color; +import java.awt.Component; import java.awt.Dimension; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import map.MapPanel; import message.Message; import net.miginfocom.swing.MigLayout; import sample.Listener; +import spectrum.ChannelSpectrumPanel; import com.jidesoft.swing.JideSplitPane; import com.jidesoft.swing.JideTabbedPane; import controller.activity.CallEventPanel; import controller.activity.MessageActivityPanel; +import controller.channel.ChannelManager; import controller.state.ChannelStateList; public class ControllerPanel extends JPanel @@ -46,7 +51,9 @@ public class ControllerPanel extends JPanel private MessageActivityPanel mMessageActivityPanel = new MessageActivityPanel(); - + + private ChannelSpectrumPanel mChannelSpectrumPanel; + private JideTabbedPane mTabbedPane; protected ConfigurationTreePanel mSystemControlViewPanel; @@ -79,14 +86,41 @@ public class ControllerPanel extends JPanel mSystemControlSplitPane.add( mSystemControlViewPanel ); mSystemControlSplitPane.add( mConfigurationEditor ); + mChannelSpectrumPanel = new ChannelSpectrumPanel( mResourceManager ); + //Tabbed View - configuration, calls, messages, map mTabbedPane = new JideTabbedPane(); mTabbedPane.setFont( this.getFont() ); mTabbedPane.setForeground( Color.BLACK ); mTabbedPane.addTab( "Configuration", mSystemControlSplitPane ); - mTabbedPane.addTab( "Calls", mCallEventPanel ); + mTabbedPane.addTab( "Channel Spectrum", mChannelSpectrumPanel ); + mTabbedPane.addTab( "Events", mCallEventPanel ); mTabbedPane.addTab( "Messages", mMessageActivityPanel ); + /** + * Change listener to enable/disable the channel spectrum display + * only when the tab is visible, and a channel has been selected + */ + mTabbedPane.addChangeListener( new ChangeListener() + { + @Override + public void stateChanged( ChangeEvent event ) + { + int index = mTabbedPane.getSelectedIndex(); + + Component component = mTabbedPane.getComponentAt( index ); + + if( component instanceof ChannelSpectrumPanel ) + { + mChannelSpectrumPanel.setEnabled( true ); + } + else + { + mChannelSpectrumPanel.setEnabled( false ); + } + } + } ); + /** * Add mapping services and map panel to a new tab */ @@ -102,17 +136,13 @@ public class ControllerPanel extends JPanel /* Channel state list */ mChannelStateList = new ChannelStateList( mResourceManager.getSettingsManager() ); - /* Add message activity panel to receive channel selection events so - * that we can display the correct message listings */ - mResourceManager.getChannelManager().addListener( mMessageActivityPanel ); - - /* Add call event panel to receive channel selection events so - * that we can display the correct call event listings */ - mResourceManager.getChannelManager().addListener( mCallEventPanel ); - - /* Register ChannelStateList as a listener on the channel manager to - * receive channel starts and stops and deletes */ - mResourceManager.getChannelManager().addListener( mChannelStateList ); + /* Register each of the components to receive channel events when the + * channels are selected or change */ + ChannelManager channelManager = mResourceManager.getChannelManager(); + channelManager.addListener( mCallEventPanel ); + channelManager.addListener( mChannelStateList ); + channelManager.addListener( mChannelSpectrumPanel ); + channelManager.addListener( mMessageActivityPanel ); JScrollPane channelStateListScroll = new JScrollPane(); channelStateListScroll.getViewport().setView( mChannelStateList ); diff --git a/src/controller/activity/CallEvent.java b/src/controller/activity/CallEvent.java index be62110f..ebf6de87 100644 --- a/src/controller/activity/CallEvent.java +++ b/src/controller/activity/CallEvent.java @@ -202,6 +202,7 @@ public abstract class CallEvent PAGE( "Page" ), REGISTER( "Register" ), REGISTER_ESN( "ESN" ), + REQUEST( "Request" ), SDM( "SDM" ), STATUS( "Status" ), UNKNOWN( "Unknown" ); diff --git a/src/controller/activity/CallEventPanel.java b/src/controller/activity/CallEventPanel.java index 62846d98..2611ad54 100644 --- a/src/controller/activity/CallEventPanel.java +++ b/src/controller/activity/CallEventPanel.java @@ -23,10 +23,11 @@ import javax.swing.JTable; import net.miginfocom.swing.MigLayout; import controller.channel.Channel; -import controller.channel.Channel.ChannelEvent; -import controller.channel.ChannelListener; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEvent.Event; +import controller.channel.ChannelEventListener; -public class CallEventPanel extends JPanel implements ChannelListener +public class CallEventPanel extends JPanel implements ChannelEventListener { private static final long serialVersionUID = 1L; @@ -46,31 +47,34 @@ public class CallEventPanel extends JPanel implements ChannelListener } @Override - public void occurred( Channel channel, ChannelEvent event ) + public void channelChanged( ChannelEvent event ) { - if( event == ChannelEvent.CHANGE_SELECTED && channel.getSelected() ) + if( event.getEvent() == Event.CHANGE_SELECTED && + event.getChannel().isSelected() ) { if( mDisplayedChannel == null || - ( mDisplayedChannel != null && mDisplayedChannel != channel ) ) + ( mDisplayedChannel != null && + mDisplayedChannel != event.getChannel() ) ) { removeAll(); JScrollPane scroller = new JScrollPane( - channel.getProcessingChain().getChannelState() + event.getChannel().getProcessingChain().getChannelState() .getCallEventTable() ); add( scroller ); - mDisplayedChannel = channel; + mDisplayedChannel = event.getChannel(); revalidate(); repaint(); } } - else if( event == ChannelEvent.PROCESSING_STOPPED || - event == ChannelEvent.CHANNEL_DELETED ) + else if( event.getEvent() == Event.PROCESSING_STOPPED || + event.getEvent() == Event.CHANNEL_DELETED ) { - if( mDisplayedChannel != null && mDisplayedChannel == channel ) + if( mDisplayedChannel != null && + mDisplayedChannel == event.getChannel() ) { mDisplayedChannel = null; removeAll(); diff --git a/src/controller/activity/MessageActivityPanel.java b/src/controller/activity/MessageActivityPanel.java index 5783a19d..6be195de 100644 --- a/src/controller/activity/MessageActivityPanel.java +++ b/src/controller/activity/MessageActivityPanel.java @@ -23,10 +23,11 @@ import javax.swing.JTable; import net.miginfocom.swing.MigLayout; import controller.channel.Channel; -import controller.channel.Channel.ChannelEvent; -import controller.channel.ChannelListener; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEvent.Event; +import controller.channel.ChannelEventListener; -public class MessageActivityPanel extends JPanel implements ChannelListener +public class MessageActivityPanel extends JPanel implements ChannelEventListener { private static final long serialVersionUID = 1L; @@ -44,31 +45,34 @@ public class MessageActivityPanel extends JPanel implements ChannelListener } @Override - public void occurred( Channel channel, ChannelEvent event ) + public void channelChanged( ChannelEvent event ) { - if( event == ChannelEvent.CHANGE_SELECTED && channel.getSelected() ) + if( event.getEvent() == Event.CHANGE_SELECTED && + event.getChannel().isSelected() ) { if( mDisplayedChannel == null || - ( mDisplayedChannel != null && mDisplayedChannel != channel ) ) + ( mDisplayedChannel != null && + mDisplayedChannel != event.getChannel() ) ) { removeAll(); JScrollPane scroll = - new JScrollPane( channel.getProcessingChain() + new JScrollPane( event.getChannel().getProcessingChain() .getChannelState().getMessageActivityTable() ); add( scroll ); - mDisplayedChannel = channel; + mDisplayedChannel = event.getChannel(); revalidate(); repaint(); } } - else if( event == ChannelEvent.PROCESSING_STOPPED || - event == ChannelEvent.CHANNEL_DELETED ) + else if( event.getEvent() == Event.PROCESSING_STOPPED || + event.getEvent() == Event.CHANNEL_DELETED ) { - if( mDisplayedChannel != null && mDisplayedChannel == channel ) + if( mDisplayedChannel != null && + mDisplayedChannel == event.getChannel() ) { mDisplayedChannel = null; removeAll(); diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index ce35eef4..6dee242a 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -47,6 +47,7 @@ import source.tuner.TunerChannel; import source.tuner.TunerChannel.Type; import controller.ResourceManager; import controller.activity.ActivitySummaryFrame; +import controller.channel.ChannelEvent.Event; import controller.config.Configuration; import controller.site.Site; import controller.state.ChannelState; @@ -71,8 +72,8 @@ public class Channel extends Configuration private static final boolean DISABLED = false; private static final boolean BROADCAST_CHANGE = true; - private CopyOnWriteArrayList mChannelListeners = - new CopyOnWriteArrayList(); + private CopyOnWriteArrayList mChannelListeners = + new CopyOnWriteArrayList(); private CopyOnWriteArrayList> mMessageListeners = new CopyOnWriteArrayList>(); @@ -181,7 +182,7 @@ public class Channel extends Configuration * Indicates if this channel has been selected for audio output */ @XmlTransient - public boolean getSelected() + public boolean isSelected() { return mSelected; } @@ -195,7 +196,7 @@ public class Channel extends Configuration mProcessingChain.getAudioOutput().setAudioPlaybackEnabled( selected ); } - fireChannelEvent( ChannelEvent.CHANGE_SELECTED ); + fireChannelEvent( Event.CHANGE_SELECTED ); } public JMenu getContextMenu() @@ -290,7 +291,7 @@ public class Channel extends Configuration setEnabled( DISABLED, BROADCAST_CHANGE ); /* Broadcast channel deleted event */ - fireChannelEvent( ChannelEvent.CHANNEL_DELETED ); + fireChannelEvent( Event.CHANNEL_DELETED ); } /** @@ -375,7 +376,7 @@ public class Channel extends Configuration if( fireChangeEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_SYSTEM ); + fireChannelEvent( Event.CHANGE_SYSTEM ); } } @@ -397,7 +398,7 @@ public class Channel extends Configuration if( fireChangeEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_SITE ); + fireChannelEvent( Event.CHANGE_SITE ); } } @@ -440,7 +441,7 @@ public class Channel extends Configuration if( fireChannelEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_ENABLED ); + fireChannelEvent( Event.CHANGE_ENABLED ); } } @@ -475,7 +476,7 @@ public class Channel extends Configuration if( fireChangeEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_ALIAS_LIST ); + fireChannelEvent( Event.CHANGE_ALIAS_LIST ); } } @@ -534,7 +535,7 @@ public class Channel extends Configuration if( fireChangeEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_DECODER ); + fireChannelEvent( Event.CHANGE_DECODER ); } } @@ -571,7 +572,7 @@ public class Channel extends Configuration if( fireChangeEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_SOURCE ); + fireChannelEvent( Event.CHANGE_SOURCE ); } } @@ -608,7 +609,7 @@ public class Channel extends Configuration if( fireChangeEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_EVENT_LOGGER ); + fireChannelEvent( Event.CHANGE_EVENT_LOGGER ); } } @@ -645,7 +646,7 @@ public class Channel extends Configuration if( fireChangeEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_RECORDER ); + fireChannelEvent( Event.CHANGE_RECORDER ); } } @@ -676,7 +677,7 @@ public class Channel extends Configuration if( fireChangeEvent ) { - fireChannelEvent( ChannelEvent.CHANGE_NAME ); + fireChannelEvent( Event.CHANGE_NAME ); } } @@ -715,7 +716,7 @@ public class Channel extends Configuration mProcessingChain.start(); - fireChannelEvent( ChannelEvent.PROCESSING_STARTED ); + fireChannelEvent( Event.PROCESSING_STARTED ); } /* If this is a traffic channel, override the call fade timeout before @@ -762,17 +763,17 @@ public class Channel extends Configuration mSelected = false; } - fireChannelEvent( ChannelEvent.PROCESSING_STOPPED ); + fireChannelEvent( Event.PROCESSING_STOPPED ); } /** * Broadcasts a channel change event to all registered listeners */ - public void fireChannelEvent( ChannelEvent event ) + public void fireChannelEvent( Event event ) { - for( ChannelListener listener: mChannelListeners ) + for( ChannelEventListener listener: mChannelListeners ) { - listener.occurred( this, event ); + listener.channelChanged( new ChannelEvent( this, event ) ); } } @@ -780,17 +781,19 @@ public class Channel extends Configuration * Adds a channel listener to receive all channel events from this channel * and automatically sends the listener a channel add event. */ - public void addListener( ChannelListener listener ) + public void addListener( ChannelEventListener listener ) { if( !mChannelListeners.contains( listener ) ) { mChannelListeners.add( listener ); - listener.occurred( this, ChannelEvent.CHANNEL_ADDED ); + listener.channelChanged( + new ChannelEvent( this, Event.CHANNEL_ADDED ) ); } else { - Log.error( "Channel - attempt to add already existing channel listener [" + listener.getClass() + "]" ); + Log.error( "Channel - attempt to add already existing channel " + + "listener [" + listener.getClass() + "]" ); } } @@ -798,9 +801,9 @@ public class Channel extends Configuration * Adds a list of channel listeners to receive all channel events from this * channel and automatically sends each one a channel add event. */ - public void addListeners( List listeners ) + public void addListeners( List listeners ) { - for( ChannelListener listener: listeners ) + for( ChannelEventListener listener: listeners ) { addListener( listener ); } @@ -809,7 +812,7 @@ public class Channel extends Configuration /** * Removes a channel listener from receiving channel events from this channel */ - public void removeListener( ChannelListener listener ) + public void removeListener( ChannelEventListener listener ) { mChannelListeners.remove( listener ); } @@ -884,28 +887,4 @@ public class Channel extends Configuration { return mTrafficChannels.containsKey( channelNumber ); } - - /** - * Channel Events - used to specify channel events and changes to channel - * configurations and settings. - */ - public enum ChannelEvent - { - CHANGE_ALIAS_LIST, - CHANGE_DECODER, - CHANGE_ENABLED, - CHANGE_EVENT_LOGGER, - CHANGE_NAME, - CHANGE_SITE, - CHANGE_RECORDER, - CHANGE_SELECTED, - CHANGE_SOURCE, - CHANGE_SYSTEM, - - CHANNEL_ADDED, - CHANNEL_DELETED, - - PROCESSING_STARTED, - PROCESSING_STOPPED, - } } diff --git a/src/controller/channel/ChannelEvent.java b/src/controller/channel/ChannelEvent.java new file mode 100644 index 00000000..df2fc15b --- /dev/null +++ b/src/controller/channel/ChannelEvent.java @@ -0,0 +1,52 @@ +package controller.channel; + +public class ChannelEvent +{ + private Channel mChannel; + private Event mEvent; + + /** + * ChannelEvent - event describing any changes to channels + * @param channel - channel that changed + * @param event - change event + */ + public ChannelEvent( Channel channel, Event event ) + { + mChannel = channel; + mEvent = event; + } + + public Channel getChannel() + { + return mChannel; + } + + public Event getEvent() + { + return mEvent; + } + + /** + * Channel Events - used to specify channel events and changes to channel + * configurations and settings. + */ + public enum Event + { + CHANGE_ALIAS_LIST, + CHANGE_DECODER, + CHANGE_ENABLED, + CHANGE_EVENT_LOGGER, + CHANGE_NAME, + CHANGE_SITE, + CHANGE_RECORDER, + CHANGE_SELECTED, + CHANGE_SOURCE, + CHANGE_SYSTEM, + + CHANNEL_ADDED, + CHANNEL_DELETED, + + PROCESSING_STARTED, + PROCESSING_STOPPED, + } +} diff --git a/src/controller/channel/ChannelListener.java b/src/controller/channel/ChannelEventListener.java similarity index 83% rename from src/controller/channel/ChannelListener.java rename to src/controller/channel/ChannelEventListener.java index c42324bd..7c5405db 100644 --- a/src/controller/channel/ChannelListener.java +++ b/src/controller/channel/ChannelEventListener.java @@ -17,16 +17,14 @@ ******************************************************************************/ package controller.channel; -import controller.channel.Channel.ChannelEvent; - /** * Interface for receiving channel events. Each listener will receive * notification of all channel events. */ -public interface ChannelListener +public interface ChannelEventListener { /** - * Notifies the listener that a channel has changed and what has changed + * Notifies the listener that a channel event has occurred */ - public void occurred( Channel channel, ChannelEvent event ); + public void channelChanged( ChannelEvent event ); } diff --git a/src/controller/channel/ChannelManager.java b/src/controller/channel/ChannelManager.java index 2ca807fc..96dfbeb0 100644 --- a/src/controller/channel/ChannelManager.java +++ b/src/controller/channel/ChannelManager.java @@ -22,14 +22,13 @@ import java.util.ArrayList; import message.Message; import sample.Listener; import controller.ResourceManager; -import controller.channel.Channel.ChannelEvent; -public class ChannelManager implements ChannelListener +public class ChannelManager implements ChannelEventListener { private ArrayList mChannels = new ArrayList(); - private ArrayList mChannelListeners = - new ArrayList(); + private ArrayList mChannelListeners = + new ArrayList(); private ArrayList> mMessageListeners = new ArrayList>(); @@ -45,18 +44,18 @@ public class ChannelManager implements ChannelListener */ @SuppressWarnings( "incomplete-switch" ) @Override - public void occurred( Channel channel, ChannelEvent component ) + public void channelChanged( ChannelEvent event ) { - switch( component ) + switch( event.getEvent() ) { case CHANNEL_ADDED: - if( !mChannels.contains( channel ) ) + if( !mChannels.contains( event.getChannel() ) ) { - mChannels.add( channel ); + mChannels.add( event.getChannel() ); } break; case CHANNEL_DELETED: - mChannels.remove( channel ); + mChannels.remove( event.getChannel() ); break; } } @@ -66,7 +65,7 @@ public class ChannelManager implements ChannelListener * any channel change events. This listener will automatically receive * a channel add event as it is added to each of the existing channels. */ - public void addListener( ChannelListener listener ) + public void addListener( ChannelEventListener listener ) { mChannelListeners.add( listener ); @@ -80,7 +79,7 @@ public class ChannelManager implements ChannelListener * Removes a channel listener from being automatically added to all channels * to receive channel change events. */ - public void removeListener( ChannelListener listener ) + public void removeListener( ChannelEventListener listener ) { mChannelListeners.remove( listener ); @@ -94,7 +93,7 @@ public class ChannelManager implements ChannelListener * Returns the list of channel change listeners that will be automatically * added to all channels to receive channel change events */ - public ArrayList getChannelListeners() + public ArrayList getChannelListeners() { return mChannelListeners; } diff --git a/src/controller/channel/ChannelNode.java b/src/controller/channel/ChannelNode.java index 665fca75..9bf26c0b 100644 --- a/src/controller/channel/ChannelNode.java +++ b/src/controller/channel/ChannelNode.java @@ -22,10 +22,8 @@ import java.text.DecimalFormat; import javax.swing.JPanel; import javax.swing.JPopupMenu; -import log.Log; import source.tuner.TunerChannel; import controller.ConfigurableNode; -import controller.channel.Channel.ChannelEvent; import controller.site.SiteNode; import controller.system.SystemNode; @@ -33,7 +31,7 @@ import controller.system.SystemNode; * A channel has all of the pieces needed to wire together a source, decoder, * event logger and recorder and be started and stopped. */ -public class ChannelNode extends ConfigurableNode implements ChannelListener +public class ChannelNode extends ConfigurableNode implements ChannelEventListener { private static final long serialVersionUID = 1L; @@ -73,9 +71,9 @@ public class ChannelNode extends ConfigurableNode implements ChannelListener * Listener method to receive channel changes from the underlying chnannel */ @Override - public void occurred( Channel channel, ChannelEvent event ) + public void channelChanged( ChannelEvent event ) { - switch( event ) + switch( event.getEvent() ) { /* Refresh the node for each of these events */ case CHANGE_DECODER: diff --git a/src/controller/state/ChannelStateList.java b/src/controller/state/ChannelStateList.java index 6db9b475..5d976bd2 100644 --- a/src/controller/state/ChannelStateList.java +++ b/src/controller/state/ChannelStateList.java @@ -36,9 +36,9 @@ import settings.ColorSettingMenuItem; import settings.ColorSettingResetMenuItem; import settings.SettingsManager; import controller.channel.Channel; -import controller.channel.Channel.ChannelEvent; import controller.channel.Channel.ChannelType; -import controller.channel.ChannelListener; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEventListener; import decode.fleetsync2.FleetsyncChannelState; import decode.fleetsync2.FleetsyncPanel; import decode.ltrnet.LTRNetChannelState; @@ -54,7 +54,7 @@ import decode.nbfm.NBFMPanel; import decode.passport.PassportChannelState; import decode.passport.PassportPanel; -public class ChannelStateList extends JPanel implements ChannelListener +public class ChannelStateList extends JPanel implements ChannelEventListener { private static final long serialVersionUID = 1L; @@ -79,24 +79,25 @@ public class ChannelStateList extends JPanel implements ChannelListener } @Override - public void occurred( Channel channel, ChannelEvent event ) + public void channelChanged( ChannelEvent event ) { - ChannelType type = channel.getChannelType(); + ChannelType type = event.getChannel().getChannelType(); - switch( event ) + switch( event.getEvent() ) { case PROCESSING_STARTED: if( type == ChannelType.STANDARD ) { - channelStarted( channel ); + channelStarted( event.getChannel() ); } else if( type == ChannelType.TRAFFIC ) { for( Channel parent: mDisplayedPanels.keySet() ) { - if( parent.hasTrafficChannel( channel ) ) + if( parent.hasTrafficChannel( event.getChannel() ) ) { - mDisplayedPanels.get( parent ).trafficChannelAdded( channel ); + mDisplayedPanels.get( parent ) + .trafficChannelAdded( event.getChannel() ); validate(); } } @@ -105,7 +106,7 @@ public class ChannelStateList extends JPanel implements ChannelListener case PROCESSING_STOPPED: if( type == ChannelType.STANDARD ) { - channelStopped( channel ); + channelStopped( event.getChannel() ); } /* Since we don't have a reference to the parent channel that * owns the traffic channel, iterate all channels to find the @@ -114,11 +115,12 @@ public class ChannelStateList extends JPanel implements ChannelListener { for( Channel control: mDisplayedPanels.keySet() ) { - if( control.hasTrafficChannel( channel ) ) + if( control.hasTrafficChannel( event.getChannel() ) ) { - control.removeTrafficChannel( channel ); + control.removeTrafficChannel( event.getChannel() ); - mDisplayedPanels.get( control ).trafficChannelDeleted( channel ); + mDisplayedPanels.get( control ) + .trafficChannelDeleted( event.getChannel() ); validate(); } @@ -252,15 +254,17 @@ public class ChannelStateList extends JPanel implements ChannelListener public void setSelectedChannel( ChannelStatePanel selectedPanel ) { - boolean current = selectedPanel.getSelected(); - + //De-select any other selected panel for( ChannelStatePanel panel: mDisplayedPanels.values() ) { - //Turn selected off - panel.setSelected( false ); + if( panel != selectedPanel && panel.isSelected() ) + { + panel.setSelected( false ); + } } - selectedPanel.setSelected( !current ); + //Toggle selection state + selectedPanel.setSelected( !selectedPanel.isSelected() ); } public class ListSelectionListener implements MouseListener diff --git a/src/controller/state/ChannelStatePanel.java b/src/controller/state/ChannelStatePanel.java index 2ed59bd4..4c8e6b3f 100644 --- a/src/controller/state/ChannelStatePanel.java +++ b/src/controller/state/ChannelStatePanel.java @@ -163,9 +163,9 @@ public abstract class ChannelStatePanel extends JPanel return null; } - public boolean getSelected() + public boolean isSelected() { - return mChannel.getSelected(); + return mChannel.isSelected(); } public void addPanel( final JPanel panel ) @@ -224,13 +224,13 @@ public abstract class ChannelStatePanel extends JPanel public void setSelected( boolean selected ) { - mChannel.setSelected( selected ); - for( ChannelStatePanel traffic: getTrafficPanels().values() ) { traffic.setSelected( false ); } - + + mChannel.setSelected( selected ); + repaint(); } @@ -293,7 +293,7 @@ public abstract class ChannelStatePanel extends JPanel g2.drawLine( 0, getHeight() - 1, getWidth(), getHeight() - 1 ); /* Draw channel selected box */ - if( mChannel.getSelected() ) + if( mChannel.isSelected() ) { g2.setColor( mColorChannelSelected ); g2.drawRect( 1, 1, getWidth() - 2, getHeight() - 2 ); diff --git a/src/dsp/filter/FilterFactory.java b/src/dsp/filter/FilterFactory.java index 2a9a1a4f..24f56fcc 100644 --- a/src/dsp/filter/FilterFactory.java +++ b/src/dsp/filter/FilterFactory.java @@ -22,8 +22,10 @@ import java.util.Set; import java.util.TreeSet; import log.Log; + +import org.jtransforms.fft.DoubleFFT_1D; + import dsp.filter.Window.WindowType; -import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D; public class FilterFactory { diff --git a/src/dsp/filter/Window.java b/src/dsp/filter/Window.java index eaf1588c..72e846b4 100644 --- a/src/dsp/filter/Window.java +++ b/src/dsp/filter/Window.java @@ -157,7 +157,7 @@ public class Window */ public static float[] apply( double[] coefficients, float[] samples ) { - for( int x = 0; x < samples.length; x++ ) + for( int x = 0; x < coefficients.length; x++ ) { samples[ x ] = (float)( samples[ x ] * coefficients[ x ] ); } @@ -177,7 +177,7 @@ public class Window */ public static double[] apply( double[] coefficients, double[] samples ) { - for( int x = 0; x < samples.length; x++ ) + for( int x = 0; x < coefficients.length; x++ ) { samples[ x ] = samples[ x ] * coefficients[ x ]; } @@ -231,9 +231,4 @@ public class Window return sb.toString(); } - - public static void main( String[] args ) - { - System.out.println( arrayToString( getBlackmanWindow( 31 ), false ) ); - } } diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java index 6b08bc10..bef3bfca 100644 --- a/src/gui/SDRTrunk.java +++ b/src/gui/SDRTrunk.java @@ -145,8 +145,8 @@ public class SDRTrunk JideSplitPane splitPane = new JideSplitPane( JideSplitPane.VERTICAL_SPLIT ); splitPane.setDividerSize( 5 ); - splitPane.add( "Denny", mSpectralPanel ); - splitPane.add( "Denny2", mControllerPanel ); + splitPane.add( mSpectralPanel ); + splitPane.add( mControllerPanel ); mMainGui.add( splitPane, "cell 0 0,span,grow"); @@ -174,13 +174,13 @@ public class SDRTrunk JMenuItem exitMenu = new JMenuItem( "Exit" ); exitMenu.addActionListener( - new ActionListener() - { - public void actionPerformed( ActionEvent event ) - { - System.exit( 0 ); - } - } + new ActionListener() + { + public void actionPerformed( ActionEvent event ) + { + System.exit( 0 ); + } + } ); fileMenu.add( exitMenu ); diff --git a/src/settings/IntegerSetting.java b/src/settings/IntegerSetting.java new file mode 100644 index 00000000..f38541c5 --- /dev/null +++ b/src/settings/IntegerSetting.java @@ -0,0 +1,30 @@ +package settings; + +import javax.xml.bind.annotation.XmlAttribute; + +public class IntegerSetting extends Setting +{ + private int mValue; + + public IntegerSetting() + { + } + + public IntegerSetting( String name, int value ) + { + super( name ); + + mValue = value; + } + + @XmlAttribute + public int getValue() + { + return mValue; + } + + public void setValue( int value ) + { + mValue = value; + } +} diff --git a/src/settings/Settings.java b/src/settings/Settings.java index 3144bbc7..d70cb8c2 100644 --- a/src/settings/Settings.java +++ b/src/settings/Settings.java @@ -45,6 +45,7 @@ import source.tuner.rtl.r820t.R820TTunerConfiguration; FCD1TunerConfiguration.class, FCD2TunerConfiguration.class, FileSetting.class, + IntegerSetting.class, MapViewSetting.class, MapIcon.class, R820TTunerConfiguration.class, @@ -177,6 +178,20 @@ public class Settings return null; } + + public IntegerSetting getIntegerSetting( String name ) + { + for( Setting setting: mSettings ) + { + if( setting instanceof IntegerSetting && + ((IntegerSetting)setting).getName().contentEquals( name ) ) + { + return (IntegerSetting)setting; + } + } + + return null; + } public ColorSetting getColorSetting( ColorSettingName name ) { diff --git a/src/settings/SettingsManager.java b/src/settings/SettingsManager.java index 0fe0f0b5..940e6f71 100644 --- a/src/settings/SettingsManager.java +++ b/src/settings/SettingsManager.java @@ -143,6 +143,33 @@ public class SettingsManager return mSettings.getSetting( name ); } + public IntegerSetting getIntegerSetting( String name, int defaultValue ) + { + IntegerSetting setting = mSettings.getIntegerSetting( name ); + + if( setting == null ) + { + setting = new IntegerSetting( name, defaultValue ); + + mSettings.addSetting( setting ); + + save(); + } + + return setting; + } + + public void setIntegerSetting( String name, int value ) + { + IntegerSetting setting = getIntegerSetting( name, value ); + + setting.setValue( value ); + + save(); + + broadcastSettingChange( setting ); + } + /** * Returns the current setting, or if the setting doesn't exist * returns a newly created setting with the specified parameters diff --git a/src/source/mixer/ShortAdapter.java b/src/source/mixer/ShortAdapter.java index b8f0bf09..6497bb11 100644 --- a/src/source/mixer/ShortAdapter.java +++ b/src/source/mixer/ShortAdapter.java @@ -20,8 +20,6 @@ package source.mixer; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import source.Source.SampleType; - /** * Converts 16-bit little endian byte data into an array of floats */ diff --git a/src/source/tuner/TunerClass.java b/src/source/tuner/TunerClass.java index 9f9350e1..dae3665b 100644 --- a/src/source/tuner/TunerClass.java +++ b/src/source/tuner/TunerClass.java @@ -84,7 +84,7 @@ public enum TunerClass return mVendorDescription + " " + mDeviceDescription; } - public TunerType getTunerClass() + public TunerType getTunerType() { return mTunerType; } diff --git a/src/source/tuner/TunerManager.java b/src/source/tuner/TunerManager.java index b4c4a064..7e34fd60 100644 --- a/src/source/tuner/TunerManager.java +++ b/src/source/tuner/TunerManager.java @@ -141,7 +141,7 @@ public class TunerManager case FUNCUBE_DONGLE_PRO: //Locate the matching mixer tuner dataline MixerTunerDataLine fcd1Dataline = - getMixerTunerDataLine( tunerClass.getTunerClass() ); + getMixerTunerDataLine( tunerClass.getTunerType() ); if( fcd1Dataline != null ) { FCD1TunerController fcd1Controller; @@ -182,7 +182,7 @@ public class TunerManager case FUNCUBE_DONGLE_PRO_PLUS: //Locate the matching mixer tuner dataline MixerTunerDataLine fcd2Dataline = - getMixerTunerDataLine( tunerClass.getTunerClass() ); + getMixerTunerDataLine( tunerClass.getTunerType() ); if( fcd2Dataline != null ) { @@ -245,13 +245,15 @@ public class TunerManager case TERRATEC_T_STICK_PLUS: case TWINTECH_UT40: case ZAAPA_ZTMINDVBZP: - TunerType tunerType = tunerClass.getTunerClass(); + TunerType tunerType = tunerClass.getTunerType(); if( tunerType == TunerType.RTL2832_VARIOUS ) { tunerType = RTL2832TunerController.identifyTunerType( device ); } + Log.debug( "TunerManager - identified tuner class [" + tunerClass.toString() + "] with tuner type [" + tunerType.toString() + "]" ); + switch( tunerType ) { case ELONICS_E4000: @@ -286,11 +288,13 @@ public class TunerManager } break; case RAFAELMICRO_R820T: + Log.debug( "TunerManager - attempting to construct R820T tuner controller" ); try { R820TTunerController controller = new R820TTunerController( device ); + Log.debug( "TunerManager - init() R820T tuner controller" ); controller.init(); RTL2832Tuner rtlTuner = @@ -302,8 +306,13 @@ public class TunerManager if( config != null ) { + Log.debug( "TunerManager - applying tuner config to R820T tuner" ); rtlTuner.apply( config ); } + else + { + Log.debug( "TunerManager - R820T tuner config was null - not applied" ); + } mTuners.add( rtlTuner ); status = sLOADED; @@ -315,6 +324,8 @@ public class TunerManager status = sNOT_LOADED; reason = "Error constructing R820T tuner " + "controller - " + se.getLocalizedMessage(); + + Log.error( "TunerManager - error constructing tuner", se ); } break; case FITIPOWER_FC0012: @@ -324,8 +335,8 @@ public class TunerManager default: status = sNOT_LOADED; reason = "SDRTRunk doesn't currently support RTL2832 " - + "Dongle with [" + tunerType.toString() + - "] tuner"; + + "Dongle with [" + tunerType.toString() + + "] tuner for tuner class[" + tunerClass.toString() + "]"; break; } break; diff --git a/src/source/tuner/rtl/RTL2832SampleAdapter.java b/src/source/tuner/rtl/RTL2832SampleAdapter.java index 3ec92322..d11fdfd6 100644 --- a/src/source/tuner/rtl/RTL2832SampleAdapter.java +++ b/src/source/tuner/rtl/RTL2832SampleAdapter.java @@ -17,7 +17,6 @@ ******************************************************************************/ package source.tuner.rtl; -import source.Source.SampleType; import source.mixer.SampleAdapter; public class RTL2832SampleAdapter extends SampleAdapter @@ -26,17 +25,15 @@ public class RTL2832SampleAdapter extends SampleAdapter /** * Creates a static lookup table that converts the 8-bit valued range - * from 0 - 255 into scaled float values of -128 to 0 to 127 + * from 0 - 255 into scaled float values of -32768.0 to 0 to 32512.0 */ static { mLOOKUP_VALUES = new float[ 256 ]; - float scale = (float)( 1.0f / 127.0f ); - for( int x = 0; x < 256; x++ ) { - mLOOKUP_VALUES[ x ] = (float)( x - 128 ) * scale; + mLOOKUP_VALUES[ x ] = (float)( x - 128 ) * 256.0f; } mLOOKUP_VALUES[ 128 ] = 0.0000001f; diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java index c10c49ae..f9e3cedb 100644 --- a/src/source/tuner/rtl/RTL2832TunerController.java +++ b/src/source/tuner/rtl/RTL2832TunerController.java @@ -412,7 +412,7 @@ public abstract class RTL2832TunerController extends TunerController tunerClass = TunerType.FITIPOWER_FC0012; } - Log.debug( "RTL2832 - disabling I2C repeater" ); + Log.debug( "RTL2832 - disabling I2C repeater -- detected tuner type:" + tunerClass.toString() ); enableI2CRepeater( device, false ); diff --git a/src/spectrum/ChannelOverlayPanel.java b/src/spectrum/ChannelOverlayPanel.java new file mode 100644 index 00000000..145edb10 --- /dev/null +++ b/src/spectrum/ChannelOverlayPanel.java @@ -0,0 +1,264 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package spectrum; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RenderingHints; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.text.DecimalFormat; + +import javax.swing.JPanel; + +import settings.ColorSetting; +import settings.ColorSetting.ColorSettingName; +import settings.Setting; +import settings.SettingChangeListener; +import controller.ResourceManager; + +public class ChannelOverlayPanel extends JPanel implements SettingChangeListener +{ + private static final long serialVersionUID = 1L; + + private static DecimalFormat sFORMAT = new DecimalFormat( "000.0" ); + private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.0" ); + private double mBandwidth = 6000.0d; + private Point mCursorLocation = new Point( 0, 0 ); + private boolean mCursorVisible = false; + + /** + * Colors used by this component + */ + private Color mColorSpectrumBackground; + private Color mColorSpectrumCursor; + private Color mColorSpectrumLine; + + //Defines the offset at the bottom of the spectral display to account for + //the frequency labels + private float mSpectrumInset = 20.0f; + + private ResourceManager mResourceManager; + + /** + * Translucent overlay panel for displaying channel configurations, + * processing channels, selected channels, frequency labels and lines, and + * a cursor with a frequency readout. + */ + public ChannelOverlayPanel( ResourceManager resourceManager ) + { + mResourceManager = resourceManager; + + //Set the background transparent, so the spectrum display can be seen + setOpaque( false ); + + //Fetch color settings from settings manager + setColors(); + } + + public void dispose() + { + mResourceManager = null; + } + + public void setCursorLocation( Point point ) + { + mCursorLocation = point; + + repaint(); + } + + public void setCursorVisible( boolean visible ) + { + mCursorVisible = visible; + + repaint(); + } + + /** + * Fetches the color settings from the settings manager + */ + private void setColors() + { + mColorSpectrumCursor = getColor( ColorSettingName.SPECTRUM_CURSOR ); + + mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE ); + + mColorSpectrumBackground = + getColor( ColorSettingName.SPECTRUM_BACKGROUND ); + } + + /** + * Fetches a named color setting from the settings manager. If the setting + * doesn't exist, creates the setting using the defaultColor + */ + private Color getColor( ColorSettingName name ) + { + ColorSetting setting = + mResourceManager.getSettingsManager() + .getColorSetting( name ); + + return setting.getColor(); + } + + /** + * Monitors for setting changes. Colors can be changed by external actions + * and will automatically update in this class + */ + @Override + public void settingChanged( Setting setting ) + { + if( setting instanceof ColorSetting ) + { + ColorSetting colorSetting = (ColorSetting)setting; + + switch( colorSetting.getColorSettingName() ) + { + case SPECTRUM_BACKGROUND: + mColorSpectrumBackground = colorSetting.getColor(); + break; + case SPECTRUM_CURSOR: + mColorSpectrumCursor = colorSetting.getColor(); + break; + case SPECTRUM_LINE: + mColorSpectrumLine = colorSetting.getColor(); + break; + } + } + } + + /** + * Renders the channel configs, lines, labels, and cursor + */ + @Override + public void paintComponent( Graphics g ) + { + super.paintComponent( g ); + + Graphics2D graphics = (Graphics2D) g; + graphics.setBackground( mColorSpectrumBackground ); + + RenderingHints renderHints = + new RenderingHints( RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON ); + + renderHints.put( RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY ); + + graphics.setRenderingHints( renderHints ); + + drawFrequencies( graphics ); + drawCursor( graphics ); + } + + /** + * Draws a cursor on the panel, whenever the mouse is hovering over the + * panel + */ + private void drawCursor( Graphics2D graphics ) + { + if( mCursorVisible ) + { + drawFrequencyLine( graphics, + mCursorLocation.x, + mColorSpectrumCursor ); + + double value = mBandwidth * + ( mCursorLocation.getX() / getSize().getWidth() ); + + String frequency = sCURSOR_FORMAT.format( value ); + + graphics.drawString( frequency , + mCursorLocation.x + 5, + mCursorLocation.y ); + } + } + + /** + * Draws the frequency lines and labels every 10kHz + */ + private void drawFrequencies( Graphics2D graphics ) + { + drawFrequencyLine( graphics, getAxisFromFrequency( 1000 ), Color.LIGHT_GRAY ); + drawFrequencyLineAndLabel( graphics, 2000 ); + drawFrequencyLine( graphics, getAxisFromFrequency( 3000 ), Color.LIGHT_GRAY ); + drawFrequencyLineAndLabel( graphics, 4000 ); + drawFrequencyLine( graphics, getAxisFromFrequency( 5000 ), Color.LIGHT_GRAY ); +// drawFrequencyLineAndLabel( graphics, 6000 ); +// drawFrequencyLine( graphics, getAxisFromFrequency( 7000 ), Color.LIGHT_GRAY ); +// drawFrequencyLineAndLabel( graphics, 8000 ); +// drawFrequencyLine( graphics, getAxisFromFrequency( 9000 ), Color.LIGHT_GRAY ); +// drawFrequencyLineAndLabel( graphics, 10000 ); +// drawFrequencyLine( graphics, getAxisFromFrequency( 11000 ), Color.LIGHT_GRAY ); + } + + private float getAxisFromFrequency( long frequency ) + { + return (float)( getSize().getWidth() * ( (double)frequency / mBandwidth ) ); + } + + /** + * Draws a vertical line and a corresponding frequency label at the bottom + */ + private void drawFrequencyLineAndLabel( Graphics2D graphics, long frequency ) + { + float xAxis = (float)( getSize().getWidth() * + ( (double)frequency / mBandwidth ) ); + + drawFrequencyLine( graphics, xAxis, mColorSpectrumLine ); + + graphics.setColor( mColorSpectrumLine ); + + drawFrequencyLabel( graphics, xAxis, frequency ); + } + + /** + * Draws a vertical line at the xaxis + */ + private void drawFrequencyLine( Graphics2D graphics, float xaxis, Color color ) + { + graphics.setColor( color ); + + graphics.draw( new Line2D.Float( xaxis, 0, + xaxis, (float)(getSize().getHeight()) - mSpectrumInset ) ); + } + + /** + * Draws a frequency label at the x-axis position, at the bottom of the panel + */ + private void drawFrequencyLabel( Graphics2D graphics, + float xaxis, + long frequency ) + { + String label = sFORMAT.format( (float)frequency ); + + FontMetrics fontMetrics = graphics.getFontMetrics( this.getFont() ); + + Rectangle2D rect = fontMetrics.getStringBounds( label, graphics ); + + float offset = (float)rect.getWidth() / 2; + + graphics.drawString( label, xaxis - offset, + (float)getSize().getHeight() - ( mSpectrumInset * 0.2f ) ); + } + + public void settingDeleted( Setting setting ) {} +} diff --git a/src/spectrum/ChannelSpectrumPanel.java b/src/spectrum/ChannelSpectrumPanel.java new file mode 100644 index 00000000..1ef4a716 --- /dev/null +++ b/src/spectrum/ChannelSpectrumPanel.java @@ -0,0 +1,520 @@ +package spectrum; + +import java.awt.Component; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.swing.JLayeredPane; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; + +import net.miginfocom.swing.MigLayout; +import sample.Listener; +import settings.ColorSetting.ColorSettingName; +import settings.ColorSettingMenuItem; +import settings.Setting; +import settings.SettingChangeListener; +import settings.SettingsManager; +import source.Source.SampleType; +import spectrum.converter.DFTResultsConverter; +import spectrum.converter.RealDecibelConverter; +import spectrum.menu.AmplificationItem; +import spectrum.menu.AveragingItem; +import spectrum.menu.BaselineItem; +import spectrum.menu.FFTWidthItem; +import spectrum.menu.FFTWindowTypeItem; +import spectrum.menu.FrameRateItem; +import controller.ResourceManager; +import controller.channel.Channel; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEventListener; +import dsp.filter.Filters; +import dsp.filter.FloatHalfBandFilter; +import dsp.filter.Window.WindowType; + +public class ChannelSpectrumPanel extends JPanel + implements ChannelEventListener, + Listener, + SettingChangeListener, + SpectralDisplayAdjuster +{ + private static final long serialVersionUID = 1L; + private static final String CHANNEL_SPECTRUM_AVERAGING_SIZE = + "channel_spectrum_averaging_size"; + + private ResourceManager mResourceManager; + private DFTProcessor mDFTProcessor = new DFTProcessor( SampleType.FLOAT ); + private DFTResultsConverter mDFTConverter = new RealDecibelConverter(); + private JLayeredPane mLayeredPane; + private SpectrumPanel mSpectrumPanel; + private ChannelOverlayPanel mOverlayPanel; + + private Channel mCurrentChannel; + + private int mSpectrumAveragingSize; + private int mSampleBufferSize = 2400; + private Float[] mSamples = new Float[ mSampleBufferSize ]; + private int mSamplePointer = 0; + + private DecimatingSampleAssembler mDecimatingSampleAssembler; + + private AtomicBoolean mEnabled = new AtomicBoolean(); + + public ChannelSpectrumPanel( ResourceManager resourceManager ) + { + mResourceManager = resourceManager; + mResourceManager.getSettingsManager().addListener( this ); + mSpectrumPanel = new SpectrumPanel( mResourceManager ); + mOverlayPanel = new ChannelOverlayPanel( mResourceManager ); + + + mSpectrumAveragingSize = mResourceManager.getSettingsManager() + .getIntegerSetting( CHANNEL_SPECTRUM_AVERAGING_SIZE, 10 ).getValue(); + + mSpectrumPanel.setAveraging( mSpectrumAveragingSize ); + + mDFTProcessor.addConverter( mDFTConverter ); + mDFTConverter.addListener( mSpectrumPanel ); + + mDecimatingSampleAssembler = new DecimatingSampleAssembler( mDFTProcessor ); + + /* Set the DFTProcessor to the decimated 24kHz sample rate */ + mDFTProcessor.frequencyChanged( 0, 24000 ); + + initGui(); + } + + public void dispose() + { + setEnabled( false ); + + mDFTProcessor.dispose(); + + mResourceManager = null; + mCurrentChannel = null; + mDFTProcessor = null; + mSpectrumPanel = null; + } + + public void setFrameRate( int framesPerSecond ) + { + mSampleBufferSize = (int)( 48000 / framesPerSecond ); + + mDFTProcessor.setFrameRate( framesPerSecond ); + } + + private void initGui() + { + setLayout( new MigLayout( "insets 0 0 0 0 ", + "[grow,fill]", + "[grow,fill]") ); + + mLayeredPane = new JLayeredPane(); + mLayeredPane.addComponentListener( new ResizeListener() ); + + MouseEventProcessor mouser = new MouseEventProcessor(); + + mOverlayPanel.addMouseListener( mouser ); + mOverlayPanel.addMouseMotionListener( mouser ); + + mLayeredPane.add( mSpectrumPanel, new Integer( 0 ), 0 ); + mLayeredPane.add( mOverlayPanel, new Integer( 1 ), 0 ); + + add( mLayeredPane ); + } + + public void setEnabled( boolean enabled ) + { + if( enabled && mEnabled.compareAndSet( false, true ) ) + { + start(); + } + else if( !enabled && mEnabled.compareAndSet( true, false ) ) + { + stop(); + } + } + + @Override + @SuppressWarnings( "incomplete-switch" ) + public void channelChanged( ChannelEvent event ) + { + switch( event.getEvent() ) + { + case CHANGE_SELECTED: + if( !event.getChannel().isSelected() ) + { + stop(); + + mCurrentChannel = null; + } + else + { + mCurrentChannel = event.getChannel(); + + if( mEnabled.get() ) + { + start(); + } + } + if( event.getChannel().isSelected() && + mCurrentChannel != event.getChannel() ) + { + stop(); + + mCurrentChannel = event.getChannel(); + + start(); + } + break; + case CHANNEL_DELETED: + if( event.getChannel() == mCurrentChannel ) + { + if( mEnabled.get() ) + { + stop(); + } + + mCurrentChannel = null; + } + break; + case CHANGE_ENABLED: + if( event.getChannel() == mCurrentChannel && + !event.getChannel().isProcessing() ) + { + stop(); + mCurrentChannel = null; + } + break; + } + } + + private void start() + { + if( mEnabled.get() && mCurrentChannel != null && mCurrentChannel.isProcessing() ) + { + mCurrentChannel.getProcessingChain().addFloatListener( this ); + mDFTProcessor.start(); + } + } + + private void stop() + { + + if( mCurrentChannel != null && mCurrentChannel.isProcessing() ) + { + mCurrentChannel.getProcessingChain().removeFloatListener( this ); + } + + mDFTProcessor.stop(); + + mSpectrumPanel.clearSpectrum(); + } + + @Override + public void settingChanged( Setting setting ) + { + if( mSpectrumPanel != null ) + { + mSpectrumPanel.settingChanged( setting ); + } + if( mOverlayPanel != null ) + { + mOverlayPanel.settingChanged( setting ); + } + } + + @Override + public void settingDeleted( Setting setting ) + { + if( mSpectrumPanel != null ) + { + mSpectrumPanel.settingDeleted( setting ); + } + + if( mOverlayPanel != null ) + { + mOverlayPanel.settingDeleted( setting ); + } + } + + @Override + public void receive( Float sample ) + { + mDecimatingSampleAssembler.receive( sample ); + } + + /** + * Monitors the sizing of the layered pane and resizes the spectrum and + * channel panels whenever the layered pane is resized + */ + public class ResizeListener implements ComponentListener + { + @Override + public void componentResized( ComponentEvent e ) + { + Component c = e.getComponent(); + + mSpectrumPanel.setBounds( 0, 0, c.getWidth(), c.getHeight() ); + mOverlayPanel.setBounds( 0, 0, c.getWidth(), c.getHeight() ); + } + + @Override + public void componentHidden( ComponentEvent arg0 ) {} + @Override + public void componentMoved( ComponentEvent arg0 ) {} + @Override + public void componentShown( ComponentEvent arg0 ) {} + } + + /** + * Mouse event handler for the channel panel. + */ + public class MouseEventProcessor implements MouseMotionListener, MouseListener + { + @Override + public void mouseMoved( MouseEvent event ) + { + update( event ); + } + @Override + public void mouseDragged( MouseEvent event ) + { + update( event ); + } + + private void update( MouseEvent event ) + { + if( event.getComponent() == mOverlayPanel ) + { + mOverlayPanel.setCursorLocation( event.getPoint() ); + } + } + + @Override + public void mouseEntered( MouseEvent e ) + { + if( e.getComponent() == mOverlayPanel ) + { + mOverlayPanel.setCursorVisible( true ); + } + } + + @Override + public void mouseExited( MouseEvent e ) + { + mOverlayPanel.setCursorVisible( false ); + } + + /** + * Displays the context menu. + */ + @Override + public void mouseClicked( MouseEvent event ) + { + if( SwingUtilities.isRightMouseButton( event ) ) + { + JPopupMenu contextMenu = new JPopupMenu(); + + /** + * Color Menus + */ + JMenu colorMenu = new JMenu( "Color" ); + + SettingsManager sm = mResourceManager.getSettingsManager(); + + colorMenu.add( new ColorSettingMenuItem( sm, + ColorSettingName.SPECTRUM_CURSOR ) ); + + colorMenu.add( new ColorSettingMenuItem( sm, + ColorSettingName.SPECTRUM_LINE ) ); + + colorMenu.add( new ColorSettingMenuItem( sm, + ColorSettingName.SPECTRUM_BACKGROUND ) ); + + colorMenu.add( new ColorSettingMenuItem( sm, + ColorSettingName.SPECTRUM_GRADIENT_BOTTOM ) ); + + colorMenu.add( new ColorSettingMenuItem( sm, + ColorSettingName.SPECTRUM_GRADIENT_TOP ) ); + + contextMenu.add( colorMenu ); + + /** + * Display items: fft and frame rate + */ + JMenu displayMenu = new JMenu( "Display" ); + contextMenu.add( displayMenu ); + + /** + * Amplification menu + */ + JMenu amplificationMenu = new JMenu( "Amplification" ); + amplificationMenu.add( + new AmplificationItem( ChannelSpectrumPanel.this, 50 ) ); + displayMenu.add( amplificationMenu ); + + /** + * Averaging menu + */ + JMenu averagingMenu = new JMenu( "Averaging" ); + averagingMenu.add( + new AveragingItem( ChannelSpectrumPanel.this, 4 ) ); + displayMenu.add( averagingMenu ); + + /** + * Baseline menu + */ + JMenu baselineMenu = new JMenu( "Baseline" ); + baselineMenu.add( + new BaselineItem( ChannelSpectrumPanel.this, 50 ) ); + displayMenu.add( baselineMenu ); + + /** + * FFT width + */ + JMenu fftWidthMenu = new JMenu( "FFT Width" ); + displayMenu.add( fftWidthMenu ); + + for( FFTWidth width: FFTWidth.values() ) + { + fftWidthMenu.add( new FFTWidthItem( mDFTProcessor, width ) ); + } + + /** + * DFT Processor Frame Rate + */ + JMenu frameRateMenu = new JMenu( "Frame Rate" ); + displayMenu.add( frameRateMenu ); + + frameRateMenu.add( new FrameRateItem( mDFTProcessor, 14 ) ); + frameRateMenu.add( new FrameRateItem( mDFTProcessor, 16 ) ); + frameRateMenu.add( new FrameRateItem( mDFTProcessor, 18 ) ); + frameRateMenu.add( new FrameRateItem( mDFTProcessor, 20 ) ); + frameRateMenu.add( new FrameRateItem( mDFTProcessor, 25 ) ); + frameRateMenu.add( new FrameRateItem( mDFTProcessor, 30 ) ); + frameRateMenu.add( new FrameRateItem( mDFTProcessor, 40 ) ); + frameRateMenu.add( new FrameRateItem( mDFTProcessor, 50 ) ); + + /** + * FFT Window Type + */ + JMenu fftWindowType = new JMenu( "Window Type" ); + displayMenu.add( fftWindowType ); + + for( WindowType type: WindowType.values() ) + { + fftWindowType.add( + new FFTWindowTypeItem( mDFTProcessor, type ) ); + } + + if( contextMenu != null ) + { + contextMenu.show( mOverlayPanel, + event.getX(), + event.getY() ); + } + } + } + + @Override + public void mousePressed( MouseEvent e ) {} + @Override + public void mouseReleased( MouseEvent e ) {} + } + + @Override + public int getAmplification() + { + return mSpectrumPanel.getAmplification(); + } + + @Override + public void setAmplification( int amplification ) + { + mSpectrumPanel.setAmplification( amplification ); + } + + @Override + public int getAveraging() + { + return mSpectrumPanel.getAveraging(); + } + + @Override + public void setAveraging( int averaging ) + { + mSpectrumPanel.setAveraging( averaging ); + } + + @Override + public int getBaseline() + { + return mSpectrumPanel.getBaseline(); + } + + @Override + public void setBaseline( int baseline ) + { + mSpectrumPanel.setBaseline( baseline ); + } + + public class DecimatingSampleAssembler implements Listener + { + private FloatHalfBandFilter mDecimationFilter = new FloatHalfBandFilter( + Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 ); + + private SampleAssembler mSampleAssembler; + + public DecimatingSampleAssembler( Listener listener ) + { + mSampleAssembler = new SampleAssembler( listener ); + mDecimationFilter.setListener( mSampleAssembler ); + } + + @Override + public void receive( Float sample ) + { + mDecimationFilter.receive( sample ); + } + } + + public class SampleAssembler implements Listener + { + private Listener mListener; + + public SampleAssembler( Listener listener ) + { + mListener = listener; + } + + @Override + public void receive( Float sample ) + { + mSamples[ mSamplePointer++ ] = sample; + + if( mSamplePointer >= mSamples.length ) + { + if( mEnabled.get() ) + { + mListener.receive( Arrays.copyOf( mSamples, + mSamples.length ) ); + } + + mSamplePointer = 0; + + if( mSamples.length != mSampleBufferSize ) + { + mSamples = new Float[ mSampleBufferSize ]; + } + } + } + } +} diff --git a/src/spectrum/DFTProcessor.java b/src/spectrum/DFTProcessor.java index 9b5c9b66..c9a68890 100644 --- a/src/spectrum/DFTProcessor.java +++ b/src/spectrum/DFTProcessor.java @@ -27,24 +27,26 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import log.Log; + +import org.jtransforms.fft.FloatFFT_1D; + import sample.Listener; import source.Source; import source.Source.SampleType; import source.tuner.FrequencyChangeListener; +import spectrum.converter.DFTResultsConverter; import dsp.filter.Window; import dsp.filter.Window.WindowType; -import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D; /** * Processes both complex samples or float samples and dispatches a float array * of DFT results, using configurable fft size and output dispatch timelines. */ public class DFTProcessor implements Listener, - DFTResultsProvider, FrequencyChangeListener { - private CopyOnWriteArrayList mListeners = - new CopyOnWriteArrayList(); + private CopyOnWriteArrayList mListeners = + new CopyOnWriteArrayList(); private ArrayBlockingQueue mQueue = new ArrayBlockingQueue( 200 ); @@ -56,14 +58,16 @@ public class DFTProcessor implements Listener, private FFTWidth mNewFFTWidth = FFTWidth.FFT04096; private double[] mWindow; - private WindowType mWindowType = Window.WindowType.HANNING; + private WindowType mWindowType = Window.WindowType.BLACKMAN; private FloatFFT_1D mFFT = new FloatFFT_1D( mFFTWidth.getWidth() ); private int mFrameRate; private int mSampleRate; - private float mOverlapPerFrame; - private float mOverlapResidual; + private int mFFTFloatsPerFrame; + private float mNewFloatsPerFrame; + private float mNewFloatsPerFrameToConsume; + private float mNewFloatResidual; private float[] mPreviousFrame = new float[ 8192 ]; private Float[] mCurrentBuffer; @@ -81,17 +85,7 @@ public class DFTProcessor implements Listener, public void dispose() { - try - { - clearScheduler(); - } - catch ( InterruptedException e ) - { - Log.error( "DFTProcessor - exception shutting down processor - " + - e.getLocalizedMessage() ); - } - - mScheduler.shutdown(); + stop(); mListeners.clear(); mQueue.clear(); @@ -165,54 +159,56 @@ public class DFTProcessor implements Listener, + "more than 1000 times per second -- requested setting:" + framesPerSecond ); } - else - { - mFrameRate = framesPerSecond; - calculateOverlap(); + mFrameRate = framesPerSecond; - /** - * Shutdown the scheduler and clear out any remaining tasks - */ - try - { - clearScheduler(); - } - catch ( InterruptedException e ) - { - Log.error( "DFTProcessor - exception while awaiting shutdown of " - + "calculation scheduler for reset" ); - } + calculateConsumptionRate(); - /** - * Reset the scheduler - */ - mScheduler = Executors.newScheduledThreadPool(1); - - /** - * Schedule the DFT to run calculations at a fixed rate - */ - int initialDelay = 0; - int period = (int)( 1000 / mFrameRate ); - TimeUnit unit = TimeUnit.MILLISECONDS; - - mScheduler.scheduleAtFixedRate( new DFTCalculationTask(), - initialDelay, period, unit ); - - } + restart(); } - /** - * Shuts down the scheduler and blocks up to 100 milliseconds until all - * tasks have completed. - */ - private void clearScheduler() throws InterruptedException + public void start() { - mScheduler.shutdown(); + /** + * Reset the scheduler + */ + mScheduler = Executors.newScheduledThreadPool(1); - mScheduler.awaitTermination( 100, TimeUnit.MILLISECONDS ); + /** + * Schedule the DFT to run calculations at a fixed rate + */ + int initialDelay = 0; + int period = (int)( 1000 / mFrameRate ); + TimeUnit unit = TimeUnit.MILLISECONDS; + + mScheduler.scheduleAtFixedRate( new DFTCalculationTask(), + initialDelay, period, unit ); } + + public void stop() + { + /** + * Shutdown the scheduler and clear out any remaining tasks + */ + try + { + mScheduler.shutdown(); + mScheduler.awaitTermination( 100, TimeUnit.MILLISECONDS ); + } + catch ( InterruptedException e ) + { + Log.error( "DFTProcessor - exception while awaiting shutdown of " + + "calculation scheduler for reset" ); + } + } + + public void restart() + { + stop(); + start(); + } + public int getCalculationsPerSecond() { return mFrameRate; @@ -226,7 +222,10 @@ public class DFTProcessor implements Listener, { if( !mQueue.offer( samples ) ) { - Log.error( "DFTProcessor - queue is full, purging queue, samples[" + samples + "]" ); + Log.error( "DFTProcessor - [" + mSampleType.toString() + + "]queue is full, purging queue, " + + "samples[" + samples + "]" ); + mQueue.clear(); mQueue.offer( samples ); } @@ -236,8 +235,6 @@ public class DFTProcessor implements Listener, { mCurrentBuffer = null; - long start = System.currentTimeMillis(); - try { mCurrentBuffer = mQueue.take(); @@ -252,107 +249,66 @@ public class DFTProcessor implements Listener, private float[] getSamples() { - int remaining; - - if( mSampleType == SampleType.COMPLEX ) - { - remaining = mFFTWidth.getWidth() * 2; - } - else - { - remaining = mFFTWidth.getWidth(); - } + int remaining = (int)mFFTFloatsPerFrame; + + float[] currentFrame = new float[ remaining ]; - float[] currentFrame = new float[ mFFTWidth.getWidth() * 2 ]; int currentFramePointer = 0; - - /* Get the integer component of the overlap, leaving the fractional - * residual for the next frame */ - float requiredOverlap = mOverlapPerFrame + mOverlapResidual; - int overlap = (int)requiredOverlap; - mOverlapResidual = requiredOverlap - overlap; - /* If positive overlap, we have to re-use samples from previous frame */ - if( overlap > 0 ) + float integralFloatsToConsume = mNewFloatsPerFrame + mNewFloatResidual; + + int newFloatsToConsumeThisFrame = (int)integralFloatsToConsume; + + mNewFloatResidual = integralFloatsToConsume - newFloatsToConsumeThisFrame; + + /* If the number of required floats for the fft is greater than the + * consumption rate per frame, we have to reach into the previous + * frame to makeup the difference. */ + if( newFloatsToConsumeThisFrame < remaining ) { - int previousFramePointer; - int length; + int previousFloatsRequired = remaining - newFloatsToConsumeThisFrame; - if( mSampleType == SampleType.COMPLEX ) - { - length = overlap * 2; - previousFramePointer = mPreviousFrame.length - length; - } - else - { - length = overlap; - previousFramePointer = (int)(mPreviousFrame.length / 2) - length; - } + System.arraycopy( mPreviousFrame, + mPreviousFrame.length - previousFloatsRequired, + currentFrame, + currentFramePointer, + previousFloatsRequired ); + + remaining -= previousFloatsRequired; + currentFramePointer += previousFloatsRequired; + } - System.arraycopy( mPreviousFrame, previousFramePointer, - currentFrame, currentFramePointer, length ); - - remaining -= length; - currentFramePointer += length; - } - - /* If negative overlap, we have to throw away samples */ - else - { - if( mSampleType == SampleType.COMPLEX ) - { - purge( -( overlap * 2 ) ); - } - else - { - purge( -overlap ); - } - } - + /* Fill the rest of the buffer with new samples */ while( mRunning.get() && remaining > 0 ) { if( mCurrentBuffer == null || - mCurrentBufferPointer == mCurrentBuffer.length ) + mCurrentBufferPointer >= mCurrentBuffer.length ) { getNextBuffer(); } int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer; - if( samplesAvailable >= remaining ) + while( remaining > 0 && samplesAvailable > 0 ) { - while( remaining > 0 ) - { - currentFrame[ currentFramePointer ] = - (float)mCurrentBuffer[ mCurrentBufferPointer ]; - - currentFramePointer++; - mCurrentBufferPointer++; - remaining --; - } - - mCurrentBufferPointer += remaining; - remaining = 0; - } - else - { - while( samplesAvailable > 0 ) - { - currentFrame[ currentFramePointer ] = - (float)mCurrentBuffer[ mCurrentBufferPointer ]; + currentFrame[ currentFramePointer++ ] = + (float)mCurrentBuffer[ mCurrentBufferPointer++ ]; - currentFramePointer++; - mCurrentBufferPointer++; - samplesAvailable--; - remaining--; - } + samplesAvailable--; + remaining--; + newFloatsToConsumeThisFrame--; } } - - /* If frames are overlapping, store current frame to use next time */ - if( mOverlapPerFrame > 0 ) + + /* If the incoming float rate is greater than the fft consumption rate, + * then we have to purge some floats, otherwise, store the previous + * frame, because we have overlapping frames */ + if( newFloatsToConsumeThisFrame > 0 ) + { + purge( newFloatsToConsumeThisFrame ); + } + else { - mPreviousFrame = null; mPreviousFrame = Arrays.copyOf( currentFrame, currentFrame.length ); } @@ -367,7 +323,7 @@ public class DFTProcessor implements Listener, if( mSampleType == SampleType.FLOAT ) { - mFFT.realForwardFull( samples ); + mFFT.realForward( samples ); } else { @@ -389,7 +345,7 @@ public class DFTProcessor implements Listener, while( mRunning.get() && samplesToPurge > 0 ) { if( mCurrentBuffer == null || - mCurrentBufferPointer == mCurrentBuffer.length ) + mCurrentBufferPointer >= mCurrentBuffer.length ) { getNextBuffer(); } @@ -415,56 +371,20 @@ public class DFTProcessor implements Listener, */ private void dispatch( float[] results ) { - float dc = Math.abs( results[ 0 ] ); + Iterator it = mListeners.iterator(); - /* Ensure we have power in the DC power bin (bin 0), otherwise don't - * dispatch the results */ - if( dc != 0 ) + while( it.hasNext() ) { - float[] processed = new float[ results.length / 2 ]; - - int half = results.length / 2; - int quarter = results.length / 4; - - for( int x = 2; x < half; x += 2 ) - { - processed[ quarter + ( x / 2 ) ] = - (float)( Math.log10( - (double)( Math.abs( results[ x ] ) / - dc ) ) ); - } - - for( int x = 0; x < quarter - 1; x++ ) - { - processed[ x ] = - (float)( Math.log10( - (double)( Math.abs( results[ half + ( ( x + 1 ) * 2 ) ] ) / - dc ) ) ); - } - - //Real bin n/2 -1 - processed[ quarter - 1 ] = (float)( Math.log10( - (double)( Math.abs( results[ 1 ] ) / dc ) ) ); - - Iterator it = mListeners.iterator(); - - while( it.hasNext() ) - { - it.next().receive( processed ); - } + it.next().receive( results ); } - - results = null; } - @Override - public void addListener( DFTResultsListener listener ) + public void addConverter( DFTResultsConverter listener ) { mListeners.add( listener ); } - @Override - public void removeListener( DFTResultsListener listener ) + public void removeConverter( DFTResultsConverter listener ) { mListeners.remove( listener ); } @@ -483,11 +403,6 @@ public class DFTProcessor implements Listener, mRunning.set( false ); } - else - { - Log.error( "DFTProcessor - calculation already in progress" - + " - skipping this iteration" ); - } } } @@ -502,12 +417,18 @@ public class DFTProcessor implements Listener, { mFFTWidth = mNewFFTWidth; - calculateOverlap(); + calculateConsumptionRate(); - mWindow = Window.getWindow( WindowType.HANNING, - ( mFFTWidth.getWidth() * 2 ) ); - - mPreviousFrame = new float[ mFFTWidth.getWidth() * 2 ]; + setWindowType( mWindowType ); + + if( mSampleType == SampleType.COMPLEX ) + { + mPreviousFrame = new float[ mFFTWidth.getWidth() * 2 ]; + } + else + { + mPreviousFrame = new float[ mFFTWidth.getWidth() ]; + } mFFT = new FloatFFT_1D( mFFTWidth.getWidth() ); } @@ -523,24 +444,30 @@ public class DFTProcessor implements Listener, { mSampleRate = bandwidth; - calculateOverlap(); + calculateConsumptionRate(); } /** - * Positive overlap means we have to re-use samples from the previous - * frame * - * Negative overlap means we have to throw away samples */ - private void calculateOverlap() + private void calculateConsumptionRate() { - mOverlapPerFrame = 0.0f; - mOverlapResidual = 0.0f; + mNewFloatResidual = 0.0f; - int requiredSampleRate = mFFTWidth.getWidth() * mFrameRate; + mNewFloatsPerFrame = ( (float)mSampleRate / (float)mFrameRate ) * + ( mSampleType == SampleType.COMPLEX ? 2.0f : 1.0f ); - int overlap = requiredSampleRate - mSampleRate; + mFFTFloatsPerFrame = ( mSampleType == SampleType.COMPLEX ? + mFFTWidth.getWidth() * 2 : + mFFTWidth.getWidth() ); - mOverlapPerFrame = (float)overlap / (float)mFrameRate; + if( mFFTFloatsPerFrame < mNewFloatsPerFrame ) + { + mNewFloatsPerFrameToConsume = mFFTFloatsPerFrame; + } + else + { + mNewFloatsPerFrameToConsume = mNewFloatsPerFrame; + } } } diff --git a/src/spectrum/OverlayPanel.java b/src/spectrum/OverlayPanel.java index 1abad4ab..d58c993b 100644 --- a/src/spectrum/OverlayPanel.java +++ b/src/spectrum/OverlayPanel.java @@ -40,12 +40,12 @@ import source.tuner.FrequencyChangeListener; import source.tuner.TunerChannel; import controller.ResourceManager; import controller.channel.Channel; -import controller.channel.Channel.ChannelEvent; import controller.channel.Channel.ChannelType; -import controller.channel.ChannelListener; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEventListener; public class OverlayPanel extends JPanel - implements ChannelListener, + implements ChannelEventListener, FrequencyChangeListener, SettingChangeListener { @@ -362,7 +362,7 @@ public class OverlayPanel extends JPanel for( Channel channel: mVisibleChannels ) { //Choose the correct background color to use - if( channel.getSelected() ) + if( channel.isSelected() ) { graphics.setColor( mColorChannelConfigSelected ); } @@ -510,9 +510,11 @@ public class OverlayPanel extends JPanel */ @Override @SuppressWarnings( "incomplete-switch" ) - public void occurred( Channel channel, ChannelEvent component ) + public void channelChanged( ChannelEvent event ) { - switch( component ) + Channel channel = event.getChannel(); + + switch( event.getEvent() ) { case CHANNEL_ADDED: if( !mChannels.contains( channel ) ) diff --git a/src/spectrum/SpectralDisplayAdjuster.java b/src/spectrum/SpectralDisplayAdjuster.java new file mode 100644 index 00000000..a627b966 --- /dev/null +++ b/src/spectrum/SpectralDisplayAdjuster.java @@ -0,0 +1,55 @@ +package spectrum; + +public interface SpectralDisplayAdjuster +{ + /** + * Gets the amplification multiplier that is applied against the FFT bin + * results when displaying the value in the spectral display. + * + * @return amplification value ( 1 - 100 ) + */ + public int getAmplification(); + + /** + * Sets the amplification multiplier that is applied against the FFT bin + * results when displaying the value in the spectral display. + * + * @param amplification value ( 1 - 100 ) + */ + public void setAmplification( int amplification ); + + /** + * Gets the averaging value indicating the number of FFT results that are + * averaged to produce each FFT results output. + * + * @return averaging value ( 2 - 50 ) + */ + public int getAveraging(); + + /** + * Sets the averaging value indicating the number of FFT results to average + * when producing an FFT output result. + * @param averaging value ( 2 - 50 ) + */ + public void setAveraging( int averaging ); + + /** + * Gets the baseline used when plotting the FFT bin results in the FFT + * spectral display. This value determines the vertical position within the + * spectral display upon which the amplified values are added and then + * plotted. + * + * @return averaging value ( 1 - 50 ) + */ + public int getBaseline(); + + /** + * Sets the baseline used when plotting the FFT bin results in the FFT + * spectral display. This value determines the vertical position within the + * spectral display upon which the amplified values are added and then + * plotted. + * + * @param baseline value ( 1 - 50 ) + */ + public void setBaseline( int baseline ); +} diff --git a/src/spectrum/SpectralDisplayPanel.java b/src/spectrum/SpectralDisplayPanel.java index 437a337c..1987a3c9 100644 --- a/src/spectrum/SpectralDisplayPanel.java +++ b/src/spectrum/SpectralDisplayPanel.java @@ -29,6 +29,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.text.DecimalFormat; +import java.text.Format; import java.util.ArrayList; import javax.swing.JCheckBoxMenuItem; @@ -55,13 +56,21 @@ import source.SourceException; import source.tuner.FrequencyChangeListener; import source.tuner.Tuner; import source.tuner.TunerSelectionListener; +import spectrum.converter.ComplexDecibelConverter; +import spectrum.converter.DFTResultsConverter; +import spectrum.menu.AmplificationItem; +import spectrum.menu.AveragingItem; +import spectrum.menu.BaselineItem; +import spectrum.menu.FFTWidthItem; +import spectrum.menu.FFTWindowTypeItem; +import spectrum.menu.FrameRateItem; import com.jidesoft.swing.JideSplitPane; import controller.ResourceManager; import controller.channel.Channel; -import controller.channel.Channel.ChannelEvent; -import controller.channel.ChannelListener; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEventListener; import decode.DecoderType; import dsp.filter.Window.WindowType; @@ -74,7 +83,7 @@ import dsp.filter.Window.WindowType; * in the spectral and waterfall displays. */ public class SpectralDisplayPanel extends JPanel - implements ChannelListener, + implements ChannelEventListener, Listener, FrequencyChangeListener, SettingChangeListener, @@ -98,8 +107,9 @@ public class SpectralDisplayPanel extends JPanel private JLayeredPane mLayeredPanel; private SpectrumPanel mSpectrumPanel; private WaterfallPanel mWaterfallPanel; - private OverlayPanel mChannelPanel; + private OverlayPanel mOverlayPanel; private DFTProcessor mDFTProcessor; + private DFTResultsConverter mDFTConverter; private ResourceManager mResourceManager; private Tuner mTuner; @@ -122,14 +132,17 @@ public class SpectralDisplayPanel extends JPanel mDFTProcessor.dispose(); mDFTProcessor = null; + mDFTConverter.dispose(); + mDFTConverter = null; + mSpectrumPanel.dispose(); mSpectrumPanel = null; mWaterfallPanel.dispose(); mWaterfallPanel = null; - mChannelPanel.dispose(); - mChannelPanel = null; + mOverlayPanel.dispose(); + mOverlayPanel = null; mResourceManager = null; @@ -193,9 +206,9 @@ public class SpectralDisplayPanel extends JPanel @Override public void settingChanged( Setting setting ) { - if( mChannelPanel != null ) + if( mOverlayPanel != null ) { - mChannelPanel.settingChanged( setting ); + mOverlayPanel.settingChanged( setting ); } if( mWaterfallPanel != null ) @@ -274,14 +287,14 @@ public class SpectralDisplayPanel extends JPanel //Create the spectrum panel mSpectrumPanel = new SpectrumPanel( mResourceManager ); - //Create the channel panel - mChannelPanel = new OverlayPanel( mResourceManager ); - mChannelPanel.addMouseListener( mouser ); - mChannelPanel.addMouseMotionListener( mouser ); + //Create the overlay panel + mOverlayPanel = new OverlayPanel( mResourceManager ); + mOverlayPanel.addMouseListener( mouser ); + mOverlayPanel.addMouseMotionListener( mouser ); //Add the spectrum and channel panels to the layered panel mLayeredPanel.add( mSpectrumPanel, new Integer( 0 ), 0 ); - mLayeredPanel.add( mChannelPanel, new Integer( 1 ), 0 ); + mLayeredPanel.add( mOverlayPanel, new Integer( 1 ), 0 ); //Create the waterfall mWaterfallPanel = new WaterfallPanel( mResourceManager ); @@ -311,8 +324,11 @@ public class SpectralDisplayPanel extends JPanel * spectrum panel to receive the processed dft results */ mDFTProcessor = new DFTProcessor( SampleType.COMPLEX ); - mDFTProcessor.addListener( (DFTResultsListener)mSpectrumPanel ); - mDFTProcessor.addListener( (DFTResultsListener)mWaterfallPanel ); + mDFTConverter = new ComplexDecibelConverter(); + mDFTProcessor.addConverter( mDFTConverter ); + + mDFTConverter.addListener( (DFTResultsListener)mSpectrumPanel ); + mDFTConverter.addListener( (DFTResultsListener)mWaterfallPanel ); } /** @@ -321,17 +337,17 @@ public class SpectralDisplayPanel extends JPanel @Override public void frequencyChanged( long frequency, int bandwidth ) { - mChannelPanel.frequencyChanged( frequency, bandwidth ); + mOverlayPanel.frequencyChanged( frequency, bandwidth ); mDFTProcessor.frequencyChanged( frequency, bandwidth ); } /** - * Channel config change events + * Channel change events */ @Override - public void occurred( Channel config, ChannelEvent component ) + public void channelChanged( ChannelEvent event ) { - mChannelPanel.occurred( config, component ); + mOverlayPanel.channelChanged( event ); } @Override @@ -402,7 +418,7 @@ public class SpectralDisplayPanel extends JPanel Component c = e.getComponent(); mSpectrumPanel.setBounds( 0, 0, c.getWidth(), c.getHeight() ); - mChannelPanel.setBounds( 0, 0, c.getWidth(), c.getHeight() ); + mOverlayPanel.setBounds( 0, 0, c.getWidth(), c.getHeight() ); } @Override @@ -432,24 +448,24 @@ public class SpectralDisplayPanel extends JPanel private void update( MouseEvent event ) { - if( event.getComponent() == mChannelPanel ) + if( event.getComponent() == mOverlayPanel ) { - mChannelPanel.setCursorLocation( event.getPoint() ); + mOverlayPanel.setCursorLocation( event.getPoint() ); } else { mWaterfallPanel.setCursorLocation( event.getPoint() ); mWaterfallPanel.setCursorFrequency( - mChannelPanel.getFrequencyFromAxis( event.getPoint().x ) ); + mOverlayPanel.getFrequencyFromAxis( event.getPoint().x ) ); } } @Override public void mouseEntered( MouseEvent e ) { - if( e.getComponent() == mChannelPanel ) + if( e.getComponent() == mOverlayPanel ) { - mChannelPanel.setCursorVisible( true ); + mOverlayPanel.setCursorVisible( true ); } else { @@ -460,7 +476,7 @@ public class SpectralDisplayPanel extends JPanel @Override public void mouseExited( MouseEvent e ) { - mChannelPanel.setCursorVisible( false ); + mOverlayPanel.setCursorVisible( false ); mWaterfallPanel.setCursorVisible( false ); } @@ -480,12 +496,12 @@ public class SpectralDisplayPanel extends JPanel } long frequency = - mChannelPanel.getFrequencyFromAxis( event.getX() ); + mOverlayPanel.getFrequencyFromAxis( event.getX() ); - if( event.getComponent() == mChannelPanel ) + if( event.getComponent() == mOverlayPanel ) { ArrayList channels = - mChannelPanel.getChannelsAtFrequency( frequency ); + mOverlayPanel.getChannelsAtFrequency( frequency ); for( Channel channel: channels ) { @@ -562,6 +578,44 @@ public class SpectralDisplayPanel extends JPanel JMenu displayMenu = new JMenu( "Display" ); contextMenu.add( displayMenu ); + if( event.getComponent() == mWaterfallPanel ) + { + /** + * Baseline menu + */ + JMenu baselineMenu = new JMenu( "Baseline" ); + baselineMenu.add( + new BaselineItem( mWaterfallPanel, 50 ) ); + displayMenu.add( baselineMenu ); + } + else + { + /** + * Amplification menu + */ + JMenu amplificationMenu = new JMenu( "Amplify" ); + amplificationMenu.add( + new AmplificationItem( mSpectrumPanel, 50 ) ); + displayMenu.add( amplificationMenu ); + + /** + * Averaging menu + */ + JMenu averagingMenu = new JMenu( "Average" ); + averagingMenu.add( + new AveragingItem( mSpectrumPanel, 4 ) ); + displayMenu.add( averagingMenu ); + + /** + * Baseline menu + */ + JMenu baselineMenu = new JMenu( "Baseline" ); + baselineMenu.add( + new BaselineItem( mSpectrumPanel, 50 ) ); + displayMenu.add( baselineMenu ); + } + + /** * FFT width */ @@ -602,9 +656,9 @@ public class SpectralDisplayPanel extends JPanel if( contextMenu != null ) { - if( event.getComponent() == mChannelPanel ) + if( event.getComponent() == mOverlayPanel ) { - contextMenu.show( mChannelPanel, + contextMenu.show( mOverlayPanel, event.getX(), event.getY() ); } @@ -658,95 +712,6 @@ public class SpectralDisplayPanel extends JPanel } } - public class FFTWidthItem extends JCheckBoxMenuItem - { - private static final long serialVersionUID = 1L; - - private DFTProcessor mDFTProcessor; - private FFTWidth mFFTWidth; - - public FFTWidthItem( DFTProcessor processor, FFTWidth width ) - { - super( width.getLabel() ); - - mDFTProcessor = processor; - mFFTWidth = width; - - if( processor.getFFTWidth() == mFFTWidth ) - { - setSelected( true ); - } - - addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent arg0 ) - { - mDFTProcessor.setFFTSize( mFFTWidth ); - } - } ); - } - } - - public class FFTWindowTypeItem extends JCheckBoxMenuItem - { - private static final long serialVersionUID = 1L; - - private DFTProcessor mDFTProcessor; - private WindowType mWindowType; - - public FFTWindowTypeItem( DFTProcessor processor, WindowType windowType ) - { - super( windowType.toString() ); - - mDFTProcessor = processor; - mWindowType = windowType; - - if( processor.getWindowType() == mWindowType ) - { - setSelected( true ); - } - - addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent arg0 ) - { - mDFTProcessor.setWindowType( mWindowType ); - } - } ); - } - } - - public class FrameRateItem extends JCheckBoxMenuItem - { - private static final long serialVersionUID = 1L; - - private DFTProcessor mDFTProcessor; - private int mFrameRate; - - public FrameRateItem( DFTProcessor processor, int frameRate ) - { - super( String.valueOf( frameRate ) ); - - mDFTProcessor = processor; - mFrameRate = frameRate; - - if( processor.getFrameRate() == mFrameRate ) - { - setSelected( true ); - } - - addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent arg0 ) - { - mDFTProcessor.setFrameRate( mFrameRate ); - } - } ); - } - } /** * Context menu item to provide one-click access to starting a channel diff --git a/src/spectrum/SpectrumPanel.java b/src/spectrum/SpectrumPanel.java index be9035c1..7f30fd1b 100644 --- a/src/spectrum/SpectrumPanel.java +++ b/src/spectrum/SpectrumPanel.java @@ -29,33 +29,42 @@ import java.awt.geom.Line2D; import javax.swing.JPanel; +import log.Log; import settings.ColorSetting; import settings.ColorSetting.ColorSettingName; +import settings.IntegerSetting; import settings.Setting; import settings.SettingChangeListener; import buffer.FloatArrayCircularAveragingBuffer; +import buffer.FloatAveragingBuffer; import controller.ResourceManager; public class SpectrumPanel extends JPanel implements DFTResultsListener, - SettingChangeListener + SettingChangeListener, + SpectralDisplayAdjuster { private static final long serialVersionUID = 1L; /* Set display bins size to 1, so that we're guaranteed a reset to the * correct width once the first sample set arrives */ private float[] mDisplayFFTBins = new float[ 1 ]; - private FloatArrayCircularAveragingBuffer mFFTAveragingBuffer = - new FloatArrayCircularAveragingBuffer( 4 ); + private FloatArrayCircularAveragingBuffer mFFTAveragingBuffer; + private FloatAveragingBuffer mScaleBuffer = new FloatAveragingBuffer( 20 ); + private int mAmplification = 50; + private int mAveraging = 4; + private int mBaseline = 50; /** * Spectral Display Color Settings */ - private static final String sSPECTRUM_BACKGROUND = "spectrum_background"; - private static final String sSPECTRUM_GRADIENT_TOP = "spectrum_gradient_top"; - private static final String sSPECTRUM_GRADIENT_BOTTOM = "spectrum_gradient_bottom"; - private static final String sSPECTRUM_LINE = "spectrum_line"; - private static final int sSPECTRUM_TRANSLUCENCY = 128; + private static final String SPECTRUM_BACKGROUND = "spectrum_background"; + private static final String SPECTRUM_GRADIENT_TOP = "spectrum_gradient_top"; + private static final String SPECTRUM_GRADIENT_BOTTOM = "spectrum_gradient_bottom"; + private static final String SPECTRUM_LINE = "spectrum_line"; + private static final String SPECTRUM_AVERAGING_SIZE = "spectrum_averaging_size"; + private static final int SPECTRUM_TRANSLUCENCY = 128; + private Color mColorSpectrumBackground; private Color mColorSpectrumGradientTop; @@ -63,14 +72,16 @@ public class SpectrumPanel extends JPanel private Color mColorSpectrumLine; private float mSpectrumInset = 20.0f; - + private ResourceManager mResourceManager; public SpectrumPanel( ResourceManager resourceManager ) { mResourceManager = resourceManager; - setColors(); + getSettings(); + + setAveraging( mAveraging ); } public void dispose() @@ -78,7 +89,50 @@ public class SpectrumPanel extends JPanel mResourceManager = null; } - private void setColors() + public void setAveraging( int size ) + { + mAveraging = size; + + mResourceManager.getSettingsManager().setIntegerSetting( + SPECTRUM_AVERAGING_SIZE, mAveraging ); + + mFFTAveragingBuffer = + new FloatArrayCircularAveragingBuffer( mAveraging ); + } + + public int getAveraging() + { + return mAveraging; + } + + public void setAmplification( int amplification ) + { + mAmplification = amplification; + } + + public int getAmplification() + { + return mAmplification; + } + + public void setBaseline( int baseline ) + { + mBaseline = baseline; + } + + public int getBaseline() + { + return mBaseline; + } + + public void clearSpectrum() + { + mDisplayFFTBins = null; + + repaint(); + } + + private void getSettings() { mColorSpectrumBackground = getColor( ColorSettingName.SPECTRUM_BACKGROUND ); @@ -90,6 +144,16 @@ public class SpectrumPanel extends JPanel getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP ); mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE ); + + mAveraging = getIntegerSetting( "spectrum_averaging_size", 4 ); + } + + private int getIntegerSetting( String name, int defaultValue ) + { + IntegerSetting setting = mResourceManager.getSettingsManager() + .getIntegerSetting( name, defaultValue ); + + return setting.getValue(); } private Color getColor( ColorSettingName name ) @@ -203,25 +267,45 @@ public class SpectrumPanel extends JPanel //If we have FFT data to display ... if( mDisplayFFTBins != null ) { + int binCount = mDisplayFFTBins.length - 1; + + float amplify = mAmplification / 50.0f; + float insideHeight = size.height - mSpectrumInset; + float baseline = ( insideHeight * ( 50.0f / (float)mBaseline ) ); + + /* Use the DC component as a reference to scale values to panel height */ + float scale = mScaleBuffer.get( ( insideHeight * 0.75f ) / + mDisplayFFTBins[ binCount ] ); + float insideWidth = size.width; - float binSize = insideWidth / ( mDisplayFFTBins.length - 1 ); - float heightMultiplier = (float)( insideHeight * .15 ); + float binSize = insideWidth / ( binCount ); - float spectrumMiddle = insideHeight * .23f; - - for( int x = 0; x < mDisplayFFTBins.length; x++ ) + for( int x = 0; x < binCount; x++ ) { - float height = spectrumMiddle - ( mDisplayFFTBins[ x ] * heightMultiplier ); + /** + * Height is an inverse value reflecting the drawable spectral + * display vertical axis between the inset value at the bottom + * and the top of the display. Values range from: + * + * top = 0 + * bottom = insideHeight = panel height - mSpectrumInset + */ + float height = baseline - ( mDisplayFFTBins[ x ] * scale * amplify ); - if( height > size.getHeight() - mSpectrumInset ) + if( height > insideHeight ) { - height = (float)size.getHeight() - mSpectrumInset; + height = insideHeight; } - spectrumShape.lineTo( binSize * x, height ); + if( height < 0 ) + { + height = 0; + } + + spectrumShape.lineTo( ( x * binSize ), height ); } } //Otherwise show an empty spectrum diff --git a/src/spectrum/WaterfallPanel.java b/src/spectrum/WaterfallPanel.java index b19101e6..4fac1214 100644 --- a/src/spectrum/WaterfallPanel.java +++ b/src/spectrum/WaterfallPanel.java @@ -47,7 +47,8 @@ import controller.ResourceManager; */ public class WaterfallPanel extends JPanel implements DFTResultsListener, Pausable, - SettingChangeListener + SettingChangeListener, + SpectralDisplayAdjuster { private static final long serialVersionUID = 1L; private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.00000" ); @@ -61,11 +62,7 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, private ColorModel mColorModel = WaterfallColorModel.getDefaultColorModel(); private Color mColorSpectrumCursor; private Image mWaterfallImage; - - float mAverage = 0.0f; - double mAlpha = 0.95d; - int mScalor = 85; - int temp = 0; + private int mBaseline = 50; private Point mCursorLocation = new Point( 0, 0 ); private boolean mCursorVisible = false; @@ -108,7 +105,7 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, mWaterfallImage = createImage( mMemoryImageSource ); } - + /** * Pausable interface - pauses updates to the screen */ @@ -210,9 +207,9 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, public void receive( float[] update ) { //If our FFT size changes, reset our pixel map and image source - if( mFFTSize != update.length ) + if( mFFTSize != update.length - 1 ) { - mFFTSize = update.length; + mFFTSize = update.length - 1; reset(); } @@ -221,37 +218,41 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, System.arraycopy( mPixels, 0, mPixels, mFFTSize, mPixels.length - mFFTSize ); - //Convert the power spectrum bin values into a byte (0-255) - //value to use with the color mapping + float baseline = mBaseline / 50.0f; - double averagingTotal = 0.0; + /** + * Scale the power spectrum bin values into a byte range (0-255) value + * using the last bin as the scaling factor + */ + float scale = 256.0f / update[ update.length - 1 ]; + + double sum = 0.0d; - for( int x = 0; x < update.length; x++ ) + for( int x = 0; x < update.length - 1; x++ ) { - float value = update[ x ]; - - averagingTotal += value; - - //Average the values toward zero - float pixel = value - mAverage; - - //If we're below the average, make the negative value a positive - if( pixel < 0 ) - { -// pixel = -pixel; - pixel = 0.0f; - } - - //Scale the value - pixel *= mScalor; - -// mPixels[ x ] = (byte)pixel; - mPixels[ x ] = (byte)( mIntraBinAveragingBuffer.get( pixel ) ); + sum += update[ x ]; } - //Update the average value - mAverage = (float) averagingTotal / update.length; + float average = (float)( sum / (double)update.length - 1 ); + + for( int x = 0; x < update.length - 1; x++ ) + { + float value = ( update[ x ] - average ) * scale * baseline; + if( value < 0 ) + { + mPixels[ x ] = 0; + } + else if( value > 255 ) + { + mPixels[ x ] = (byte)255; + } + else + { + mPixels[ x ] = (byte)value; + } + } + if( !mPaused.get() ) { EventQueue.invokeLater( new Runnable() @@ -284,4 +285,42 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, @Override public void settingDeleted( Setting setting ) {} + + @Override + public void setBaseline( int baseline ) + { + mBaseline = baseline; + } + + @Override + public int getBaseline() + { + return mBaseline; + } + + @Override + public int getAmplification() + { + /* Not Implemented */ + return 0; + } + + @Override + public void setAmplification( int amplification ) + { + /* Not Implemented */ + } + + @Override + public int getAveraging() + { + /* Not Implemented */ + return 0; + } + + @Override + public void setAveraging( int averaging ) + { + /* Not Implemented */ + } } diff --git a/src/spectrum/converter/ComplexDecibelConverter.java b/src/spectrum/converter/ComplexDecibelConverter.java new file mode 100644 index 00000000..e88e0480 --- /dev/null +++ b/src/spectrum/converter/ComplexDecibelConverter.java @@ -0,0 +1,62 @@ +package spectrum.converter; + +public class ComplexDecibelConverter extends DFTResultsConverter +{ + /** + * Converts the output of the JTransforms FloatFFT_1D.complexForward() + * calculation into a normalized power spectrum in decibels, per description + * in Lyons, Understanding Digital Signal Processing, 3e, page 141. + */ + @Override + public void receive( float[] results ) + { + float dc = Math.abs( results[ 0 ] ); + + /* Ensure we have power in the DC power bin (bin 0), otherwise don't + * dispatch the results */ + if( dc > 0 ) + { + float[] processed = new float[ results.length / 2 + 1 ]; + + /** + * Output from JTransforms 3.0 has the lower and upper halves of the + * spectrum swapped. + */ + int half = processed.length / 2; + + for( int x = 0; x < processed.length - 2; x ++ ) + { + int index = ( x + 1 ) * 2; + + double magnitude2 = ( ( results[ index ] * results[ index ] ) + + ( results[ index + 1 ] * results[ index + 1 ] ) ); + + if( x < half ) + { + processed[ x + half ] = 10.0f * + (float)( Math.log10( magnitude2 / dc ) ); + } + else + { + processed[ x - half ] = 10.0f * + (float)( Math.log10( magnitude2 / dc ) ); + } + } + + /** + * Place scaled dc component in last bin of processed array as + * scaling reference + */ + processed[ results.length / 2 ] = processed[ half ]; + + /** + * Repair the erroneous DC component bin by using the average of + * the neighboring bins + */ + processed[ half ] = ( processed[ half - 1 ] + + processed[ half + 1 ] / 2.0f ); + + dispatch( processed ); + } + } +} diff --git a/src/spectrum/converter/DFTResultsConverter.java b/src/spectrum/converter/DFTResultsConverter.java new file mode 100644 index 00000000..82eada92 --- /dev/null +++ b/src/spectrum/converter/DFTResultsConverter.java @@ -0,0 +1,46 @@ +package spectrum.converter; + +import java.util.concurrent.CopyOnWriteArrayList; + +import spectrum.DFTResultsListener; +import spectrum.DFTResultsProvider; + +public abstract class DFTResultsConverter + implements DFTResultsListener, DFTResultsProvider +{ + private CopyOnWriteArrayList mListeners = + new CopyOnWriteArrayList(); + + /** + * DFT Results Converter - for converting the output of the JTransforms + * FFT library real and complex forward results + */ + public DFTResultsConverter() + { + } + + public void dispose() + { + mListeners.clear(); + } + + @Override + public void addListener( DFTResultsListener listener ) + { + mListeners.add( listener ); + } + + @Override + public void removeListener( DFTResultsListener listener ) + { + mListeners.remove( listener ); + } + + protected void dispatch( float[] results ) + { + for( DFTResultsListener listener: mListeners ) + { + listener.receive( results ); + } + } +} diff --git a/src/spectrum/converter/RealDecibelConverter.java b/src/spectrum/converter/RealDecibelConverter.java new file mode 100644 index 00000000..69fc9762 --- /dev/null +++ b/src/spectrum/converter/RealDecibelConverter.java @@ -0,0 +1,38 @@ +package spectrum.converter; + +public class RealDecibelConverter extends DFTResultsConverter +{ + /** + * Converts the output of the JTransforms FloatFFT_1D.realForward() + * calculation into a normalized power spectrum in decibels, per description + * in Lyons, Understanding Digital Signal Processing, 3e, page 141. + */ + @Override + public void receive( float[] results ) + { + float dc = Math.abs( results[ 0 ] ); + + /* Ensure we have power in the DC power bin (bin 0), otherwise don't + * dispatch the results */ + if( dc != 0 ) + { + /* Note: we're only processing 1/4 of the output samples */ + float[] processed = new float[ results.length / 4 + 1 ]; + + for( int x = 0; x < processed.length - 2; x ++ ) + { + int index = ( x + 1 ) * 2; + + double magnitude = ( ( results[ index ] * results[ index ] ) + + ( results[ index + 1 ] * results[ index + 1 ] ) ); + + processed[ x ] = 10.0f * + (float)( Math.log10( magnitude / dc ) ); + } + + processed[ processed.length - 1 ] = processed[ 0 ]; + + dispatch( processed ); + } + } +} diff --git a/src/spectrum/menu/AmplificationItem.java b/src/spectrum/menu/AmplificationItem.java new file mode 100644 index 00000000..13bd307a --- /dev/null +++ b/src/spectrum/menu/AmplificationItem.java @@ -0,0 +1,57 @@ +package spectrum.menu; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.JSlider; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import spectrum.SpectralDisplayAdjuster; + +public class AmplificationItem extends JSlider implements ChangeListener +{ + private static final long serialVersionUID = 1L; + + private SpectralDisplayAdjuster mAdjuster; + private int mDefaultValue; + + public AmplificationItem( SpectralDisplayAdjuster adjuster, int defaultValue ) + { + super( JSlider.HORIZONTAL, 1, 100, adjuster.getAmplification() ); + mDefaultValue = defaultValue; + + mAdjuster = adjuster; + + setMajorTickSpacing( 10 ); + setMinorTickSpacing( 5 ); + setPaintTicks( true ); + + addChangeListener( this ); + + addMouseListener( new MouseListener() + { + @Override + public void mouseClicked( MouseEvent event ) + { + if( event.getClickCount() == 2 ) + { + AmplificationItem.this.setValue( mDefaultValue ); + } + } + + public void mouseReleased( MouseEvent arg0 ) {} + public void mousePressed( MouseEvent arg0 ) {} + public void mouseExited( MouseEvent arg0 ) {} + public void mouseEntered( MouseEvent arg0 ) {} + } ); + } + + @Override + public void stateChanged( ChangeEvent event ) + { + int value = ((JSlider)event.getSource()).getValue(); + + mAdjuster.setAmplification( value ); + } +} diff --git a/src/spectrum/menu/AveragingItem.java b/src/spectrum/menu/AveragingItem.java new file mode 100644 index 00000000..a3a531e4 --- /dev/null +++ b/src/spectrum/menu/AveragingItem.java @@ -0,0 +1,57 @@ +package spectrum.menu; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.JSlider; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import spectrum.SpectralDisplayAdjuster; + +public class AveragingItem extends JSlider implements ChangeListener +{ + private static final long serialVersionUID = 1L; + + private SpectralDisplayAdjuster mAdjuster; + private int mDefaultValue; + + public AveragingItem( SpectralDisplayAdjuster adjuster, int defaultValue ) + { + super( JSlider.HORIZONTAL, 2, 50, adjuster.getAveraging() ); + mDefaultValue = defaultValue; + + mAdjuster = adjuster; + + setMajorTickSpacing( 10 ); + setMinorTickSpacing( 5 ); + setPaintTicks( true ); + + addChangeListener( this ); + + addMouseListener( new MouseListener() + { + @Override + public void mouseClicked( MouseEvent event ) + { + if( event.getClickCount() == 2 ) + { + AveragingItem.this.setValue( mDefaultValue ); + } + } + + public void mouseReleased( MouseEvent arg0 ) {} + public void mousePressed( MouseEvent arg0 ) {} + public void mouseExited( MouseEvent arg0 ) {} + public void mouseEntered( MouseEvent arg0 ) {} + } ); + } + + @Override + public void stateChanged( ChangeEvent event ) + { + int value = ((JSlider)event.getSource()).getValue(); + + mAdjuster.setAveraging( value ); + } +} diff --git a/src/spectrum/menu/BaselineItem.java b/src/spectrum/menu/BaselineItem.java new file mode 100644 index 00000000..bfc7bcb6 --- /dev/null +++ b/src/spectrum/menu/BaselineItem.java @@ -0,0 +1,57 @@ +package spectrum.menu; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.JSlider; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import spectrum.SpectralDisplayAdjuster; + +public class BaselineItem extends JSlider implements ChangeListener +{ + private static final long serialVersionUID = 1L; + + private SpectralDisplayAdjuster mAdjuster; + private int mDefaultValue; + + public BaselineItem( SpectralDisplayAdjuster adjuster, int defaultValue ) + { + super( JSlider.HORIZONTAL, 1, 100, adjuster.getBaseline() ); + mDefaultValue = defaultValue; + + mAdjuster = adjuster; + + setMajorTickSpacing( 10 ); + setMinorTickSpacing( 5 ); + setPaintTicks( true ); + + addChangeListener( this ); + + addMouseListener( new MouseListener() + { + @Override + public void mouseClicked( MouseEvent event ) + { + if( event.getClickCount() == 2 ) + { + BaselineItem.this.setValue( mDefaultValue ); + } + } + + public void mouseReleased( MouseEvent arg0 ) {} + public void mousePressed( MouseEvent arg0 ) {} + public void mouseExited( MouseEvent arg0 ) {} + public void mouseEntered( MouseEvent arg0 ) {} + } ); + } + + @Override + public void stateChanged( ChangeEvent event ) + { + int value = ((JSlider)event.getSource()).getValue(); + + mAdjuster.setBaseline( value ); + } +} diff --git a/src/spectrum/menu/FFTWidthItem.java b/src/spectrum/menu/FFTWidthItem.java new file mode 100644 index 00000000..4185b33d --- /dev/null +++ b/src/spectrum/menu/FFTWidthItem.java @@ -0,0 +1,39 @@ +package spectrum.menu; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBoxMenuItem; + +import spectrum.DFTProcessor; +import spectrum.FFTWidth; + +public class FFTWidthItem extends JCheckBoxMenuItem +{ + private static final long serialVersionUID = 1L; + + private DFTProcessor mDFTProcessor; + private FFTWidth mFFTWidth; + + public FFTWidthItem( DFTProcessor processor, FFTWidth width ) + { + super( width.getLabel() ); + + mDFTProcessor = processor; + mFFTWidth = width; + + if( processor.getFFTWidth() == mFFTWidth ) + { + setSelected( true ); + } + + addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + mDFTProcessor.setFFTSize( mFFTWidth ); + } + } ); + } +} diff --git a/src/spectrum/menu/FFTWindowTypeItem.java b/src/spectrum/menu/FFTWindowTypeItem.java new file mode 100644 index 00000000..6a2aacbf --- /dev/null +++ b/src/spectrum/menu/FFTWindowTypeItem.java @@ -0,0 +1,40 @@ +package spectrum.menu; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBoxMenuItem; + +import spectrum.DFTProcessor; +import dsp.filter.Window.WindowType; + +public class FFTWindowTypeItem extends JCheckBoxMenuItem +{ + private static final long serialVersionUID = 1L; + + private DFTProcessor mDFTProcessor; + private WindowType mWindowType; + + public FFTWindowTypeItem( DFTProcessor processor, WindowType windowType ) + { + super( windowType.toString() ); + + mDFTProcessor = processor; + mWindowType = windowType; + + if( processor.getWindowType() == mWindowType ) + { + setSelected( true ); + } + + addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + mDFTProcessor.setWindowType( mWindowType ); + } + } ); + } +} + diff --git a/src/spectrum/menu/FrameRateItem.java b/src/spectrum/menu/FrameRateItem.java new file mode 100644 index 00000000..3101e6d4 --- /dev/null +++ b/src/spectrum/menu/FrameRateItem.java @@ -0,0 +1,39 @@ +package spectrum.menu; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBoxMenuItem; + +import spectrum.DFTProcessor; + +public class FrameRateItem extends JCheckBoxMenuItem +{ + private static final long serialVersionUID = 1L; + + private DFTProcessor mDFTProcessor; + private int mFrameRate; + + public FrameRateItem( DFTProcessor processor, int frameRate ) + { + super( String.valueOf( frameRate ) ); + + mDFTProcessor = processor; + mFrameRate = frameRate; + + if( processor.getFrameRate() == mFrameRate ) + { + setSelected( true ); + } + + addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + mDFTProcessor.setFrameRate( mFrameRate ); + } + } ); + } +} +