#90 [WIP] Adds ability to show streaming status on main application window using menu (View > Show Streaming Status). Resolves a number of issues with the SystemProperties class.

This commit is contained in:
Denny
2017-01-21 08:13:18 -05:00
parent 2d9c3c0aa4
commit 6b67a74055
10 changed files with 1820 additions and 1701 deletions

View File

@@ -118,12 +118,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
try
{
setMixerChannelConfiguration(configuration);
setMixerChannelConfiguration(configuration, false);
}
catch(Exception e)
{
mLog.error("Couldn't set stored audio mixer/channel configuration"
+ " - using default", e);
mLog.error("Couldn't set stored audio mixer/channel configuration - using default", e);
try
{
@@ -131,8 +130,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
}
catch(Exception e2)
{
mLog.error("Couldn't set default audio mixer/channel "
+ "configuration - no audio will be available", e2);
mLog.error("Couldn't set default audio mixer/channel configuration - no audio will be available", e2);
}
}
}
@@ -217,8 +215,12 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
@Override
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
{
if(entry != null &&
(entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
setMixerChannelConfiguration(entry, true);
}
public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
{
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
{
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_STARTED);
@@ -260,11 +262,12 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
SystemProperties properties = SystemProperties.getInstance();
properties.set(AUDIO_MIXER_PROPERTY, entry.getMixer().getMixerInfo().getName());
properties.set(AUDIO_CHANNELS_PROPERTY, entry.getMixerChannel().name());
properties.save();
if(saveSettings)
{
SystemProperties properties = SystemProperties.getInstance();
properties.set(AUDIO_MIXER_PROPERTY, entry.getMixer().getMixerInfo().getName());
properties.set(AUDIO_CHANNELS_PROPERTY, entry.getMixerChannel().name());
}
}
}

View File

@@ -585,7 +585,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
switch(column)
{
case COLUMN_SERVER_ICON:
return null;
return "Streaming";
case COLUMN_STREAM_NAME:
return "Name";
case COLUMN_BROADCASTER_STATUS:

View File

@@ -50,7 +50,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
private AliasModel mAliasModel;
private IconManager mIconManager;
private JTable mBroadcastConfigurationTable;
private BroadcastStatusPanel mBroadcastStatusPanel;
private JideSplitPane mSplitPane;
private Editor<BroadcastConfiguration> mEmptyEditor = new EmptyEditor<>("Configuration:");
private Editor<BroadcastConfiguration> mEditor;
@@ -72,17 +72,11 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
{
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
mBroadcastConfigurationTable = new JTable(mBroadcastModel);
mBroadcastConfigurationTable.setAutoCreateRowSorter(true);
mBroadcastConfigurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mBroadcastConfigurationTable.getSelectionModel().addListSelectionListener(BroadcastPanel.this);
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel);
mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_SERVER_ICON).setPreferredWidth(1);
mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_STREAM_NAME).setPreferredWidth(25);
mBroadcastConfigurationTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS)
.setCellRenderer(new StatusCellRenderer());
JScrollPane scroller = new JScrollPane(mBroadcastConfigurationTable);
mBroadcastStatusPanel.getTable().setAutoCreateRowSorter(true);
mBroadcastStatusPanel.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mBroadcastStatusPanel.getTable().getSelectionModel().addListSelectionListener(BroadcastPanel.this);
JPanel buttonsPanel = new JPanel();
buttonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
@@ -122,7 +116,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
listAndButtonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
listAndButtonsPanel.add(scroller, "wrap");
listAndButtonsPanel.add(mBroadcastStatusPanel, "wrap");
listAndButtonsPanel.add(buttonsPanel);
mSplitPane = new JideSplitPane(JideSplitPane.HORIZONTAL_SPLIT);
@@ -168,11 +162,11 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
*/
private BroadcastConfiguration getSelectedBroadcastConfiguration()
{
int viewRow = mBroadcastConfigurationTable.getSelectedRow();
int viewRow = mBroadcastStatusPanel.getTable().getSelectedRow();
if(viewRow >= 0)
{
int modelRow = mBroadcastConfigurationTable.convertRowIndexToModel(viewRow);
int modelRow = mBroadcastStatusPanel.getTable().convertRowIndexToModel(viewRow);
if(modelRow >= 0)
{
@@ -255,75 +249,12 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
mBroadcastModel.addBroadcastConfiguration(configuration);
int modelRow = mBroadcastModel.getRowForConfiguration(configuration);
int tableRow = mBroadcastConfigurationTable.convertRowIndexToView(modelRow);
mBroadcastConfigurationTable.changeSelection(tableRow, 0, false, false);
int tableRow = mBroadcastStatusPanel.getTable().convertRowIndexToView(modelRow);
mBroadcastStatusPanel.getTable().changeSelection(tableRow, 0, false, false);
}
}
});
}
}
/**
* Custom cell renderer for the broadcast state column.
*/
public class StatusCellRenderer extends DefaultTableCellRenderer
{
public StatusCellRenderer()
{
setOpaque(true);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column)
{
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if(isSelected)
{
setBackground(table.getSelectionBackground());
setForeground(table.getSelectionForeground());
}
else
{
if(value instanceof BroadcastState)
{
BroadcastState state = (BroadcastState)value;
if(state == BroadcastState.CONNECTED)
{
setBackground(Color.GREEN);
setForeground(table.getForeground());
}
else if(state == BroadcastState.DISABLED)
{
setBackground(table.getBackground());
setForeground(Color.LIGHT_GRAY);
}
else if(state == BroadcastState.INVALID_SETTINGS)
{
setBackground(Color.YELLOW);
setForeground(table.getForeground());
}
else if(state.isErrorState())
{
setBackground(Color.RED);
setForeground(table.getForeground());
}
else
{
setBackground(table.getBackground());
setForeground(table.getForeground());
}
}
else
{
setForeground(table.getForeground());
setBackground(table.getBackground());
}
}
return this;
}
}
}

View File

@@ -0,0 +1,125 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
public class BroadcastStatusPanel extends JPanel
{
private JTable mTable;
private JScrollPane mScrollPane;
private BroadcastModel mBroadcastModel;
public BroadcastStatusPanel(BroadcastModel broadcastModel)
{
mBroadcastModel = broadcastModel;
init();
}
public JTable getTable()
{
return mTable;
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
mTable = new JTable(mBroadcastModel);
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)mTable.getDefaultRenderer(String.class);
renderer.setHorizontalAlignment(SwingConstants.CENTER);
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
mScrollPane = new JScrollPane(mTable);
add(mScrollPane);
}
/**
* Custom cell renderer for the broadcast state column.
*/
public class StatusCellRenderer extends DefaultTableCellRenderer
{
public StatusCellRenderer()
{
setOpaque(true);
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column)
{
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if(isSelected)
{
setBackground(table.getSelectionBackground());
setForeground(table.getSelectionForeground());
}
else
{
if(value instanceof BroadcastState)
{
BroadcastState state = (BroadcastState)value;
if(state == BroadcastState.CONNECTED)
{
setBackground(Color.GREEN);
setForeground(table.getForeground());
}
else if(state == BroadcastState.DISABLED)
{
setBackground(table.getBackground());
setForeground(Color.LIGHT_GRAY);
}
else if(state == BroadcastState.INVALID_SETTINGS)
{
setBackground(Color.YELLOW);
setForeground(table.getForeground());
}
else if(state.isErrorState())
{
setBackground(Color.RED);
setForeground(table.getForeground());
}
else
{
setBackground(table.getBackground());
setForeground(table.getForeground());
}
}
else
{
setForeground(table.getForeground());
setBackground(table.getBackground());
}
}
return this;
}
}
}

View File

@@ -24,6 +24,7 @@ import audio.AudioManager;
import audio.AudioPanel;
import audio.broadcast.BroadcastModel;
import audio.broadcast.BroadcastPanel;
import audio.broadcast.BroadcastStatusPanel;
import channel.metadata.ChannelMetadataViewer;
import com.jidesoft.swing.JideTabbedPane;
import controller.channel.ChannelController;
@@ -34,6 +35,7 @@ import icon.IconManager;
import map.MapPanel;
import map.MapService;
import net.miginfocom.swing.MigLayout;
import properties.SystemProperties;
import settings.SettingsManager;
import source.SourceManager;
import source.tuner.TunerModel;
@@ -54,6 +56,7 @@ public class ControllerPanel extends JPanel
private ChannelModel mChannelModel;
private MapPanel mMapPanel;
private TunerViewPanel mTunerManagerPanel;
private BroadcastModel mBroadcastModel;
private JideTabbedPane mTabbedPane;
protected JTable mChannelActivityTable = new JTable();
@@ -70,6 +73,7 @@ public class ControllerPanel extends JPanel
SourceManager sourceManager,
TunerModel tunerModel)
{
mBroadcastModel = broadcastModel;
mChannelModel = channelModel;
mAudioPanel = new AudioPanel(iconManager, settingsManager, sourceManager, audioManager);
@@ -91,7 +95,7 @@ public class ControllerPanel extends JPanel
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[]0[grow,fill]"));
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[]0[grow,fill]0[]"));
add(mAudioPanel, "wrap");
@@ -109,6 +113,6 @@ public class ControllerPanel extends JPanel
//Set preferred size to influence the split between these panels
mTabbedPane.setPreferredSize(new Dimension(880, 500));
add(mTabbedPane);
add(mTabbedPane,"wrap");
}
}

View File

@@ -1,343 +1,343 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 <http://www.gnu.org/licenses/>
* sdrtrunk
* Copyright (C) 2014-2017 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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
package controller.channel;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelEvent.Event;
import module.decode.DecoderType;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.table.AbstractTableModel;
import module.decode.DecoderType;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelEvent.Event;
/**
* Channel Model
*/
public class ChannelModel extends AbstractTableModel implements IChannelEventBroadcaster
{
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
private List<Channel> mChannels = new CopyOnWriteArrayList<>();
private List<Channel> mTrafficChannels = new CopyOnWriteArrayList<>();
private List<ChannelEventListener> mListeners = new CopyOnWriteArrayList<>();
public static final int COLUMN_ENABLED = 0;
public static final int COLUMN_SYSTEM = 1;
public static final int COLUMN_SITE = 2;
public static final int COLUMN_NAME = 3;
public static final int COLUMN_ALIAS_LIST = 4;
public static final int COLUMN_SOURCE = 5;
public static final int COLUMN_DECODER = 6;
public ChannelModel()
{
}
private static final String[] COLUMN_NAMES = new String[] {"Enabled", "System", "Site", "Name", "Alias List",
"Source", "Decoder"};
/**
* Unmodifiable list of non-traffic channels currently in the model
*/
public List<Channel> getChannels()
{
return Collections.unmodifiableList( mChannels );
}
public Channel getChannelAtIndex( int row )
{
if( mChannels.size() >= row )
{
return mChannels.get( row );
}
return null;
}
/**
* Returns a list of unique system values from across the channel set
*/
public List<String> getSystems()
{
List<String> systems = new ArrayList<>();
for( Channel channel: mChannels )
{
if( channel.hasSystem() && !systems.contains( channel.getSystem() ) )
{
systems.add( channel.getSystem() );
}
}
Collections.sort( systems );
return systems;
}
/**
* Returns a list of unique site values for all channels that have a matching
* system value
*/
public List<String> getSites( String system )
{
List<String> sites = new ArrayList<>();
private List<Channel> mChannels = new CopyOnWriteArrayList<>();
private List<Channel> mTrafficChannels = new CopyOnWriteArrayList<>();
private List<ChannelEventListener> mListeners = new CopyOnWriteArrayList<>();
if( system != null )
{
for( Channel channel: mChannels )
{
if( channel.hasSystem() &&
system.equals( channel.getSystem() ) &&
channel.hasSite() &&
!sites.contains( channel.getSite() ) )
{
sites.add( channel.getSite() );
}
}
}
public ChannelModel()
{
}
Collections.sort( sites );
return sites;
}
/**
* Unmodifiable list of non-traffic channels currently in the model
*/
public List<Channel> getChannels()
{
return Collections.unmodifiableList(mChannels);
}
/**
* Broadcasts the channel event to all registered listeners
*/
@Override
public void broadcast( ChannelEvent event )
{
for( ChannelEventListener listener: mListeners )
{
listener.channelChanged( event );
}
if( event.getChannel().getChannelType() == ChannelType.STANDARD )
{
switch( event.getEvent() )
{
case NOTIFICATION_CONFIGURATION_CHANGE:
case NOTIFICATION_PROCESSING_START:
case NOTIFICATION_PROCESSING_STOP:
case NOTIFICATION_SELECTION_CHANGE:
int index = mChannels.indexOf( event.getChannel() );
fireTableRowsUpdated( index, index );
break;
default:
break;
}
}
if( event.getEvent() == Event.REQUEST_DELETE )
{
removeChannel( event.getChannel() );
}
}
public Channel getChannelAtIndex(int row)
{
if(row < mChannels.size())
{
return mChannels.get(row);
}
/**
* Bulk loading of channel list. Each channel is added and a channel add
* event is broadcast.
*/
public void addChannels( List<Channel> channels )
{
for( Channel channel: channels )
{
addChannel( channel );
}
}
return null;
}
/**
* Adds the channel to the model and broadcasts a channel add event
*/
public int addChannel( Channel channel )
{
int index = -1;
switch( channel.getChannelType() )
{
case STANDARD:
mChannels.add( channel );
index = mChannels.size() - 1;
fireTableRowsInserted( index, index );
break;
case TRAFFIC:
mTrafficChannels.add( channel );
index = mChannels.size() - 1;
break;
default:
break;
}
broadcast( new ChannelEvent( channel, Event.NOTIFICATION_ADD ) );
if( channel.getEnabled() )
{
broadcast( new ChannelEvent( channel, Event.REQUEST_ENABLE ) );
}
return index;
}
/**
* Removes the channel from the model and broadcasts a channel remove event
*/
public void removeChannel( Channel channel )
{
if( channel != null )
{
switch( channel.getChannelType() )
{
case STANDARD:
int index = mChannels.indexOf( channel );
mChannels.remove( channel );
/**
* Returns a list of unique system values from across the channel set
*/
public List<String> getSystems()
{
List<String> systems = new ArrayList<>();
if( index >= 0 )
{
fireTableRowsDeleted( index, index );
}
break;
case TRAFFIC:
mTrafficChannels.remove( channel );
break;
default:
break;
}
broadcast( new ChannelEvent( channel, Event.NOTIFICATION_DELETE ) );
}
}
for(Channel channel : mChannels)
{
if(channel.hasSystem() && !systems.contains(channel.getSystem()))
{
systems.add(channel.getSystem());
}
}
/**
* Returns a list of channels that fall within the frequency range
*
* @param start frequency of the range
* @param stop frequency of the range
*
* @return list of channels or an empty list if none fall within the range
*/
public List<Channel> getChannelsInFrequencyRange( long start, long stop )
{
List<Channel> channels = new ArrayList<>();
for( Channel channel: mChannels )
{
if( channel.isWithin( start, stop ) )
{
channels.add( channel );
}
}
for( Channel channel: mTrafficChannels )
{
if( channel.isWithin( start, stop ) )
{
channels.add( channel );
}
}
return channels;
}
public void addListener( ChannelEventListener listener )
{
mListeners.add( listener );
}
public void removeListener( ChannelEventListener listener )
{
mListeners.remove( listener );
}
Collections.sort(systems);
//Table Model Interface Methods - standard channels only
@Override
public int getRowCount()
{
return mChannels.size();
}
return systems;
}
@Override
public int getColumnCount()
{
return 6;
}
/**
* Returns a list of unique site values for all channels that have a matching
* system value
*/
public List<String> getSites(String system)
{
List<String> sites = new ArrayList<>();
@Override
public String getColumnName( int columnIndex )
{
switch( columnIndex )
{
case 0:
return "System";
case 1:
return "Site";
case 2:
return "Name";
case 3:
return "Alias List";
case 4:
return "Source";
case 5:
return "Decoder";
}
return null;
}
if(system != null)
{
for(Channel channel : mChannels)
{
if(channel.hasSystem() &&
system.equals(channel.getSystem()) &&
channel.hasSite() &&
!sites.contains(channel.getSite()))
{
sites.add(channel.getSite());
}
}
}
@Override
public Class<?> getColumnClass( int columnIndex )
{
return String.class;
}
Collections.sort(sites);
@Override
public boolean isCellEditable( int rowIndex, int columnIndex )
{
return false;
}
return sites;
}
@Override
public Object getValueAt( int rowIndex, int columnIndex )
{
Channel channel = mChannels.get( rowIndex );
switch( columnIndex )
{
case 0:
return channel.getSystem();
case 1:
return channel.getSite();
case 2:
return channel.getName();
case 3:
return channel.getAliasListName();
case 4:
return channel.getSourceConfiguration().getDescription();
case 5:
return channel.getDecodeConfiguration()
.getDecoderType().getShortDisplayString();
}
return null;
}
/**
* Broadcasts the channel event to all registered listeners
*/
@Override
public void broadcast(ChannelEvent event)
{
for(ChannelEventListener listener : mListeners)
{
listener.channelChanged(event);
}
@Override
public void setValueAt( Object aValue, int rowIndex, int columnIndex )
{
throw new IllegalArgumentException( "Not yet implemented" );
}
public void createChannel( DecoderType decoderType, long frequency )
{
throw new UnsupportedOperationException( "Not yet implemented" );
}
if(event.getChannel().getChannelType() == ChannelType.STANDARD)
{
switch(event.getEvent())
{
case NOTIFICATION_CONFIGURATION_CHANGE:
case NOTIFICATION_PROCESSING_START:
case NOTIFICATION_PROCESSING_STOP:
case NOTIFICATION_SELECTION_CHANGE:
int index = mChannels.indexOf(event.getChannel());
fireTableRowsUpdated(index, index);
break;
default:
break;
}
}
if(event.getEvent() == Event.REQUEST_DELETE)
{
removeChannel(event.getChannel());
}
}
/**
* Bulk loading of channel list. Each channel is added and a channel add
* event is broadcast.
*/
public void addChannels(List<Channel> channels)
{
for(Channel channel : channels)
{
addChannel(channel);
}
}
/**
* Adds the channel to the model and broadcasts a channel add event
*/
public int addChannel(Channel channel)
{
int index = -1;
switch(channel.getChannelType())
{
case STANDARD:
mChannels.add(channel);
index = mChannels.size() - 1;
fireTableRowsInserted(index, index);
break;
case TRAFFIC:
mTrafficChannels.add(channel);
index = mChannels.size() - 1;
break;
default:
break;
}
broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD));
if(channel.getEnabled())
{
broadcast(new ChannelEvent(channel, Event.REQUEST_ENABLE));
}
return index;
}
/**
* Removes the channel from the model and broadcasts a channel remove event
*/
public void removeChannel(Channel channel)
{
if(channel != null)
{
switch(channel.getChannelType())
{
case STANDARD:
int index = mChannels.indexOf(channel);
mChannels.remove(channel);
if(index >= 0)
{
fireTableRowsDeleted(index, index);
}
break;
case TRAFFIC:
mTrafficChannels.remove(channel);
break;
default:
break;
}
broadcast(new ChannelEvent(channel, Event.NOTIFICATION_DELETE));
}
}
/**
* Returns a list of channels that fall within the frequency range
*
* @param start frequency of the range
* @param stop frequency of the range
* @return list of channels or an empty list if none fall within the range
*/
public List<Channel> getChannelsInFrequencyRange(long start, long stop)
{
List<Channel> channels = new ArrayList<>();
for(Channel channel : mChannels)
{
if(channel.isWithin(start, stop))
{
channels.add(channel);
}
}
for(Channel channel : mTrafficChannels)
{
if(channel.isWithin(start, stop))
{
channels.add(channel);
}
}
return channels;
}
public void addListener(ChannelEventListener listener)
{
mListeners.add(listener);
}
public void removeListener(ChannelEventListener listener)
{
mListeners.remove(listener);
}
//Table Model Interface Methods - standard channels only
@Override
public int getRowCount()
{
return mChannels.size();
}
@Override
public int getColumnCount()
{
return COLUMN_NAMES.length;
}
@Override
public String getColumnName(int columnIndex)
{
if(columnIndex < COLUMN_NAMES.length)
{
return COLUMN_NAMES[columnIndex];
}
return null;
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
Channel channel = mChannels.get(rowIndex);
switch(columnIndex)
{
case COLUMN_ENABLED:
return channel.getEnabled() ? "Enabled" : null;
case COLUMN_SYSTEM:
return channel.getSystem();
case COLUMN_SITE:
return channel.getSite();
case COLUMN_NAME:
return channel.getName();
case COLUMN_ALIAS_LIST:
return channel.getAliasListName();
case COLUMN_SOURCE:
return channel.getSourceConfiguration().getDescription();
case COLUMN_DECODER:
return channel.getDecodeConfiguration().getDecoderType().getShortDisplayString();
}
return null;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new IllegalArgumentException("Not yet implemented");
}
public void createChannel(DecoderType decoderType, long frequency)
{
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@@ -21,6 +21,7 @@ import alias.AliasModel;
import alias.action.AliasActionManager;
import audio.AudioManager;
import audio.broadcast.BroadcastModel;
import audio.broadcast.BroadcastStatusPanel;
import com.jidesoft.swing.JideSplitPane;
import controller.ControllerPanel;
import controller.ThreadPoolManager;
@@ -65,12 +66,17 @@ import java.util.concurrent.TimeUnit;
public class SDRTrunk implements Listener<TunerEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(SDRTrunk.class);
private static final String PROPERTY_BROADCAST_STATUS_VISIBLE = "main.broadcast.status.visible";
private boolean mBroadcastStatusVisible;
private IconManager mIconManager;
private BroadcastStatusPanel mBroadcastStatusPanel;
private BroadcastModel mBroadcastModel;
private ControllerPanel mControllerPanel;
private SettingsManager mSettingsManager;
private SpectralDisplayPanel mSpectralPanel;
private JFrame mMainGui = new JFrame();
private JideSplitPane mSplitPane;
private String mTitle;
@@ -139,14 +145,14 @@ public class SDRTrunk implements Listener<TunerEvent>
AudioManager audioManager = new AudioManager(threadPoolManager, sourceManager.getMixerManager());
channelProcessingManager.addAudioPacketListener(audioManager);
BroadcastModel broadcastModel = new BroadcastModel(threadPoolManager, mIconManager);
mBroadcastModel = new BroadcastModel(threadPoolManager, mIconManager);
channelProcessingManager.addAudioPacketListener(broadcastModel);
channelProcessingManager.addAudioPacketListener(mBroadcastModel);
MapService mapService = new MapService(mIconManager);
channelProcessingManager.addMessageListener(mapService);
mControllerPanel = new ControllerPanel(audioManager, aliasModel, broadcastModel,
mControllerPanel = new ControllerPanel(audioManager, aliasModel, mBroadcastModel,
channelModel, channelMapModel, channelProcessingManager, mIconManager,
mapService, mSettingsManager, sourceManager, tunerModel);
@@ -160,7 +166,7 @@ public class SDRTrunk implements Listener<TunerEvent>
tunerModel.addListener(this);
PlaylistManager playlistManager = new PlaylistManager(threadPoolManager,
aliasModel, broadcastModel, channelModel, channelMapModel);
aliasModel, mBroadcastModel, channelModel, channelMapModel);
playlistManager.init();
@@ -209,9 +215,7 @@ public class SDRTrunk implements Listener<TunerEvent>
*/
private void initGUI()
{
mMainGui.setLayout(new MigLayout("insets 0 0 0 0 ",
"[grow,fill]",
"[grow,fill]"));
mMainGui.setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
/**
* Setup main JFrame window
@@ -225,12 +229,20 @@ public class SDRTrunk implements Listener<TunerEvent>
mSpectralPanel.setPreferredSize(new Dimension(1280, 300));
mControllerPanel.setPreferredSize(new Dimension(1280, 500));
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
splitPane.setDividerSize(5);
splitPane.add(mSpectralPanel);
splitPane.add(mControllerPanel);
mSplitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
mSplitPane.setDividerSize(5);
mSplitPane.add(mSpectralPanel);
mSplitPane.add(mControllerPanel);
mMainGui.add(splitPane, "cell 0 0,span,grow");
mBroadcastStatusVisible = SystemProperties.getInstance().get(PROPERTY_BROADCAST_STATUS_VISIBLE, false);
//Show broadcast status panel when user requests - disabled by default
if(mBroadcastStatusVisible)
{
mSplitPane.add(getBroadcastStatusPanel());
}
mMainGui.add(mSplitPane, "cell 0 0,span,grow");
/**
* Menu items
@@ -291,6 +303,12 @@ public class SDRTrunk implements Listener<TunerEvent>
fileMenu.add(exitMenu);
JMenu viewMenu = new JMenu("View");
viewMenu.add(new BroadcastStatusVisibleMenuItem(mControllerPanel));
menuBar.add(viewMenu);
JMenuItem screenCaptureItem = new JMenuItem("Screen Capture");
screenCaptureItem.setMnemonic(KeyEvent.VK_C);
@@ -360,6 +378,50 @@ public class SDRTrunk implements Listener<TunerEvent>
menuBar.add(screenCaptureItem);
}
/**
* Lazy constructor for broadcast status panel
*/
private BroadcastStatusPanel getBroadcastStatusPanel()
{
if(mBroadcastStatusPanel == null)
{
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel);
mBroadcastStatusPanel.setPreferredSize(new Dimension(880, 100));
mBroadcastStatusPanel.getTable().setEnabled(false);
}
return mBroadcastStatusPanel;
}
/**
* Toggles visibility of the broadcast channels status panel at the bottom of the controller panel
*/
private void toggleBroadcastStatusPanelVisibility()
{
mBroadcastStatusVisible = !mBroadcastStatusVisible;
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
if(mBroadcastStatusVisible)
{
mSplitPane.add(getBroadcastStatusPanel());
}
else
{
mSplitPane.remove(getBroadcastStatusPanel());
}
mMainGui.revalidate();
}
});
SystemProperties.getInstance().set(PROPERTY_BROADCAST_STATUS_VISIBLE, mBroadcastStatusVisible);
}
/**
* Loads the application properties file from the user's home directory,
* creating the properties file for the first-time, if necessary
@@ -435,4 +497,28 @@ public class SDRTrunk implements Listener<TunerEvent>
mMainGui.setTitle(mTitle + " - " + event.getTuner().getName());
}
}
public class BroadcastStatusVisibleMenuItem extends JCheckBoxMenuItem
{
private ControllerPanel mControllerPanel;
public BroadcastStatusVisibleMenuItem(ControllerPanel controllerPanel)
{
super("Show Streaming Status");
mControllerPanel = controllerPanel;
setSelected(mBroadcastStatusPanel != null);
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
toggleBroadcastStatusPanelVisibility();
setSelected(mBroadcastStatusVisible);
}
});
}
}
}

View File

@@ -1,19 +1,20 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* sdrtrunk
* Copyright (C) 2014-2017 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 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.
* 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 <http://www.gnu.org/licenses/>
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package properties;
@@ -33,24 +34,27 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* SystemProperties - provides an isolated instance of properties for the
* application
* SystemProperties - provides an isolated instance of properties for the application
*/
public class SystemProperties
{
private final static Logger mLog =
LoggerFactory.getLogger(SystemProperties.class);
private final static Logger mLog = LoggerFactory.getLogger(SystemProperties.class);
private static String sDEFAULT_APP_ROOT = "SDRTrunk";
private static String sPROPERTIES_FILENAME = "SDRTrunk.properties";
private static String DEFAULT_APP_ROOT = "SDRTrunk";
private static String PROPERTIES_FILENAME = "SDRTrunk.properties";
private static SystemProperties mInstance;
private static SystemProperties INSTANCE;
private static Properties mProperties;
private Path mPropertiesPath;
private String mApplicationName;
private AtomicBoolean mSavePending = new AtomicBoolean();
private ScheduledExecutorService mScheduledExecutorService;
private SystemProperties()
{
@@ -64,50 +68,27 @@ public class SystemProperties
*/
public static SystemProperties getInstance()
{
if(mInstance == null)
if(INSTANCE == null)
{
mInstance = new SystemProperties();
INSTANCE = new SystemProperties();
}
return mInstance;
return INSTANCE;
}
/**
* Saves any currently changed settings to the application properties file
*/
public void save()
private void save()
{
Path propsPath =
getApplicationRootPath().resolve(sPROPERTIES_FILENAME);
OutputStream out = null;
try
if(mSavePending.compareAndSet(false, true))
{
out = new FileOutputStream(propsPath.toString());
String comments =
"SDRTrunk - SDR Trunking Decoder Application Settings";
mProperties.store(out, comments);
}
catch(Exception e)
{
mLog.error("SystemProperties - exception while saving " +
"application properties", e);
}
finally
{
if(out != null)
if(mScheduledExecutorService == null)
{
try
{
out.close();
}
catch(IOException e)
{
}
mScheduledExecutorService = Executors.newScheduledThreadPool(1);
}
mScheduledExecutorService.schedule(new SavePropertiesTask(), 2, TimeUnit.SECONDS);
}
}
@@ -120,12 +101,12 @@ public class SystemProperties
{
Path retVal = null;
String root = get("root.directory", sDEFAULT_APP_ROOT);
String root = get("root.directory", DEFAULT_APP_ROOT);
if(root.equalsIgnoreCase(sDEFAULT_APP_ROOT))
if(root.equalsIgnoreCase(DEFAULT_APP_ROOT))
{
retVal = Paths.get(
System.getProperty("user.home"), sDEFAULT_APP_ROOT);
System.getProperty("user.home"), DEFAULT_APP_ROOT);
}
else
{
@@ -147,8 +128,7 @@ public class SystemProperties
}
catch(IOException e)
{
mLog.error("SystemProperties - exception while creating " +
"app folder [" + folder + "]", e);
mLog.error("SystemProperties - exception while creating app folder [" + folder + "]", e);
}
}
@@ -169,8 +149,6 @@ public class SystemProperties
/**
* Loads the properties file into this properties set
*
* @param file
*/
public void load(Path propertiesPath)
{
@@ -178,44 +156,20 @@ public class SystemProperties
{
mPropertiesPath = propertiesPath;
InputStream in = null;
try
if(Files.exists(mPropertiesPath))
{
in = new FileInputStream(propertiesPath.toString());
}
catch(FileNotFoundException e)
{
mLog.error("SDRTrunk - exception while opening inputstream on " +
"application properties file", e);
}
if(in != null)
{
try
try(InputStream in = new FileInputStream(propertiesPath.toString()))
{
mProperties.load(in);
}
catch(IOException e)
catch(IOException ioe)
{
mLog.error("SDRTrunk - exception while loading properties " +
"inputstream into SystemProperties", e);
}
finally
{
try
{
in.close();
}
catch(IOException e)
{
}
mLog.error("Error loading system properties file", ioe.getMessage());
}
}
}
mLog.info("SystemProperties - loaded [" +
propertiesPath.toString() + "]");
mLog.info("SystemProperties - loaded [" + propertiesPath.toString() + "]");
}
public String getApplicationName()
@@ -339,6 +293,22 @@ public class SystemProperties
save();
}
/**
* Sets (overrides) the property key with the new boolean value
*/
public void set(String key, boolean value)
{
set(key, String.valueOf(value));
}
/**
* Sets (overrides) the property key with the new integer value
*/
public void set(String key, int value)
{
set(key, String.valueOf(value));
}
/**
* Gets the named color property, storing the default value in properties file if it doesn't exist.
*
@@ -362,4 +332,25 @@ public class SystemProperties
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
public class SavePropertiesTask implements Runnable
{
@Override
public void run()
{
if(mSavePending.compareAndSet(true, false))
{
Path propsPath = getApplicationRootPath().resolve(PROPERTIES_FILENAME);
try(OutputStream out = new FileOutputStream(propsPath.toString()))
{
String comments = "SDRTrunk - SDR Trunking Decoder Application Settings";
mProperties.store(out, comments);
}
catch(IOException ioe)
{
mLog.error("Error saving system properties file [" + propsPath.toString() + "]", ioe);
}
}
}
}
}

View File

@@ -1,22 +1,38 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 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 <http://www.gnu.org/licenses/>
* sdrtrunk
* Copyright (C) 2014-2017 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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
package spectrum;
import controller.NamingThreadFactory;
import dsp.filter.Window;
import dsp.filter.Window.WindowType;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import sample.Buffer;
import sample.Listener;
import sample.SampleType;
import sample.complex.ComplexBuffer;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeProcessor;
import spectrum.converter.DFTResultsConverter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
@@ -27,491 +43,460 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import sample.Buffer;
import sample.Listener;
import sample.SampleType;
import sample.complex.ComplexBuffer;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeProcessor;
import spectrum.converter.DFTResultsConverter;
import controller.NamingThreadFactory;
import dsp.filter.Window;
import dsp.filter.Window.WindowType;
/**
* Processes both complex samples or float samples and dispatches a float array
* of DFT results, using configurable fft size and output dispatch timelines.
* of DFT results, using configurable fft size and output dispatch timelines.
*/
public class DFTProcessor implements Listener<ComplexBuffer>,
IFrequencyChangeProcessor,
IDFTWidthChangeProcessor
public class DFTProcessor implements Listener<ComplexBuffer>, IFrequencyChangeProcessor, IDFTWidthChangeProcessor
{
private final static Logger mLog =
LoggerFactory.getLogger( DFTProcessor.class );
private final static Logger mLog = LoggerFactory.getLogger(DFTProcessor.class);
private CopyOnWriteArrayList<DFTResultsConverter> mListeners =
new CopyOnWriteArrayList<DFTResultsConverter>();
private CopyOnWriteArrayList<DFTResultsConverter> mListeners = new CopyOnWriteArrayList<DFTResultsConverter>();
//Fixed size sample queue. We set the size at 12 based on the highest
//sample rate tuner (10 MHz) producing 153 frames per second and the lowest
//DFT processing frame rate of 12 frames per second. This means that the
//maximum size needed is 153 / 12 = 11 frames per DFT processing iteration
//so we set it at 12.
private BlockingQueue<ComplexBuffer> mQueue = new ArrayBlockingQueue<>( 12 );
//Fixed size sample queue. We set the size at 12 based on the highest
//sample rate tuner (10 MHz) producing 153 frames per second and the lowest
//DFT processing frame rate of 12 frames per second. This means that the
//maximum size needed is 153 / 12 = 11 frames per DFT processing iteration
//so we set it at 12.
private BlockingQueue<ComplexBuffer> mQueue = new ArrayBlockingQueue<>(12);
private ScheduledExecutorService mScheduler = Executors
.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
public static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate";
private DFTSize mDFTSize = DFTSize.FFT04096;
private DFTSize mNewDFTSize = DFTSize.FFT04096;
private double[] mWindow;
/* The Cosine and Hanning windows seem to offer the best spectral display
* with minimal bin leakage/smearing */
private WindowType mWindowType = Window.WindowType.HANNING;
private ScheduledExecutorService mScheduler = Executors
.newScheduledThreadPool(1, new NamingThreadFactory("spectrum dft processor"));
private FloatFFT_1D mFFT = new FloatFFT_1D( mDFTSize.getSize() );
private int mFrameRate;
private int mSampleRate;
private int mFFTFloatsPerFrame;
private float mNewFloatsPerFrame;
private float mNewFloatResidual;
private float[] mPreviousFrame = new float[ 8192 ];
private float[] mCurrentBuffer;
private int mCurrentBufferPointer = 0;
private SampleType mSampleType;
public static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate";
private AtomicBoolean mRunning = new AtomicBoolean();
public DFTProcessor( SampleType sampleType )
{
setSampleType( sampleType );
setFrameRate( 20 );
loadSettings();
}
private void loadSettings()
{
int frameRate = SystemProperties.getInstance().get( FRAME_RATE_PROPERTY, 20 );
setFrameRate( frameRate );
}
public void dispose()
{
stop();
mListeners.clear();
mQueue.clear();
mWindow = null;
mCurrentBuffer = null;
}
public WindowType getWindowType()
{
return mWindowType;
}
public void setWindowType( WindowType windowType )
{
mWindowType = windowType;
if( mSampleType == SampleType.COMPLEX )
{
mWindow = Window.getWindow( mWindowType,
mDFTSize.getSize() * 2 );
}
else
{
mWindow = Window.getWindow( mWindowType,
mDFTSize.getSize() );
}
}
/**
* Sets the processor mode to Float or Complex, depending on the sample
* types that will be delivered for processing
*/
public void setSampleType( SampleType type )
{
mSampleType = type;
setWindowType( mWindowType );
}
public SampleType getSampleType()
{
return mSampleType;
}
private DFTSize mDFTSize = DFTSize.FFT04096;
private DFTSize mNewDFTSize = DFTSize.FFT04096;
/**
* Queues an FFT size change request. The scheduled executor will apply
* the change when it runs.
*/
public void setDFTSize( DFTSize size )
{
mNewDFTSize = size;
}
public DFTSize getDFTSize()
{
return mDFTSize;
}
public int getFrameRate()
{
return mFrameRate;
}
public void setFrameRate( int framesPerSecond )
{
//TODO: make sure frame rate & sample rate sample requirement doesn't
//expect overlap greater than the previous frame length
if( framesPerSecond < 1 || framesPerSecond > 1000 )
{
throw new IllegalArgumentException( "DFTProcessor cannot run "
+ "more than 1000 times per second -- requested setting:"
+ framesPerSecond );
}
private double[] mWindow;
mFrameRate = framesPerSecond;
SystemProperties properties = SystemProperties.getInstance();
properties.set( FRAME_RATE_PROPERTY, String.valueOf( mFrameRate ) );
/* The Cosine and Hanning windows seem to offer the best spectral display
* with minimal bin leakage/smearing */
private WindowType mWindowType = Window.WindowType.HANNING;
calculateConsumptionRate();
private FloatFFT_1D mFFT = new FloatFFT_1D(mDFTSize.getSize());
restart();
}
public void start()
{
/**
private int mFrameRate;
private int mSampleRate;
private int mFFTFloatsPerFrame;
private float mNewFloatsPerFrame;
private float mNewFloatResidual;
private float[] mPreviousFrame = new float[8192];
private float[] mCurrentBuffer;
private int mCurrentBufferPointer = 0;
private SampleType mSampleType;
private AtomicBoolean mRunning = new AtomicBoolean();
public DFTProcessor(SampleType sampleType)
{
setSampleType(sampleType);
mFrameRate = SystemProperties.getInstance().get(FRAME_RATE_PROPERTY, 20);
calculateConsumptionRate();
start();
}
public void dispose()
{
stop();
mListeners.clear();
mQueue.clear();
mWindow = null;
mCurrentBuffer = null;
}
public WindowType getWindowType()
{
return mWindowType;
}
public void setWindowType(WindowType windowType)
{
mWindowType = windowType;
if(mSampleType == SampleType.COMPLEX)
{
mWindow = Window.getWindow(mWindowType, mDFTSize.getSize() * 2);
}
else
{
mWindow = Window.getWindow(mWindowType, mDFTSize.getSize());
}
}
/**
* Sets the processor mode to Float or Complex, depending on the sample
* types that will be delivered for processing
*/
public void setSampleType(SampleType type)
{
mSampleType = type;
setWindowType(mWindowType);
}
public SampleType getSampleType()
{
return mSampleType;
}
/**
* Queues an FFT size change request. The scheduled executor will apply
* the change when it runs.
*/
public void setDFTSize(DFTSize size)
{
mNewDFTSize = size;
}
public DFTSize getDFTSize()
{
return mDFTSize;
}
public int getFrameRate()
{
return mFrameRate;
}
public void setFrameRate(int framesPerSecond)
{
//TODO: make sure frame rate & sample rate sample requirement doesn't
//expect overlap greater than the previous frame length
if(framesPerSecond < 1 || framesPerSecond > 1000)
{
throw new IllegalArgumentException("DFTProcessor cannot run "
+ "more than 1000 times per second -- requested setting:"
+ framesPerSecond);
}
mFrameRate = framesPerSecond;
SystemProperties.getInstance().set(FRAME_RATE_PROPERTY, mFrameRate);
calculateConsumptionRate();
restart();
}
public void start()
{
/**
* Reset the scheduler
*/
mScheduler = Executors.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
mScheduler = Executors.newScheduledThreadPool(1, new NamingThreadFactory("spectrum dft"));
/**
* Schedule the DFT to run calculations at a fixed rate
*/
int initialDelay = 0;
int period = (int)( 1000 / mFrameRate );
TimeUnit unit = 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
*/
mScheduler.scheduleAtFixedRate(new DFTCalculationTask(),
initialDelay, period, unit);
}
public void stop()
{
/**
* Shutdown the scheduler and clear out any remaining tasks
*/
try
{
mScheduler.shutdown();
mScheduler.shutdown();
mScheduler.awaitTermination( 100, TimeUnit.MILLISECONDS );
mScheduler.awaitTermination(100, TimeUnit.MILLISECONDS);
}
catch ( InterruptedException e )
catch(InterruptedException e)
{
/* Do nothing ... we're shutting down */
/* Do nothing ... we're shutting down */
// mLog.error( "DFTProcessor - exception while awaiting shutdown of "
// + "calculation scheduler for reset", e );
}
}
public void restart()
{
stop();
start();
}
public int getCalculationsPerSecond()
{
return mFrameRate;
}
/**
* Places the sample into a transfer queue for future processing.
*/
@Override
public void receive( ComplexBuffer sampleBuffer )
{
mQueue.offer( sampleBuffer );
}
private void getNextBuffer()
{
mCurrentBuffer = null;
try
public void restart()
{
stop();
start();
}
public int getCalculationsPerSecond()
{
return mFrameRate;
}
/**
* Places the sample into a transfer queue for future processing.
*/
@Override
public void receive(ComplexBuffer sampleBuffer)
{
mQueue.offer(sampleBuffer);
}
private void getNextBuffer()
{
mCurrentBuffer = null;
try
{
Buffer buffer = mQueue.take();
Buffer buffer = mQueue.take();
mCurrentBuffer = buffer.getSamples();
}
catch ( InterruptedException e )
catch(InterruptedException e)
{
mCurrentBuffer = null;
mCurrentBuffer = null;
}
mCurrentBufferPointer = 0;
}
mCurrentBufferPointer = 0;
}
private float[] getSamples()
{
int remaining = (int)mFFTFloatsPerFrame;
private float[] getSamples()
{
int remaining = (int) mFFTFloatsPerFrame;
float[] currentFrame = new float[ remaining ];
float[] currentFrame = new float[remaining];
int currentFramePointer = 0;
int currentFramePointer = 0;
float integralFloatsToConsume = mNewFloatsPerFrame + mNewFloatResidual;
int newFloatsToConsumeThisFrame = (int)integralFloatsToConsume;
mNewFloatResidual = integralFloatsToConsume - newFloatsToConsumeThisFrame;
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 previousFloatsRequired = remaining - newFloatsToConsumeThisFrame;
System.arraycopy( mPreviousFrame,
mPreviousFrame.length - previousFloatsRequired,
currentFrame,
currentFramePointer,
previousFloatsRequired );
remaining -= previousFloatsRequired;
currentFramePointer += previousFloatsRequired;
}
if(newFloatsToConsumeThisFrame < remaining)
{
int previousFloatsRequired = remaining - newFloatsToConsumeThisFrame;
System.arraycopy(mPreviousFrame,
mPreviousFrame.length - previousFloatsRequired,
currentFrame,
currentFramePointer,
previousFloatsRequired);
remaining -= previousFloatsRequired;
currentFramePointer += previousFloatsRequired;
}
/* Fill the rest of the buffer with new samples */
while( mRunning.get() && remaining > 0 )
{
if( mCurrentBuffer == null ||
mCurrentBufferPointer >= mCurrentBuffer.length )
{
getNextBuffer();
}
while(mRunning.get() && remaining > 0)
{
if(mCurrentBuffer == null ||
mCurrentBufferPointer >= mCurrentBuffer.length)
{
getNextBuffer();
}
/* If we don't have new samples to use, send the current frame with
* the remaining values as zero */
if( mCurrentBuffer == null )
{
remaining = 0;
}
else
{
int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer;
if(mCurrentBuffer == null)
{
remaining = 0;
}
else
{
int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer;
while( remaining > 0 && samplesAvailable > 0 )
{
currentFrame[ currentFramePointer++ ] =
(float)mCurrentBuffer[ mCurrentBufferPointer++ ];
samplesAvailable--;
remaining--;
newFloatsToConsumeThisFrame--;
}
}
}
while(remaining > 0 && samplesAvailable > 0)
{
currentFrame[currentFramePointer++] =
(float) mCurrentBuffer[mCurrentBufferPointer++];
samplesAvailable--;
remaining--;
newFloatsToConsumeThisFrame--;
}
}
}
/* 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 = Arrays.copyOf( currentFrame, currentFrame.length );
}
if(newFloatsToConsumeThisFrame > 0)
{
purge(newFloatsToConsumeThisFrame);
}
else
{
mPreviousFrame = Arrays.copyOf(currentFrame, currentFrame.length);
}
return currentFrame;
}
private void calculate()
{
float[] samples = getSamples();
Window.apply( mWindow, samples );
if( mSampleType == SampleType.REAL )
{
mFFT.realForward( samples );
}
else
{
mFFT.complexForward( samples );
}
dispatch( samples );
}
private void purge( int samplesToPurge )
{
if( samplesToPurge <= 0 )
{
throw new IllegalArgumentException( "DFTProcessor - cannot purge "
+ "negative sample amount" );
}
while( mRunning.get() && samplesToPurge > 0 )
{
if( mCurrentBuffer == null ||
mCurrentBufferPointer >= mCurrentBuffer.length )
{
getNextBuffer();
}
if( mCurrentBuffer != null )
{
int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer;
if( samplesAvailable >= samplesToPurge )
{
mCurrentBufferPointer += samplesToPurge;
samplesToPurge = 0;
}
else
{
samplesToPurge -= samplesAvailable;
mCurrentBufferPointer = mCurrentBuffer.length;
}
}
else
{
samplesToPurge = 0;
}
}
}
/**
* Takes a calculated DFT results set, reformats the data, and sends it
* out to all registered listeners.
*/
private void dispatch( float[] results )
{
Iterator<DFTResultsConverter> it = mListeners.iterator();
while( it.hasNext() )
{
it.next().receive( results );
}
}
public void addConverter( DFTResultsConverter listener )
{
mListeners.add( listener );
return currentFrame;
}
public void removeConverter( DFTResultsConverter listener )
private void calculate()
{
mListeners.remove( listener );
float[] samples = getSamples();
Window.apply(mWindow, samples);
if(mSampleType == SampleType.REAL)
{
mFFT.realForward(samples);
}
else
{
mFFT.complexForward(samples);
}
dispatch(samples);
}
private class DFTCalculationTask implements Runnable
{
@Override
private void purge(int samplesToPurge)
{
if(samplesToPurge <= 0)
{
throw new IllegalArgumentException("DFTProcessor - cannot purge "
+ "negative sample amount");
}
while(mRunning.get() && samplesToPurge > 0)
{
if(mCurrentBuffer == null ||
mCurrentBufferPointer >= mCurrentBuffer.length)
{
getNextBuffer();
}
if(mCurrentBuffer != null)
{
int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer;
if(samplesAvailable >= samplesToPurge)
{
mCurrentBufferPointer += samplesToPurge;
samplesToPurge = 0;
}
else
{
samplesToPurge -= samplesAvailable;
mCurrentBufferPointer = mCurrentBuffer.length;
}
}
else
{
samplesToPurge = 0;
}
}
}
/**
* Takes a calculated DFT results set, reformats the data, and sends it
* out to all registered listeners.
*/
private void dispatch(float[] results)
{
Iterator<DFTResultsConverter> it = mListeners.iterator();
while(it.hasNext())
{
it.next().receive(results);
}
}
public void addConverter(DFTResultsConverter listener)
{
mListeners.add(listener);
}
public void removeConverter(DFTResultsConverter listener)
{
mListeners.remove(listener);
}
private class DFTCalculationTask implements Runnable
{
@Override
public void run()
{
try
{
try
{
/* Only run if we're not currently running */
if( mRunning.compareAndSet( false, true ) )
{
checkFFTSize();
if(mRunning.compareAndSet(false, true))
{
checkFFTSize();
calculate();
mRunning.set( false );
}
}
catch( Exception e )
{
mLog.error( "error during dft processor calculation task", e );
}
calculate();
mRunning.set(false);
}
}
catch(Exception e)
{
mLog.error("error during dft processor calculation task", e);
}
}
}
/**
* Checks for a queued FFT width change request and applies it. This
* method will only be accessed by the scheduled executor that gains
* access to run a calculate method, thus providing thread safety.
*/
private void checkFFTSize()
{
if( mNewDFTSize.getSize() != mDFTSize.getSize() )
{
mDFTSize = mNewDFTSize;
calculateConsumptionRate();
setWindowType( mWindowType );
if( mSampleType == SampleType.COMPLEX )
{
mPreviousFrame = new float[ mDFTSize.getSize() * 2 ];
}
else
{
mPreviousFrame = new float[ mDFTSize.getSize() ];
}
mFFT = new FloatFFT_1D( mDFTSize.getSize() );
}
}
public void clearBuffer()
{
mQueue.clear();
}
@Override
public void frequencyChanged( FrequencyChangeEvent event )
{
switch( event.getEvent() )
{
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mSampleRate = event.getValue().intValue();
calculateConsumptionRate();
break;
default:
break;
}
}
/**
*
*/
private void calculateConsumptionRate()
{
mNewFloatResidual = 0.0f;
mNewFloatsPerFrame = ( (float)mSampleRate / (float)mFrameRate ) *
( mSampleType == SampleType.COMPLEX ? 2.0f : 1.0f );
mFFTFloatsPerFrame = ( mSampleType == SampleType.COMPLEX ?
mDFTSize.getSize() * 2 :
mDFTSize.getSize() );
}
/**
* Checks for a queued FFT width change request and applies it. This
* method will only be accessed by the scheduled executor that gains
* access to run a calculate method, thus providing thread safety.
*/
private void checkFFTSize()
{
if(mNewDFTSize.getSize() != mDFTSize.getSize())
{
mDFTSize = mNewDFTSize;
calculateConsumptionRate();
setWindowType(mWindowType);
if(mSampleType == SampleType.COMPLEX)
{
mPreviousFrame = new float[mDFTSize.getSize() * 2];
}
else
{
mPreviousFrame = new float[mDFTSize.getSize()];
}
mFFT = new FloatFFT_1D(mDFTSize.getSize());
}
}
public void clearBuffer()
{
mQueue.clear();
}
@Override
public void frequencyChanged(FrequencyChangeEvent event)
{
switch(event.getEvent())
{
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mSampleRate = event.getValue().intValue();
calculateConsumptionRate();
break;
default:
break;
}
}
/**
*
*/
private void calculateConsumptionRate()
{
mNewFloatResidual = 0.0f;
mNewFloatsPerFrame = ((float) mSampleRate / (float) mFrameRate) *
(mSampleType == SampleType.COMPLEX ? 2.0f : 1.0f);
mFFTFloatsPerFrame = (mSampleType == SampleType.COMPLEX ?
mDFTSize.getSize() * 2 :
mDFTSize.getSize());
}
}

File diff suppressed because it is too large Load Diff