mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#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:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
125
src/audio/broadcast/BroadcastStatusPanel.java
Normal file
125
src/audio/broadcast/BroadcastStatusPanel.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user