diff --git a/build/build.xml b/build/build.xml
index f44429b9..86210e2b 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -7,7 +7,7 @@
-
+
diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java
index 2181d093..8d659173 100644
--- a/src/audio/AudioChannelPanel.java
+++ b/src/audio/AudioChannelPanel.java
@@ -90,7 +90,8 @@ public class AudioChannelPanel extends JPanel
private void init()
{
- setLayout(new MigLayout("align center center, insets 0 0 0 0", "[][][align right]0[grow,fill]", ""));
+ setLayout(new MigLayout("align center center, insets 0 0 0 0",
+ "[][][align right]0[grow,fill]", ""));
setBackground(mBackgroundColor);
mMutedLabel.setFont(mFont);
@@ -109,7 +110,7 @@ public class AudioChannelPanel extends JPanel
mTo.setFont(mFont);
mTo.setForeground(mValueColor);
- add(mTo);
+ add(mTo,"wmin 10lp");
}
@Override
@@ -159,30 +160,40 @@ public class AudioChannelPanel extends JPanel
* does not occur on the Swing event thread -- wrap any calls to this
* method with an event thread call.
*/
- private void updateLabel(JLabel valueLabel, String value, JLabel aliasLabel, Alias alias)
+ private void updateLabel(JLabel textLabel, String value, JLabel iconLabel, Alias alias)
{
if(value != null)
{
if(alias != null)
{
- valueLabel.setText(alias.getName());
- aliasLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18));
+ iconLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18));
+
+ String aliasName = alias.getName();
+
+ //Truncate label if length exceeds 33 characters - hack!
+ if(aliasName.length() > 33)
+ {
+ textLabel.setText(aliasName.substring(0,30) + "...");
+ }
+ else
+ {
+ textLabel.setText(aliasName);
+ }
}
else
{
- valueLabel.setText(value);
- aliasLabel.setIcon(null);
+ iconLabel.setIcon(null);
+ textLabel.setText(value);
}
}
else
{
- valueLabel.setText("-----");
- aliasLabel.setIcon(null);
+ iconLabel.setIcon(null);
+ textLabel.setText("-----");
}
}
-
/**
* Processes audio metadata to update this panel's display values
*/
diff --git a/src/audio/AudioChannelsPanel.java b/src/audio/AudioChannelsPanel.java
index b2d94fb6..ebfc1b9b 100644
--- a/src/audio/AudioChannelsPanel.java
+++ b/src/audio/AudioChannelsPanel.java
@@ -33,8 +33,7 @@ public class AudioChannelsPanel extends JPanel
public AudioChannelsPanel(IconManager iconManager, SettingsManager settingsManager, IAudioController controller)
{
-// setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill][][grow,fill]", "[grow,fill]"));
- setLayout(new MigLayout("insets 0 0 0 0", "[][sg,grow,fill][][sg,grow,fill]", "[grow,fill]"));
+ setLayout(new MigLayout("insets 0 0 0 0", "[][sg abc,grow,fill][][sg abc,grow,fill]", "[grow,fill]"));
setBackground(Color.BLACK);
@@ -44,7 +43,7 @@ public class AudioChannelsPanel extends JPanel
for(int x = 0; x < outputs.size(); x++)
{
- add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x)), "growx");
+ add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x)));
if(x < outputs.size() - 1)
{
diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java
index 2cab2400..4d6c57ac 100644
--- a/src/audio/AudioManager.java
+++ b/src/audio/AudioManager.java
@@ -86,6 +86,9 @@ public class AudioManager implements Listener, IAudioController
loadSettings();
}
+ /**
+ * Loads the saved mixer configuration or a default configuration for audio playback.
+ */
private void loadSettings()
{
MixerChannelConfiguration configuration = null;
@@ -132,6 +135,9 @@ public class AudioManager implements Listener, IAudioController
}
}
+ /**
+ * Creates a default audio playback configuration with a mono audio playback channel.
+ */
private MixerChannelConfiguration getDefaultConfiguration()
{
/* Use the system default mixer and mono channel as default startup */
@@ -161,6 +167,9 @@ public class AudioManager implements Listener, IAudioController
mAudioOutputConnections.clear();
}
+ /**
+ * Primary ingest point for audio produced by all decoding channels, for distribution to audio playback devices.
+ */
@Override
public synchronized void receive(AudioPacket packet)
{
@@ -176,11 +185,11 @@ public class AudioManager implements Listener, IAudioController
for(AudioOutputConnection connection : mAudioOutputConnections)
{
- if(connection.isInactive() &&
- mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
+ if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
{
mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.disconnect();
+ mAvailableConnectionCount++;
changed = true;
}
}
@@ -192,7 +201,7 @@ public class AudioManager implements Listener, IAudioController
}
/**
- * Identifies the lowest priority channel connection
+ * Identifies the lowest priority channel connection where the a higher value indicates a lower priority.
*/
private void updateLowestPriorityAssignment()
{
@@ -201,19 +210,32 @@ public class AudioManager implements Listener, IAudioController
for(AudioOutputConnection connection : mAudioOutputConnections)
{
if(connection.isConnected() &&
- (mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() > connection.getPriority()))
+ (mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() < connection.getPriority()))
{
mLowestPriorityConnection = connection;
}
}
}
+ /**
+ * Configures audio playback to use the configuration specified in the entry argument.
+ *
+ * @param entry to use in configuring the audio playback setup.
+ * @throws AudioException if there is an error
+ */
@Override
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
{
setMixerChannelConfiguration(entry, true);
}
+ /**
+ * Configures audio playback to use the configuration specified in the entry argument.
+ *
+ * @param entry to use in configuring the audio playback setup.
+ * @param saveSettings to save the audio playback configuration settings in the properties file.
+ * @throws AudioException if there is an error
+ */
public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
{
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
@@ -266,9 +288,8 @@ public class AudioManager implements Listener, IAudioController
}
/**
- * Clears all channel assignments and terminates all audio outputs in
- * preparation for complete shutdown or change to another mixer/channel
- * configuration
+ * Clears all channel assignments and terminates all audio outputs in preparation for complete shutdown or change
+ * to another mixer/channel configuration
*/
private void disposeCurrentConfiguration()
{
@@ -288,12 +309,18 @@ public class AudioManager implements Listener, IAudioController
mLowestPriorityConnection = null;
}
+ /**
+ * Current audio playback mixer channel configuration setting.
+ */
@Override
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
{
return mMixerChannelConfiguration;
}
+ /**
+ * List of audio outputs available for the current mixer channel configuration
+ */
@Override
public List getAudioOutputs()
{
@@ -311,12 +338,18 @@ public class AudioManager implements Listener, IAudioController
return outputs;
}
+ /**
+ * Adds an audio event listener to receive audio event notifications.
+ */
@Override
public void addControllerListener(Listener listener)
{
mControllerBroadcaster.addListener(listener);
}
+ /**
+ * Removes an audio event listener from receiving audio event notifications.
+ */
@Override
public void removeControllerListener(Listener listener)
{
@@ -324,16 +357,15 @@ public class AudioManager implements Listener, IAudioController
}
/**
- * Returns an audio output connection for the packet if one is
- * available, or overrides an existing lower priority connection.
- * Returns null if no connection is available for the audio packet.
+ * Returns an audio output connection for the packet if one is available, or overrides an existing lower priority
+ * connection. Returns null if no connection is available for the audio packet.
*
- * @param packet from a source
+ * @param audioPacket from a decoding channel source
* @return an audio output connection or null
*/
- private AudioOutputConnection getConnection(AudioPacket packet)
+ private AudioOutputConnection getConnection(AudioPacket audioPacket)
{
- int channelMetadataID = packet.getMetadata().getMetadataID();
+ int channelMetadataID = audioPacket.getMetadata().getMetadataID();
//Use an existing connection
if(mChannelConnectionMap.containsKey(channelMetadataID))
@@ -341,37 +373,35 @@ public class AudioManager implements Listener, IAudioController
return mChannelConnectionMap.get(channelMetadataID);
}
- //Connect to and use an available connection
+ //Connect to an unused, available connection
if(mAvailableConnectionCount > 0)
{
for(AudioOutputConnection connection : mAudioOutputConnections)
{
if(connection.isDisconnected())
{
- connection.connect(channelMetadataID);
+ connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority());
mChannelConnectionMap.put(channelMetadataID, connection);
+ mAvailableConnectionCount--;
return connection;
}
}
}
- //Preempt an existing connection and connect it to this higher priority source
- else //Check for a channel priority override
+ //Preempt an existing lower priority connection and connect when this is a higher priority packet
+ else
{
- int priority = packet.getMetadata().getAudioPriority();
+ int priority = audioPacket.getMetadata().getAudioPriority();
- if(mLowestPriorityConnection != null &&
- priority < mLowestPriorityConnection.getPriority())
+ AudioOutputConnection connection = mLowestPriorityConnection;
+
+ if(connection != null && priority < connection.getPriority())
{
- AudioOutputConnection connection = mLowestPriorityConnection;
-
mChannelConnectionMap.remove(connection.getChannelMetadataID());
- connection.connect(channelMetadataID);
+ connection.connect(channelMetadataID, priority);
mChannelConnectionMap.put(channelMetadataID, connection);
- updateLowestPriorityAssignment();
-
return connection;
}
}
@@ -396,7 +426,7 @@ public class AudioManager implements Listener, IAudioController
for(AudioPacket packet : packets)
{
- /* Don't process any packet's marked as do not monitor */
+ /* Don't process any packet's marked as do not monitor */
if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO)
{
AudioOutputConnection connection = getConnection(packet);
@@ -494,9 +524,11 @@ public class AudioManager implements Listener, IAudioController
* Connects this assignment to the indicated source so that audio
* packets from this source can be sent to the audio output
*/
- public void connect(int source)
+ public void connect(int source, int priority)
{
mChannelMetadataID = source;
+ mPriority = priority;
+ updateLowestPriorityAssignment();
mAudioOutput.updateTimestamp();
}
diff --git a/src/channel/metadata/ChannelMetadataModel.java b/src/channel/metadata/ChannelMetadataModel.java
index b5648eb3..4d6844af 100644
--- a/src/channel/metadata/ChannelMetadataModel.java
+++ b/src/channel/metadata/ChannelMetadataModel.java
@@ -32,7 +32,7 @@ import java.util.Map;
public class ChannelMetadataModel extends AbstractTableModel implements Listener
{
- private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.0000" );
+ private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" );
public static final int COLUMN_STATE = 0;
public static final int COLUMN_DECODER = 1;
diff --git a/src/channel/state/ChannelState.java b/src/channel/state/ChannelState.java
index 768c1062..d9407c0f 100644
--- a/src/channel/state/ChannelState.java
+++ b/src/channel/state/ChannelState.java
@@ -36,14 +36,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.IOverflowListener;
+import source.tuner.frequency.FrequencyChangeEvent;
+import source.tuner.frequency.IFrequencyChangeListener;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
+
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
- IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener
+ IDecoderStateEventProvider, IFrequencyChangeListener, ISquelchStateProvider, IAttributeChangeRequestListener,
+ IOverflowListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
@@ -61,6 +66,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent;
+ private FrequencyChangeListener mFrequencyChangeListener;
private boolean mSquelchLocked = false;
private boolean mSelected = false;
@@ -189,6 +195,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mStateMonitor = null;
}
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ if(mFrequencyChangeListener == null)
+ {
+ mFrequencyChangeListener = new FrequencyChangeListener();
+ }
+
+ return mFrequencyChangeListener;
+ }
+
private boolean isStandardChannel()
{
return mChannelType == ChannelType.STANDARD;
@@ -532,6 +549,23 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
broadcast(mTrafficChannelCallEvent);
}
+ /**
+ * Listener to receive frequency change events and rebroadcast them to the decoder states
+ */
+ public class FrequencyChangeListener implements Listener
+ {
+ @Override
+ public void receive(FrequencyChangeEvent frequencyChangeEvent)
+ {
+ if(frequencyChangeEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
+ {
+ long frequency = frequencyChangeEvent.getValue().longValue();
+
+ broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
+ }
+ }
+ }
+
/**
* DecoderStateEvent receiver wrapper
*/
diff --git a/src/channel/traffic/TrafficChannelManager.java b/src/channel/traffic/TrafficChannelManager.java
index 16cdec10..c2568de8 100644
--- a/src/channel/traffic/TrafficChannelManager.java
+++ b/src/channel/traffic/TrafficChannelManager.java
@@ -63,13 +63,11 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
private Listener mCallEventListener;
private ChannelModel mChannelModel;
- private ChannelProcessingManager mChannelProcessingManager;
private DecodeConfiguration mDecodeConfiguration;
private RecordConfiguration mRecordConfiguration;
private String mSystem;
private String mSite;
private String mAliasListName;
- private CallEvent mPreviousDoNotMonitorCallEvent;
/**
* Monitors call events and allocates traffic decoder channels in response
@@ -77,7 +75,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
* traffic channel allocations.
*
* @param channelModel containing channels currently in use
- * @param channelProcessingManager
* @param decodeConfiguration - decoder configuration to use for each
* traffic channel allocation.
* @param recordConfiguration - recording options for each traffic channel
@@ -88,7 +85,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
* in the pool
*/
public TrafficChannelManager(ChannelModel channelModel,
- ChannelProcessingManager channelProcessingManager,
DecodeConfiguration decodeConfiguration,
RecordConfiguration recordConfiguration,
String system,
@@ -97,7 +93,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
int trafficChannelPoolSize)
{
mChannelModel = channelModel;
- mChannelProcessingManager = channelProcessingManager;
mDecodeConfiguration = decodeConfiguration;
mRecordConfiguration = recordConfiguration;
mSystem = system;
@@ -120,7 +115,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
mCallEventListener = null;
mDecodeConfiguration = null;
- mPreviousDoNotMonitorCallEvent = null;
}
/**
@@ -197,21 +191,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
long frequency = callEvent.getFrequency();
- /* Check the from/to aliases for do not monitor priority */
- if(isDoNotMonitor(callEvent))
- {
- callEvent.setCallEventType(CallEventType.CALL_DO_NOT_MONITOR);
-
- if(isSameCallEvent(mPreviousDoNotMonitorCallEvent, callEvent))
- {
- return;
- }
- else
- {
- mPreviousDoNotMonitorCallEvent = callEvent;
- }
- }
- else if(frequency > 0)
+ if(frequency > 0)
{
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(Type.TRAFFIC,
frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
@@ -330,31 +310,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
return false;
}
- /**
- * Checks for aliases that contain a do not follow/process priority.
- *
- * @param event
- * @return - true if there is an alias that has a do not process alias id
- */
- private boolean isDoNotMonitor(CallEvent event)
- {
- Alias to = event.getToIDAlias();
-
- if(to != null && to.hasCallPriority() && to.getCallPriority() == Priority.DO_NOT_MONITOR)
- {
- return true;
- }
-
- Alias from = event.getFromIDAlias();
-
- if(from != null && from.hasCallPriority() && from.getCallPriority() == Priority.DO_NOT_MONITOR)
- {
- return true;
- }
-
- return false;
- }
-
@Override
public Listener getDecoderStateListener()
{
diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java
index 81ced518..79be174b 100644
--- a/src/controller/channel/Channel.java
+++ b/src/controller/channel/Channel.java
@@ -1,28 +1,24 @@
/*******************************************************************************
- * 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
+ * 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
+ *
******************************************************************************/
package controller.channel;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlSeeAlso;
-import javax.xml.bind.annotation.XmlTransient;
-
+import controller.config.Configuration;
import module.decode.DecoderFactory;
import module.decode.DecoderType;
import module.decode.config.AuxDecodeConfiguration;
@@ -31,6 +27,7 @@ import module.log.EventLogType;
import module.log.config.EventLogConfiguration;
import record.RecorderType;
import record.config.RecordConfiguration;
+import sample.Listener;
import source.SourceType;
import source.config.SourceConfigFactory;
import source.config.SourceConfigRecording;
@@ -40,424 +37,444 @@ import source.tuner.TunerChannel;
import source.tuner.TunerChannel.Type;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event;
-import source.tuner.frequency.IFrequencyChangeProcessor;
-import controller.config.Configuration;
-@XmlSeeAlso( { Configuration.class } )
-@XmlRootElement( name = "channel" )
-public class Channel extends Configuration implements IFrequencyChangeProcessor
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlTransient;
+
+@XmlSeeAlso({Configuration.class})
+@XmlRootElement(name = "channel")
+public class Channel extends Configuration implements Listener
{
- // Standard channels are persisted and traffic channels are temporary
- public enum ChannelType { STANDARD, TRAFFIC };
-
+ // Standard channels are persisted and traffic channels are temporary
+ public enum ChannelType
+ {
+ STANDARD, TRAFFIC
+ }
- // Static unique channel identifier tracking
- private static int UNIQUE_ID = 0;
+ ;
- private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
- private AuxDecodeConfiguration mAuxDecodeConfiguration =
- new AuxDecodeConfiguration();
- private SourceConfiguration mSourceConfiguration =
- SourceConfigFactory.getDefaultSourceConfiguration();
- private EventLogConfiguration mEventLogConfiguration =
- new EventLogConfiguration();
- private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
- private String mAliasListName;
- private String mSystem = "System";
+ // Static unique channel identifier tracking
+ private static int UNIQUE_ID = 0;
+
+ private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
+ private AuxDecodeConfiguration mAuxDecodeConfiguration =
+ new AuxDecodeConfiguration();
+ private SourceConfiguration mSourceConfiguration =
+ SourceConfigFactory.getDefaultSourceConfiguration();
+ private EventLogConfiguration mEventLogConfiguration =
+ new EventLogConfiguration();
+ private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
+
+ private String mAliasListName;
+ private String mSystem = "System";
private String mSite = "Site";
private String mName = "Channel";
-
- private boolean mEnabled;
- private boolean mSelected;
- private TunerChannel mTunerChannel = null;
-
- private ChannelType mChannelType = ChannelType.STANDARD;
- private int mChannelID;
- private int mChannelFrequencyCorrection = 0;
-
- /**
- * Channel represents a complete set of configurations needed to setup and
- * manage a processing chain and/or manage as a set of business objects that
- * can be displayed in graphical components.
- *
- * @param channelName
- * @param channelType
- */
- public Channel( String channelName, ChannelType channelType )
- {
- this( channelName );
- mChannelType = channelType;
- }
+ private boolean mEnabled;
+ private boolean mSelected;
+ private TunerChannel mTunerChannel = null;
- /**
- * Constructs a new standard channel with the specified channel name
- */
- public Channel( String channelName )
- {
- this();
- mName = channelName;
- }
+ private ChannelType mChannelType = ChannelType.STANDARD;
- /**
- * Constructs a new standard channel with a default name of "Channel"
- */
- public Channel()
- {
- mChannelID = UNIQUE_ID++;
- }
+ private int mChannelID;
+ private int mChannelFrequencyCorrection = 0;
- /**
- * Creates a (new) deep copy of this channel
- */
- public Channel copyOf()
- {
- Channel channel = new Channel( mName );
- channel.setSystem( mSystem );
- channel.setSite( mSite );
- channel.setAliasListName( mAliasListName );
+ /**
+ * Channel represents a complete set of configurations needed to setup and
+ * manage a processing chain and/or manage as a set of business objects that
+ * can be displayed in graphical components.
+ *
+ * @param channelName
+ * @param channelType
+ */
+ public Channel(String channelName, ChannelType channelType)
+ {
+ this(channelName);
+ mChannelType = channelType;
+ }
- AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
-
- for( DecoderType auxType: aux.getAuxDecoders() )
- {
- aux.addAuxDecoder( auxType );
- }
-
- channel.setAuxDecodeConfiguration( aux );
-
- channel.setDecodeConfiguration( DecoderFactory.copy( mDecodeConfiguration ) );
-
- EventLogConfiguration log = new EventLogConfiguration();
-
- for( EventLogType logType: mEventLogConfiguration.getLoggers() )
- {
- log.addLogger( logType );
- }
-
- channel.setEventLogConfiguration( log );
+ /**
+ * Constructs a new standard channel with the specified channel name
+ */
+ public Channel(String channelName)
+ {
+ this();
+ mName = channelName;
+ }
- RecordConfiguration record = new RecordConfiguration();
-
- for( RecorderType recordType: mRecordConfiguration.getRecorders() )
- {
- record.addRecorder( recordType );
- }
-
- channel.setRecordConfiguration( record );
+ /**
+ * Constructs a new standard channel with a default name of "Channel"
+ */
+ public Channel()
+ {
+ mChannelID = UNIQUE_ID++;
+ }
+
+ /**
+ * Creates a (new) deep copy of this channel
+ */
+ public Channel copyOf()
+ {
+ Channel channel = new Channel(mName);
+ channel.setSystem(mSystem);
+ channel.setSite(mSite);
+ channel.setAliasListName(mAliasListName);
+
+ AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
+
+ for(DecoderType auxType : aux.getAuxDecoders())
+ {
+ aux.addAuxDecoder(auxType);
+ }
+
+ channel.setAuxDecodeConfiguration(aux);
+
+ channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
+
+ EventLogConfiguration log = new EventLogConfiguration();
+
+ for(EventLogType logType : mEventLogConfiguration.getLoggers())
+ {
+ log.addLogger(logType);
+ }
+
+ channel.setEventLogConfiguration(log);
+
+ RecordConfiguration record = new RecordConfiguration();
+
+ for(RecorderType recordType : mRecordConfiguration.getRecorders())
+ {
+ record.addRecorder(recordType);
+ }
+
+ channel.setRecordConfiguration(record);
+
+ channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
+
+ return channel;
+ }
+
+ /**
+ * Unique identifier for this channel. Value is transient (not persisted).
+ */
+ @XmlTransient
+ public int getChannelID()
+ {
+ return mChannelID;
+ }
+
+ /**
+ * Indicates if this channel has been selected for prioritized audio output
+ * and display of processing chain products
+ */
+ @XmlTransient
+ public boolean isSelected()
+ {
+ return mSelected;
+ }
+
+ /**
+ * Sets selection status of the channel.
+ *
+ * NOTE: this method is package private so that only the ChannelSelectionManager
+ * can toggle the selection state. Use the channel model to broadcast a
+ * request select/deselect channel event.
+ *
+ * @param selected
+ */
+ void setSelected(boolean selected)
+ {
+ mSelected = selected;
+ }
+
+ @XmlTransient
+ public ChannelType getChannelType()
+ {
+ return mChannelType;
+ }
- channel.setSourceConfiguration( SourceConfigFactory.copy( mSourceConfiguration ) );
-
- return channel;
- }
-
- /**
- * Unique identifier for this channel. Value is transient (not persisted).
- */
- @XmlTransient
- public int getChannelID()
- {
- return mChannelID;
- }
-
- /**
- * Indicates if this channel has been selected for prioritized audio output
- * and display of processing chain products
- */
- @XmlTransient
- public boolean isSelected()
- {
- return mSelected;
- }
-
- /**
- * Sets selection status of the channel.
- *
- * NOTE: this method is package private so that only the ChannelSelectionManager
- * can toggle the selection state. Use the channel model to broadcast a
- * request select/deselect channel event.
- *
- * @param selected
- */
- void setSelected( boolean selected )
- {
- mSelected = selected;
- }
-
- @XmlTransient
- public ChannelType getChannelType()
- {
- return mChannelType;
- }
-
/**
* Returns the owning system for this channel.
*/
- @XmlAttribute( name = "system" )
- public String getSystem()
- {
- return mSystem;
- }
-
- public void setSystem( String system )
- {
- mSystem = system;
- }
+ @XmlAttribute(name = "system")
+ public String getSystem()
+ {
+ return mSystem;
+ }
- public boolean hasSystem()
- {
- return mSystem != null;
- }
-
- /**
- * Returns the owning site for this channel.
- */
- @XmlAttribute( name = "site" )
- public String getSite()
- {
- return mSite;
- }
-
- public void setSite( String site )
- {
- mSite = site;
- }
-
- public boolean hasSite()
- {
- return mSite != null;
- }
-
- /**
- * Returns the name of this channel.
- */
- @XmlAttribute( name = "name" )
- public String getName()
- {
- return mName;
- }
-
- /**
- * Sets the name of this channel.
- */
- public void setName( String name )
- {
- mName = name;
- }
+ public void setSystem(String system)
+ {
+ mSystem = system;
+ }
+
+ public boolean hasSystem()
+ {
+ return mSystem != null;
+ }
+
+ /**
+ * Returns the owning site for this channel.
+ */
+ @XmlAttribute(name = "site")
+ public String getSite()
+ {
+ return mSite;
+ }
+
+ public void setSite(String site)
+ {
+ mSite = site;
+ }
+
+ public boolean hasSite()
+ {
+ return mSite != null;
+ }
+
+ /**
+ * Returns the name of this channel.
+ */
+ @XmlAttribute(name = "name")
+ public String getName()
+ {
+ return mName;
+ }
+
+ /**
+ * Sets the name of this channel.
+ */
+ public void setName(String name)
+ {
+ mName = name;
+ }
/**
* Default display string for this channel: SYSTEM_SITE_NAME
*/
- public String toString()
- {
- StringBuilder sb = new StringBuilder();
- sb.append( hasSystem() ? getSystem() : "SYSTEM" );
- sb.append( "_" );
- sb.append( hasSite() ? getSite() : "SITE" );
- sb.append( "_" );
- sb.append( getName() );
-
- return sb.toString();
- }
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(hasSystem() ? getSystem() : "SYSTEM");
+ sb.append("_");
+ sb.append(hasSite() ? getSite() : "SITE");
+ sb.append("_");
+ sb.append(getName());
- /**
- * Indicates if this channel is enabled for processing.
- */
- @XmlAttribute( name = "enabled" )
- public boolean getEnabled()
- {
- return mEnabled;
- }
+ return sb.toString();
+ }
- /**
- * Indicates if this channel will automatically start processing on
- * application startup or if this channel is currently processing after
- * application startup.
- *
- * Note: this method is package private and is intended to be managed
- * by a co-package central processing manager
- *
- * @see controller.ChannelProcessingManager
- */
- void setEnabled( boolean enabled )
- {
- mEnabled = enabled;
- }
+ /**
+ * Indicates if this channel is enabled for processing.
+ */
+ @XmlAttribute(name = "enabled")
+ public boolean getEnabled()
+ {
+ return mEnabled;
+ }
- /**
- * Returns the alias list that is used by this channel for looking up alias
- * values for the various identifiers produced by the decoder(s).
- */
- @XmlElement( name = "alias_list_name" )
- public String getAliasListName()
- {
- return mAliasListName;
- }
+ /**
+ * Indicates if this channel will automatically start processing on
+ * application startup or if this channel is currently processing after
+ * application startup.
+ *
+ * Note: this method is package private and is intended to be managed
+ * by a co-package central processing manager
+ *
+ * @see controller.channel.ChannelProcessingManager
+ */
+ void setEnabled(boolean enabled)
+ {
+ mEnabled = enabled;
+ }
- /**
- * Sets the alias list to be used for looking up the alias values for the
- * various identifiers produced by the decoder
- */
- public void setAliasListName( String name )
- {
- mAliasListName = name;
- }
+ /**
+ * Returns the alias list that is used by this channel for looking up alias
+ * values for the various identifiers produced by the decoder(s).
+ */
+ @XmlElement(name = "alias_list_name")
+ public String getAliasListName()
+ {
+ return mAliasListName;
+ }
- /**
- * Gets the primary decoder configuration used by this channel
- */
- @XmlElement( name = "decode_configuration" )
- public DecodeConfiguration getDecodeConfiguration()
- {
- return mDecodeConfiguration;
- }
+ /**
+ * Sets the alias list to be used for looking up the alias values for the
+ * various identifiers produced by the decoder
+ */
+ public void setAliasListName(String name)
+ {
+ mAliasListName = name;
+ }
- /**
- * Sets the primary decoder configuration used by this channel
- */
- public void setDecodeConfiguration( DecodeConfiguration config )
- {
- mDecodeConfiguration = config;
- }
+ /**
+ * Gets the primary decoder configuration used by this channel
+ */
+ @XmlElement(name = "decode_configuration")
+ public DecodeConfiguration getDecodeConfiguration()
+ {
+ return mDecodeConfiguration;
+ }
- /**
- * Gets the aux decoder configuration used by this channel. Aux decoders
- * operate on demodulated audio
- */
- @XmlElement( name = "aux_decode_configuration" )
- public AuxDecodeConfiguration getAuxDecodeConfiguration()
- {
- return mAuxDecodeConfiguration;
- }
+ /**
+ * Sets the primary decoder configuration used by this channel
+ */
+ public void setDecodeConfiguration(DecodeConfiguration config)
+ {
+ mDecodeConfiguration = config;
+ }
- /**
- * Sets the decoder configuration used by this channel
- */
- public void setAuxDecodeConfiguration( AuxDecodeConfiguration config )
- {
- mAuxDecodeConfiguration = config;
- }
+ /**
+ * Gets the aux decoder configuration used by this channel. Aux decoders
+ * operate on demodulated audio
+ */
+ @XmlElement(name = "aux_decode_configuration")
+ public AuxDecodeConfiguration getAuxDecodeConfiguration()
+ {
+ return mAuxDecodeConfiguration;
+ }
- /**
- * Returns the source configuration for this channel. A channel source
- * identifies where the processing chain will get source sample data from.
- */
- @XmlElement( name = "source_configuration" )
- public SourceConfiguration getSourceConfiguration()
- {
- return mSourceConfiguration;
- }
+ /**
+ * Sets the decoder configuration used by this channel
+ */
+ public void setAuxDecodeConfiguration(AuxDecodeConfiguration config)
+ {
+ mAuxDecodeConfiguration = config;
+ }
- /**
- * Sets the source configuration for this channel.
- */
- public void setSourceConfiguration( SourceConfiguration config )
- {
- mSourceConfiguration = config;
+ /**
+ * Returns the source configuration for this channel. A channel source
+ * identifies where the processing chain will get source sample data from.
+ */
+ @XmlElement(name = "source_configuration")
+ public SourceConfiguration getSourceConfiguration()
+ {
+ return mSourceConfiguration;
+ }
- //Clear the tune channel object so that it can be recreated if the
- //source configuration changes
- mTunerChannel = null;
- }
+ /**
+ * Sets the source configuration for this channel.
+ */
+ public void setSourceConfiguration(SourceConfiguration config)
+ {
+ mSourceConfiguration = config;
- /**
- * Returns the event logger configuration for this channel.
- */
- @XmlElement( name = "event_log_configuration" )
- public EventLogConfiguration getEventLogConfiguration()
- {
- return mEventLogConfiguration;
- }
+ //Clear the tune channel object so that it can be recreated if the
+ //source configuration changes
+ mTunerChannel = null;
+ }
- /**
- * Sets the event logger configuration for this channel.
- */
- public void setEventLogConfiguration( EventLogConfiguration config )
- {
- mEventLogConfiguration = config;
- }
+ /**
+ * Returns the event logger configuration for this channel.
+ */
+ @XmlElement(name = "event_log_configuration")
+ public EventLogConfiguration getEventLogConfiguration()
+ {
+ return mEventLogConfiguration;
+ }
- /**
- * Returns the recorder configuration for this channel.
- */
- @XmlElement( name = "record_configuration" )
- public RecordConfiguration getRecordConfiguration()
- {
- return mRecordConfiguration;
- }
+ /**
+ * Sets the event logger configuration for this channel.
+ */
+ public void setEventLogConfiguration(EventLogConfiguration config)
+ {
+ mEventLogConfiguration = config;
+ }
- /**
- * Sets the recorder configuration for this channel.
- */
- public void setRecordConfiguration( RecordConfiguration config )
- {
- mRecordConfiguration = config;
- }
+ /**
+ * Returns the recorder configuration for this channel.
+ */
+ @XmlElement(name = "record_configuration")
+ public RecordConfiguration getRecordConfiguration()
+ {
+ return mRecordConfiguration;
+ }
- /**
- * Convenience method to construct a tuner channel object representing a
- * tuner or recording source frequency and bandwidth that can be used by
- * application components for graphically representing this channel.
- *
- * If the source configuration is not a tuner or recording, this method
- * returns null.
- */
- @XmlTransient
+ /**
+ * Sets the recorder configuration for this channel.
+ */
+ public void setRecordConfiguration(RecordConfiguration config)
+ {
+ mRecordConfiguration = config;
+ }
+
+ /**
+ * Convenience method to construct a tuner channel object representing a
+ * tuner or recording source frequency and bandwidth that can be used by
+ * application components for graphically representing this channel.
+ *
+ * If the source configuration is not a tuner or recording, this method
+ * returns null.
+ */
+ @XmlTransient
public TunerChannel getTunerChannel()
{
- if( mTunerChannel == null )
- {
- if( mSourceConfiguration.getSourceType() == SourceType.TUNER )
- {
- SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
+ if(mTunerChannel == null)
+ {
+ if(mSourceConfiguration.getSourceType() == SourceType.TUNER)
+ {
+ SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
- mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(),
- mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
- }
- else if( mSourceConfiguration.getSourceType() == SourceType.RECORDING )
- {
- SourceConfigRecording config =
- (SourceConfigRecording)mSourceConfiguration;
+ mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
+ mDecodeConfiguration.getDecoderType().getChannelBandwidth());
+ }
+ else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
+ {
+ SourceConfigRecording config =
+ (SourceConfigRecording)mSourceConfiguration;
+
+ mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
+ mDecodeConfiguration.getDecoderType().getChannelBandwidth());
+ }
+ }
- mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(),
- mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
- }
- }
-
return mTunerChannel;
}
- /**
- * Convenience method to indicate if any part of this channel is contained
- * within the minimum and maximum frequency values.
- */
- public boolean isWithin( long minimum, long maximum )
- {
- TunerChannel tunerChannel = getTunerChannel();
-
- return tunerChannel != null &&
- tunerChannel.overlaps( minimum, maximum );
- }
-
- /**
- * Convenience method to make the current channel frequency correction value
- * available for use outside of a constructed processing chain while maintaining
- * linkage to the source channel. This hack allows the correction value to
- * be used to visually show in the spectral display.
- */
- @Override
- public void frequencyChanged( FrequencyChangeEvent event )
- {
- if( event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE )
- {
- mChannelFrequencyCorrection = event.getValue().intValue();
- }
- }
+ /**
+ * Convenience method to indicate if any part of this channel is contained
+ * within the minimum and maximum frequency values.
+ */
+ public boolean isWithin(long minimum, long maximum)
+ {
+ TunerChannel tunerChannel = getTunerChannel();
- @XmlTransient
- public int getChannelFrequencyCorrection()
- {
- return mChannelFrequencyCorrection;
- }
+ return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
+ }
+
+ /**
+ * Convenience method to make the current channel frequency correction value
+ * available for use outside of a constructed processing chain while maintaining
+ * linkage to the source channel. This hack allows the correction value to
+ * be used to visually show in the spectral display.
+ */
+ @Override
+ public void receive(FrequencyChangeEvent event)
+ {
+ if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
+ {
+ mChannelFrequencyCorrection = event.getValue().intValue();
+ }
+ }
+
+ /**
+ * Current channel frequency correction value (when this channel is processing)
+ * @return
+ */
+ @XmlTransient
+ public int getChannelFrequencyCorrection()
+ {
+ return mChannelFrequencyCorrection;
+ }
+
+ /**
+ * Resets frequency correction value to 0
+ */
+ public void resetFrequencyCorrection()
+ {
+ mChannelFrequencyCorrection = 0;
+ }
}
diff --git a/src/controller/channel/ChannelProcessingManager.java b/src/controller/channel/ChannelProcessingManager.java
index 93bf1f0c..ad65bd3c 100644
--- a/src/controller/channel/ChannelProcessingManager.java
+++ b/src/controller/channel/ChannelProcessingManager.java
@@ -231,7 +231,7 @@ public class ChannelProcessingManager implements ChannelEventListener
if(processingChain == null)
{
- processingChain = new ProcessingChain(channel.getName(), channel.getChannelType());
+ processingChain = new ProcessingChain(channel.getChannelType());
/* Register global listeners */
for(Listener listener : mAudioPacketListeners)
@@ -244,6 +244,9 @@ public class ChannelProcessingManager implements ChannelEventListener
processingChain.addMessageListener(listener);
}
+ //Register channel to receive frequency correction events to show in the spectral display (hack!)
+ processingChain.addFrequencyChangeListener(channel);
+
/* Processing Modules */
List modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this,
mAliasModel, channel, processingChain.getChannelState().getMutableMetadata());
@@ -347,6 +350,9 @@ public class ChannelProcessingManager implements ChannelEventListener
processingChain.removeRecordingModules();
+ //Deregister channel from receive frequency correction events to show in the spectral display (hack!)
+ processingChain.removeFrequencyChangeListener(channel);
+ channel.resetFrequencyCorrection();
mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP));
diff --git a/src/dsp/filter/cic/ComplexPrimeCICDecimate.java b/src/dsp/filter/cic/ComplexPrimeCICDecimate.java
index 72a869d0..95bf15ce 100644
--- a/src/dsp/filter/cic/ComplexPrimeCICDecimate.java
+++ b/src/dsp/filter/cic/ComplexPrimeCICDecimate.java
@@ -1,408 +1,412 @@
/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 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
+ * 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
+ *
******************************************************************************/
package dsp.filter.cic;
-import java.util.ArrayList;
-import java.util.List;
-
+import dsp.filter.FilterFactory;
+import dsp.filter.Filters;
+import dsp.filter.Window.WindowType;
+import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
+import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
import org.apache.commons.lang3.Validate;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexSampleListener;
import sample.complex.ComplexToComplexBufferAssembler;
import sample.decimator.ComplexDecimator;
-import dsp.filter.FilterFactory;
-import dsp.filter.Filters;
-import dsp.filter.Window.WindowType;
-import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
-import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
+
+import java.util.ArrayList;
+import java.util.List;
public class ComplexPrimeCICDecimate implements Listener
{
- public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
- 59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,
- 157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,
- 257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,
- 367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,
- 467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,
- 599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 };
-
- private ArrayList mDecimatingStages =
- new ArrayList();
-
- private DecimatingStage mFirstDecimatingStage;
+ public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
+ 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
+ 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
+ 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359,
+ 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
+ 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593,
+ 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
- private Output mOutput;
+ private ArrayList mDecimatingStages =
+ new ArrayList();
- /**
- * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
- * decimated, single paired i/q float sample output.
- *
- * Implements the CIC filter described in Understanding Digital Signal
- * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
- * decimating stages each with a prime factor decimation rate. Multiple
- * stages are cascaded to achieve the overall decimation rate.
- *
- * This filter supports a maximum decimation rate of 700. This filter can
- * be adapted to higher decimation rates by adding additional prime factors
- * to the PRIMES array.
- *
- * @param decimation - overall decimation rate
- * @param order - filter order
- */
- public ComplexPrimeCICDecimate( int decimation, int order,
- int passFrequency, int attenuation, WindowType windowType )
- {
- Validate.isTrue(decimation <= 700);
-
+ private DecimatingStage mFirstDecimatingStage;
- List stageSizes = getPrimeFactors( decimation );
-
- for( int x = 0; x < stageSizes.size(); x++ )
- {
- DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order );
-
- mDecimatingStages.add( stage );
-
- if( x == 0 )
- {
- /* Reference to first stage -- will receive all samples */
- mFirstDecimatingStage = stage;
- }
- else
- {
- /* Wire the current stage to the previous stage */
- mDecimatingStages.get( x - 1 ).setListener( stage );
- }
- }
-
- mOutput = new Output( 48000, passFrequency, attenuation, windowType );
-
- mDecimatingStages.get( mDecimatingStages.size() - 1 )
- .setListener( mOutput );
- }
-
- public void dispose()
- {
- for( DecimatingStage stage: mDecimatingStages )
- {
- stage.dispose();
- }
-
- mDecimatingStages.clear();
- mDecimatingStages = null;
- mFirstDecimatingStage = null;
-
- mOutput.dispose();
- mOutput = null;
- }
-
- /**
- * Adds a listener to receive the output of this CIC decimation filter
- */
- public void setListener( Listener listener )
- {
- mOutput.setListener( listener );
- }
-
- /**
- * Removes listener from output of this CIC decimation filter
- */
- public void removeListener()
- {
- mOutput.removeListener();
- }
+ private Output mOutput;
- /**
- * Calculates the prime factors of the decimation rate up to a maximum
- * decimation rate of 700. If you wish to have decimation rates higher
- * than 700, then add additional prime factors to the PRIMES array.
- *
- * @param decimation - integral decimation rate
- * @return - ordered list (smallest to largest) of prime factors
- */
- public static List getPrimeFactors( int decimation )
- {
- ArrayList stages = new ArrayList();
-
- int pointer = 0;
-
- while( decimation > 0 && pointer < PRIMES.length )
- {
- int prime = PRIMES[ pointer ];
-
- if( decimation % prime == 0 )
- {
- stages.add( prime );
-
- decimation /= prime;
- }
- else
- {
- pointer++;
- }
- }
-
- return stages;
- }
-
- /**
- * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
- */
- @Override
- public void receive( ComplexBuffer buffer )
+ /**
+ * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
+ * decimated, single paired i/q float sample output.
+ *
+ * Implements the CIC filter described in Understanding Digital Signal
+ * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
+ * decimating stages each with a prime factor decimation rate. Multiple
+ * stages are cascaded to achieve the overall decimation rate.
+ *
+ * This filter supports a maximum decimation rate of 700. This filter can
+ * be adapted to higher decimation rates by adding additional prime factors
+ * to the PRIMES array.
+ *
+ * @param decimation - overall decimation rate
+ * @param order - filter order
+ */
+ public ComplexPrimeCICDecimate(int decimation, int order,
+ int passFrequency, int attenuation, WindowType windowType)
{
- if( mFirstDecimatingStage != null )
- {
- float[] samples = buffer.getSamples();
-
- for( int x = 0; x < samples.length; x += 2 )
- {
- mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] );
- }
- }
- }
-
- /**
- * Decimating stage combines multiple CIC stages with a decimator. The
- * number of stages is indicated by the order value and the size indicates
- * the decimation rate of this stage.
- */
- public class DecimatingStage implements ComplexSampleListener
- {
- private ArrayList mStages = new ArrayList();
- private Stage mFirstStage;
- private ComplexDecimator mDecimator;
-
- public DecimatingStage( int size, int order )
- {
- for( int x = 0; x < order; x++ )
- {
- Stage stage;
-
- if( size == 2 )
- {
- stage = new TwoStage();
- }
- else
- {
- stage = new Stage( size );
- }
-
- mStages.add( stage );
-
- if( x == 0 )
- {
- mFirstStage = stage;
- }
- else
- {
- mStages.get( x - 1 ).setListener( stage );
- }
- }
-
- mDecimator = new ComplexDecimator( size );
-
- mStages.get( mStages.size() - 1 ).setListener( mDecimator );
- }
-
- public void dispose()
- {
- for( Stage stage: mStages )
- {
- stage.dispose();
- }
-
- mStages.clear();
- mDecimator.dispose();
- mDecimator = null;
- mFirstStage = null;
- mStages = null;
- }
+ Validate.isTrue(decimation <= 700);
- @Override
- public void receive( float i, float q )
+
+ List stageSizes = getPrimeFactors(decimation);
+
+ for(int x = 0; x < stageSizes.size(); x++)
{
- mFirstStage.receive( i, q );
- }
-
- public void setListener( ComplexSampleListener listener )
- {
- mDecimator.setListener( listener );
- }
- }
-
- /**
- * Single non-decimating CIC stage component. Uses a circular buffer and a
- * running average internally to implement the stage so that stage size has
- * essentially no impact on the computational requirements of the stage
- */
- public class Stage implements ComplexSampleListener
- {
- protected ComplexSampleListener mListener;
-
- private float[] mISamples;
- private float[] mQSamples;
-
- protected float mISum;
- protected float mQSum;
-
- private int mSamplePointer = 0;
- private int mSize;
-
- protected float mGain;
-
- public Stage()
- {
- }
+ DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
- public Stage( int size )
- {
- mSize = size - 1;
-
- mISamples = new float[ mSize ];
- mQSamples = new float[ mSize ];
-
- mGain = 1.0f / (float)size;
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- public void receive( float i, float q )
- {
+ mDecimatingStages.add(stage);
+
+ if(x == 0)
+ {
+ /* Reference to first stage -- will receive all samples */
+ mFirstDecimatingStage = stage;
+ }
+ else
+ {
+ /* Wire the current stage to the previous stage */
+ mDecimatingStages.get(x - 1).setListener(stage);
+ }
+ }
+
+ mOutput = new Output(48000, passFrequency, attenuation, windowType);
+
+ mDecimatingStages.get(mDecimatingStages.size() - 1)
+ .setListener(mOutput);
+ }
+
+ public void dispose()
+ {
+ for(DecimatingStage stage : mDecimatingStages)
+ {
+ stage.dispose();
+ }
+
+ mDecimatingStages.clear();
+ mDecimatingStages = null;
+ mFirstDecimatingStage = null;
+
+ mOutput.dispose();
+ mOutput = null;
+ }
+
+ /**
+ * Adds a listener to receive the output of this CIC decimation filter
+ */
+ public void setListener(Listener listener)
+ {
+ mOutput.setListener(listener);
+ }
+
+ /**
+ * Removes listener from output of this CIC decimation filter
+ */
+ public void removeListener()
+ {
+ mOutput.removeListener();
+ }
+
+ /**
+ * Calculates the prime factors of the decimation rate up to a maximum
+ * decimation rate of 700. If you wish to have decimation rates higher
+ * than 700, then add additional prime factors to the PRIMES array.
+ *
+ * @param decimation - integral decimation rate
+ * @return - ordered list (smallest to largest) of prime factors
+ */
+ public static List getPrimeFactors(int decimation)
+ {
+ ArrayList stages = new ArrayList();
+
+ int pointer = 0;
+
+ while(decimation > 0 && pointer < PRIMES.length)
+ {
+ int prime = PRIMES[pointer];
+
+ if(decimation % prime == 0)
+ {
+ stages.add(prime);
+
+ decimation /= prime;
+ }
+ else
+ {
+ pointer++;
+ }
+ }
+
+ return stages;
+ }
+
+ /**
+ * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
+ */
+ @Override
+ public void receive(ComplexBuffer buffer)
+ {
+ if(mFirstDecimatingStage != null)
+ {
+ float[] samples = buffer.getSamples();
+
+ for(int x = 0; x < samples.length; x += 2)
+ {
+ mFirstDecimatingStage.receive(samples[x], samples[x + 1]);
+ }
+ }
+ }
+
+ /**
+ * Decimating stage combines multiple CIC stages with a decimator. The
+ * number of stages is indicated by the order value and the size indicates
+ * the decimation rate of this stage.
+ */
+ public class DecimatingStage implements ComplexSampleListener
+ {
+ private ArrayList mStages = new ArrayList();
+ private Stage mFirstStage;
+ private ComplexDecimator mDecimator;
+
+ public DecimatingStage(int size, int order)
+ {
+ for(int x = 0; x < order; x++)
+ {
+ Stage stage;
+
+ if(size == 2)
+ {
+ stage = new TwoStage();
+ }
+ else
+ {
+ stage = new Stage(size);
+ }
+
+ mStages.add(stage);
+
+ if(x == 0)
+ {
+ mFirstStage = stage;
+ }
+ else
+ {
+ mStages.get(x - 1).setListener(stage);
+ }
+ }
+
+ mDecimator = new ComplexDecimator(size);
+
+ mStages.get(mStages.size() - 1).setListener(mDecimator);
+ }
+
+ public void dispose()
+ {
+ for(Stage stage : mStages)
+ {
+ stage.dispose();
+ }
+
+ mStages.clear();
+ mDecimator.dispose();
+ mDecimator = null;
+ mFirstStage = null;
+ mStages = null;
+ }
+
+ @Override
+ public void receive(float i, float q)
+ {
+ mFirstStage.receive(i, q);
+ }
+
+ public void setListener(ComplexSampleListener listener)
+ {
+ mDecimator.setListener(listener);
+ }
+ }
+
+ /**
+ * Single non-decimating CIC stage component. Uses a circular buffer and a
+ * running average internally to implement the stage so that stage size has
+ * essentially no impact on the computational requirements of the stage
+ */
+ public class Stage implements ComplexSampleListener
+ {
+ protected ComplexSampleListener mListener;
+
+ private float[] mISamples;
+ private float[] mQSamples;
+
+ protected float mISum;
+ protected float mQSum;
+
+ private int mSamplePointer = 0;
+ private int mSize;
+
+ protected float mGain;
+
+ public Stage()
+ {
+ }
+
+ public Stage(int size)
+ {
+ mSize = size - 1;
+
+ mISamples = new float[mSize];
+ mQSamples = new float[mSize];
+
+ mGain = 1.0f / (float)size;
+ }
+
+ public void dispose()
+ {
+ mListener = null;
+ }
+
+ public void receive(float i, float q)
+ {
/* Subtract the oldest sample and add back in the newest */
- mISum = mISum - mISamples[ mSamplePointer ] + i;
- mQSum = mQSum - mQSamples[ mSamplePointer ] + q;
+ mISum = mISum - mISamples[mSamplePointer] + i;
+ mQSum = mQSum - mQSamples[mSamplePointer] + q;
/* Overwrite the oldest sample with the newest */
- mISamples[ mSamplePointer ] = i;
- mQSamples[ mSamplePointer ] = q;
-
- mSamplePointer++;
-
- if( mSamplePointer >= mSize )
- {
- mSamplePointer = 0;
- }
+ mISamples[mSamplePointer] = i;
+ mQSamples[mSamplePointer] = q;
- if( mListener != null )
- {
- mListener.receive( ( mISum * mGain ), ( mQSum * mGain ) );
- }
- }
-
- public void setListener( ComplexSampleListener listener )
- {
- mListener = listener;
- }
- }
-
- /**
- * Size 2 stage that removes the unnecessary circular buffer management for
- * a two-stage.
- */
- public class TwoStage extends Stage
- {
- public TwoStage()
- {
- super();
-
- mGain = 0.5f;
- }
-
- public void receive( float i, float q )
- {
- float iSum = mISum + i;
- float qSum = mQSum + q;
-
- mISum = i;
- mQSum = q;
-
- if( mListener != null )
- {
- mListener.receive( ( iSum * mGain ), ( qSum * mGain ) );
- }
- }
- }
-
+ mSamplePointer++;
- /**
- * Output adapter - applies gain correction and cleanup filter and broadcast
- * to registered listener.
- */
- public class Output implements ComplexSampleListener
- {
- /* Decimated output buffers will contain 1024 samples */
- private ComplexToComplexBufferAssembler mAssembler =
- new ComplexToComplexBufferAssembler( 2048 );
+ if(mSamplePointer >= mSize)
+ {
+ mSamplePointer = 0;
+ }
- private ComplexFIRFilter_CB_CB mCleanupFilter;
- private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
- Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false );
-
- public Output( int outputSampleRate, int passFrequency, int attenuation,
- WindowType windowType )
- {
- mCleanupFilter = new ComplexFIRFilter_CB_CB( FilterFactory
- .getCICCleanupFilter( outputSampleRate,
- passFrequency,
- attenuation,
- windowType ), 0.4f );
-
- mAssembler.setListener( mCleanupFilter );
- mCleanupFilter.setListener( mHalfBandFilter );
- }
-
- public void dispose()
- {
- mAssembler.dispose();
- mCleanupFilter.dispose();
- mHalfBandFilter.dispose();
- }
-
- /**
- * Receiver method for the output adapter to receive a filtered,
- * decimated sample, apply gain correction, apply cleanup filtering
- * and output the sample values as a complex sample.
- */
- @Override
- public void receive( float inphase, float quadrature )
- {
- mAssembler.receive( inphase, quadrature );
- }
-
- /**
- * Adds a listener to receive output samples
- */
- public void setListener( Listener listener )
- {
- mHalfBandFilter.setListener( listener );
+ if(mListener != null)
+ {
+ mListener.receive((mISum * mGain), (mQSum * mGain));
+ }
}
- /**
- * Removes the listener from receiving output samples
- */
+ public void setListener(ComplexSampleListener listener)
+ {
+ mListener = listener;
+ }
+ }
+
+ /**
+ * Size 2 stage that removes the unnecessary circular buffer management for
+ * a two-stage.
+ */
+ public class TwoStage extends Stage
+ {
+ public TwoStage()
+ {
+ super();
+
+ mGain = 0.5f;
+ }
+
+ public void receive(float i, float q)
+ {
+ float iSum = mISum + i;
+ float qSum = mQSum + q;
+
+ mISum = i;
+ mQSum = q;
+
+ if(mListener != null)
+ {
+ mListener.receive((iSum * mGain), (qSum * mGain));
+ }
+ }
+ }
+
+
+ /**
+ * Output adapter - applies gain correction and cleanup filter and broadcast
+ * to registered listener.
+ */
+ public class Output implements ComplexSampleListener
+ {
+ /* Decimated output buffers will contain 1024 samples */
+ private ComplexToComplexBufferAssembler mAssembler =
+ new ComplexToComplexBufferAssembler(2048);
+
+ private ComplexFIRFilter_CB_CB mCleanupFilter;
+ private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
+ Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
+
+ public Output(int outputSampleRate, int passFrequency, int attenuation,
+ WindowType windowType)
+ {
+ mCleanupFilter = new ComplexFIRFilter_CB_CB(FilterFactory
+ .getCICCleanupFilter(outputSampleRate,
+ passFrequency,
+ attenuation,
+ windowType), 0.4f);
+
+ //Bypassing the CIC cleanup filter for now
+ mAssembler.setListener(mHalfBandFilter);
+
+// mAssembler.setListener( mCleanupFilter );
+// mCleanupFilter.setListener( mHalfBandFilter );
+ }
+
+ public void dispose()
+ {
+ mAssembler.dispose();
+ mCleanupFilter.dispose();
+ mHalfBandFilter.dispose();
+ }
+
+ /**
+ * Receiver method for the output adapter to receive a filtered,
+ * decimated sample, apply gain correction, apply cleanup filtering
+ * and output the sample values as a complex sample.
+ */
+ @Override
+ public void receive(float inphase, float quadrature)
+ {
+ mAssembler.receive(inphase, quadrature);
+ }
+
+ /**
+ * Adds a listener to receive output samples
+ */
+ public void setListener(Listener listener)
+ {
+ mHalfBandFilter.setListener(listener);
+ }
+
+ /**
+ * Removes the listener from receiving output samples
+ */
public void removeListener()
{
- mHalfBandFilter.removeListener();
+ mHalfBandFilter.removeListener();
}
- }
+ }
}
diff --git a/src/instrument/InstrumentableProcessingChain.java b/src/instrument/InstrumentableProcessingChain.java
index 882eb891..0ca61c90 100644
--- a/src/instrument/InstrumentableProcessingChain.java
+++ b/src/instrument/InstrumentableProcessingChain.java
@@ -1,65 +1,82 @@
+/*******************************************************************************
+ * 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
+ *
+ ******************************************************************************/
package instrument;
+import controller.channel.Channel.ChannelType;
import instrument.tap.Tap;
import instrument.tap.TapGroup;
+import module.Module;
+import module.ProcessingChain;
+import source.Source;
import java.util.ArrayList;
import java.util.List;
-import module.Module;
-import module.ProcessingChain;
-import source.Source;
-import controller.channel.Channel.ChannelType;
-
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
{
- public InstrumentableProcessingChain()
- {
- super( "instrumented", ChannelType.STANDARD );
- }
+ public InstrumentableProcessingChain()
+ {
+ super(ChannelType.STANDARD);
+ }
- public void setSource( Source source ) throws IllegalStateException
- {
- mSource = source;
- }
+ public void setSource(Source source) throws IllegalStateException
+ {
+ mSource = source;
+ }
- @Override
- public List getTapGroups()
- {
- List groups = new ArrayList<>();
-
- for( Module module: getModules() )
- {
- if( module instanceof Instrumentable )
- {
- groups.addAll( ((Instrumentable)module).getTapGroups() );
- }
- }
-
- return groups;
- }
+ @Override
+ public List getTapGroups()
+ {
+ List groups = new ArrayList<>();
- @Override
- public void registerTap( Tap tap )
- {
- for( Module module: getModules() )
- {
- if( module instanceof Instrumentable )
- {
- ((Instrumentable)module).registerTap( tap );
- }
- }
- }
+ for(Module module : getModules())
+ {
+ if(module instanceof Instrumentable)
+ {
+ groups.addAll(((Instrumentable)module).getTapGroups());
+ }
+ }
- @Override
- public void unregisterTap( Tap tap )
- {
- for( Module module: getModules() )
- {
- if( module instanceof Instrumentable )
- {
- ((Instrumentable)module).unregisterTap( tap );
- }
- }
- }
+ return groups;
+ }
+
+ @Override
+ public void registerTap(Tap tap)
+ {
+ for(Module module : getModules())
+ {
+ if(module instanceof Instrumentable)
+ {
+ ((Instrumentable)module).registerTap(tap);
+ }
+ }
+ }
+
+ @Override
+ public void unregisterTap(Tap tap)
+ {
+ for(Module module : getModules())
+ {
+ if(module instanceof Instrumentable)
+ {
+ ((Instrumentable)module).unregisterTap(tap);
+ }
+ }
+ }
}
diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java
index fdf60e86..a018e360 100644
--- a/src/module/ProcessingChain.java
+++ b/src/module/ProcessingChain.java
@@ -108,7 +108,6 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster mSquelchStateBroadcaster = new Broadcaster<>();
- private String mName;
private AtomicBoolean mRunning = new AtomicBoolean();
protected Source mSource;
@@ -120,13 +119,10 @@ public class ProcessingChain implements IChannelEventListener
/**
* Creates a processing chain for managing a set of modules
*
- * @param name for thread pool naming each thread
* @param channelType
*/
- public ProcessingChain(String name, ChannelType channelType)
+ public ProcessingChain(ChannelType channelType)
{
- mName = name;
-
mChannelState = new ChannelState(channelType);
addModule(mChannelState);
@@ -211,6 +207,8 @@ public class ProcessingChain implements IChannelEventListener
}
mSource = source;
+
+ addModule(mSource);
}
/**
@@ -557,25 +555,6 @@ public class ProcessingChain implements IChannelEventListener
+ "sample type - cannot start processing chain");
}
- /* If this is a tuner source, broadcast the frequency to all
- * of the decoder state's and start the samples flowing */
- if(mSource instanceof TunerChannelSource)
- {
- TunerChannelSource tcs = (TunerChannelSource) mSource;
-
- try
- {
- long frequency = tcs.getFrequency();
-
- mDecoderStateEventBroadcaster.broadcast(
- new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, State.IDLE, frequency));
- }
- catch(SourceException e)
- {
- mLog.error("Error getting frequency from tuner channel source", e);
- }
- }
-
/* Start each of the modules */
for(Module module : mModules)
{
@@ -588,8 +567,6 @@ public class ProcessingChain implements IChannelEventListener
mLog.error("Error starting module", e);
}
}
-
- mSource.start(ThreadPool.SCHEDULED);
}
else
{
@@ -606,9 +583,16 @@ public class ProcessingChain implements IChannelEventListener
{
if(mRunning.compareAndSet(true, false))
{
+ /* Stop each of the modules */
+ for(Module module : mModules)
+ {
+ module.stop();
+ }
+
if(mSource != null)
{
- mSource.stop();
+ removeModule(mSource);
+
mSource.setOverflowListener(null);
switch(mSource.getSampleType())
@@ -629,11 +613,6 @@ public class ProcessingChain implements IChannelEventListener
mSource = null;
}
- /* Stop each of the modules */
- for(Module module : mModules)
- {
- module.stop();
- }
}
}
@@ -760,6 +739,24 @@ public class ProcessingChain implements IChannelEventListener
mMessageBroadcaster.removeListener(listener);
}
+ /**
+ * Adds the listener to receive frequency change events from the processing chain
+ * @param listener to receive events
+ */
+ public void addFrequencyChangeListener(Listener listener)
+ {
+ mFrequencyChangeEventBroadcaster.addListener(listener);
+ }
+
+ /**
+ * Removes the listener from receiving frequency change events
+ * @param listener to remove
+ */
+ public void removeFrequencyChangeListener(Listener listener)
+ {
+ mFrequencyChangeEventBroadcaster.removeListener(listener);
+ }
+
/**
* Adds the listener to receive call events from all modules.
*/
diff --git a/src/module/decode/DecoderFactory.java b/src/module/decode/DecoderFactory.java
index cce17e65..691f1055 100644
--- a/src/module/decode/DecoderFactory.java
+++ b/src/module/decode/DecoderFactory.java
@@ -222,7 +222,7 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD)
{
- modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
+ modules.add(new TrafficChannelManager(channelModel, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize()));
}
@@ -263,7 +263,7 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD)
{
- modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
+ modules.add(new TrafficChannelManager(channelModel, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize()));
}
diff --git a/src/module/decode/lj1200/LJ1200DecoderState.java b/src/module/decode/lj1200/LJ1200DecoderState.java
index e54e6729..5382f034 100644
--- a/src/module/decode/lj1200/LJ1200DecoderState.java
+++ b/src/module/decode/lj1200/LJ1200DecoderState.java
@@ -54,7 +54,7 @@ public class LJ1200DecoderState extends DecoderState
super(aliasList);
mAddressAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO,
- getAttributeChangeRequestListener(), getAliasList(), AliasIDType.LOJACK);
+ getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE);
}
@Override
diff --git a/src/module/decode/p25/C4FMSymbolFilter.java b/src/module/decode/p25/C4FMSymbolFilter.java
index 9b91c6e0..51e7b78d 100644
--- a/src/module/decode/p25/C4FMSymbolFilter.java
+++ b/src/module/decode/p25/C4FMSymbolFilter.java
@@ -9,21 +9,21 @@ import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.RealBuffer;
import sample.real.RealSampleListener;
import sample.real.RealSampleProvider;
+import source.tuner.TunerChannelSource;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeListener;
import dsp.gain.DirectGainControl;
-public class C4FMSymbolFilter implements Listener,
- IFrequencyChangeListener,
- RealSampleProvider,
- Instrumentable
+public class C4FMSymbolFilter implements Listener, IFrequencyChangeListener, RealSampleProvider, Instrumentable
{
- private static final float TAPS[][] =
+ private static final float TAPS[][] =
{
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
@@ -198,8 +198,7 @@ public class C4FMSymbolFilter implements Listener,
private RealSampleListener mListener;
- private DirectGainControl mGainController =
- new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
+ private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
private int mFrequencyAdjustmentRequested = 0;
private int mFrequencyCorrection = 0;
@@ -258,9 +257,8 @@ public class C4FMSymbolFilter implements Listener,
correction = -mFrequencyCorrectionMaximum;
}
- broadcast( new FrequencyChangeEvent(
- Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
-
+ broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
+
mFrequencyAdjustmentRequested = 0;
}
}
@@ -452,6 +450,10 @@ public class C4FMSymbolFilter implements Listener,
}
}
+ /**
+ * Broadcasts a frequency change event to the registered listener
+ * @param event
+ */
public void broadcast( FrequencyChangeEvent event )
{
if( mFrequencyChangeListener != null )
@@ -459,7 +461,11 @@ public class C4FMSymbolFilter implements Listener,
mFrequencyChangeListener.receive( event );
}
}
-
+
+ /**
+ * Sets the listener for frequency change events.
+ * @param listener
+ */
public void setFrequencyChangeListener( Listener listener )
{
mFrequencyChangeListener = listener;
diff --git a/src/module/decode/p25/P25Decoder.java b/src/module/decode/p25/P25Decoder.java
index 184c6f88..0ca1f8a9 100644
--- a/src/module/decode/p25/P25Decoder.java
+++ b/src/module/decode/p25/P25Decoder.java
@@ -4,8 +4,10 @@ import alias.AliasList;
import instrument.Instrumentable;
import module.decode.Decoder;
import module.decode.DecoderType;
+import source.tuner.frequency.IFrequencyChangeListener;
+import source.tuner.frequency.IFrequencyChangeProvider;
-public abstract class P25Decoder extends Decoder implements Instrumentable
+public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
{
private P25MessageProcessor mMessageProcessor;
diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java
index dbd7e596..828be84c 100644
--- a/src/module/decode/p25/P25DecoderState.java
+++ b/src/module/decode/p25/P25DecoderState.java
@@ -315,6 +315,8 @@ public class P25DecoderState extends DecoderState
*/
public void registerCallEvent(P25CallEvent event)
{
+ String channel = event.getChannel();
+
if(mChannelCallMap.containsKey(event.getChannel()))
{
P25CallEvent previousEvent = mChannelCallMap.remove(event.getChannel());
@@ -1557,6 +1559,7 @@ public class P25DecoderState extends DecoderState
break;
case IDENTIFIER_UPDATE_NON_VUHF:
case IDENTIFIER_UPDATE_VHF_UHF_BANDS:
+ case IDENTIFIER_UPDATE_TDMA:
IdentifierUpdate iu = (IdentifierUpdate)tsbk;
if(!mBands.containsKey(iu.getIdentifier()))
@@ -2735,6 +2738,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(pgvcg.isTDMAChannel() ? "TDMA " : "");
details.append(pgvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(pgvcg.getPriority());
details.append(pgvcg.isEncryptedChannel() ? " ENCRYPTED" : "");
@@ -2742,7 +2746,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
.aliasList(getAliasList())
- .channel(channel + (pgvcg.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(pgvcg.getDownlinkFrequency())
.from(from)
@@ -2775,8 +2779,9 @@ public class P25DecoderState extends DecoderState
{
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
.aliasList(getAliasList())
- .channel(channel + (gvcgu.isTDMAChannel1() ? " TDMA" : ""))
- .details((gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
+ .channel(channel)
+ .details((gvcgu.isTDMAChannel1() ? "TDMA " : "") +
+ (gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
.frequency(gvcgu.getDownlinkFrequency1())
.to(to)
.build();
@@ -2801,8 +2806,9 @@ public class P25DecoderState extends DecoderState
{
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
.aliasList(getAliasList())
- .channel(channel2 + (gvcgu.isTDMAChannel2() ? " TDMA" : ""))
- .details((gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
+ .channel(channel2)
+ .details((gvcgu.isTDMAChannel2() ? "TDMA " : "") +
+ (gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
.frequency(gvcgu.getDownlinkFrequency2())
.to(to2)
.build();
@@ -2866,6 +2872,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(gdcg.isTDMAChannel() ? "TDMA " : "");
details.append(gdcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gdcg.getPriority()).append(" ");
details.append(gdcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -2873,7 +2880,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList())
- .channel(channel + (gdcg.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(gdcg.getDownlinkFrequency())
.from(from)
@@ -2904,6 +2911,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(gvcg.isTDMAChannel() ? "TDMA " : "");
details.append(gvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcg.getPriority()).append(" ");
details.append(gvcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -2911,7 +2919,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList())
- .channel(channel + (gvcg.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(gvcg.getDownlinkFrequency())
.from(from)
@@ -2928,8 +2936,7 @@ public class P25DecoderState extends DecoderState
}
break;
case GROUP_VOICE_CHANNEL_GRANT_UPDATE:
- GroupVoiceChannelGrantUpdate gvcgu =
- (GroupVoiceChannelGrantUpdate)message;
+ GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)message;
channel = gvcgu.getChannel1();
from = null;
@@ -2942,6 +2949,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(gvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgu.getPriority()).append(" ");
details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -2949,7 +2957,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList())
- .channel(gvcgu.getChannel1() + (gvcgu.isTDMAChannel1() ? " TDMA" : ""))
+ .channel(gvcgu.getChannel1())
.details(details.toString())
.frequency(gvcgu.getDownlinkFrequency1())
.from(from)
@@ -2977,6 +2985,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(gvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgu.getPriority()).append(" ");
details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -2984,7 +2993,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event2 = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList())
- .channel(gvcgu.getChannel2() + (gvcgu.isTDMAChannel2() ? " TDMA" : ""))
+ .channel(gvcgu.getChannel2())
.details(details.toString())
.frequency(gvcgu.getDownlinkFrequency2())
.to(gvcgu.getGroupAddress2())
@@ -3016,6 +3025,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(gvcgue.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgue.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgue.getPriority()).append(" ");
details.append(gvcgue.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3023,7 +3033,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList())
- .channel(channel + (gvcgue.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(gvcgue.getDownlinkFrequency())
.from(from)
@@ -3053,6 +3063,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(idcg.isTDMAChannel() ? "TDMA " : "");
details.append(idcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(idcg.getPriority()).append(" ");
details.append(idcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3060,7 +3071,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList())
- .channel(channel + (idcg.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(idcg.getDownlinkFrequency())
.from(from)
@@ -3090,13 +3101,14 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(sdcg.isTDMAChannel() ? "TDMA " : "");
details.append(sdcg.isEmergency() ? "EMERGENCY " : "");
details.append(sdcg.isEncryptedChannel() ? "ENCRYPTED " : "");
details.append(sdcg.getSessionMode().name());
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList())
- .channel(channel + (sdcg.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(sdcg.getDownlinkFrequency())
.from(from)
@@ -3128,6 +3140,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(tivcg.isTDMAChannel() ? "TDMA " : "");
details.append(tivcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(tivcg.getPriority()).append(" ");
details.append(tivcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3137,7 +3150,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(
CallEventType.TELEPHONE_INTERCONNECT)
.aliasList(getAliasList())
- .channel(channel + (tivcg.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(tivcg.getDownlinkFrequency())
.from(from)
@@ -3170,6 +3183,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(tivcgu.isTDMAChannel() ? "TDMA " : "");
details.append(tivcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(tivcgu.getPriority()).append(" ");
details.append(tivcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3179,7 +3193,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(
CallEventType.TELEPHONE_INTERCONNECT)
.aliasList(getAliasList())
- .channel(channel + (tivcgu.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(tivcgu.getDownlinkFrequency())
.from(from)
@@ -3209,6 +3223,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(uuvcg.isTDMAChannel() ? "TDMA " : "");
details.append(uuvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(uuvcg.getPriority()).append(" ");
details.append(uuvcg.isEncryptedChannel() ? " ENCRYPTED " : "");
@@ -3217,7 +3232,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(
CallEventType.UNIT_TO_UNIT_CALL)
.aliasList(getAliasList())
- .channel(channel + (uuvcg.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(uuvcg.getDownlinkFrequency())
.from(from)
@@ -3247,6 +3262,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
+ details.append(uuvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(uuvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(uuvcgu.getPriority()).append(" ");
details.append(uuvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3255,7 +3271,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(
CallEventType.UNIT_TO_UNIT_CALL)
.aliasList(getAliasList())
- .channel(channel + (uuvcgu.isTDMAChannel() ? " TDMA" : ""))
+ .channel(channel)
.details(details.toString())
.frequency(uuvcgu.getDownlinkFrequency())
.from(from)
diff --git a/src/module/decode/p25/P25MessageFramer.java b/src/module/decode/p25/P25MessageFramer.java
index 39d9bb80..30af300b 100644
--- a/src/module/decode/p25/P25MessageFramer.java
+++ b/src/module/decode/p25/P25MessageFramer.java
@@ -689,8 +689,7 @@ public class P25MessageFramer implements Listener
private LSMDemodulator mDemodulator;
private double mCorrection;
- public CostasPhaseErrorDetector( FrameSync frameSync,
- LSMDemodulator demodulator, double correction )
+ public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction )
{
super( frameSync.getSync() );
diff --git a/src/module/decode/p25/P25_C4FMDecoder.java b/src/module/decode/p25/P25_C4FMDecoder.java
index 94f2be56..2e7b1114 100644
--- a/src/module/decode/p25/P25_C4FMDecoder.java
+++ b/src/module/decode/p25/P25_C4FMDecoder.java
@@ -1,19 +1,20 @@
/*******************************************************************************
- * 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
+ * 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
+ *
******************************************************************************/
package module.decode.p25;
@@ -41,8 +42,7 @@ import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
-public class P25_C4FMDecoder extends P25Decoder
- implements IFrequencyChangeListener, IFrequencyChangeProvider,IFilteredRealBufferListener
+public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
{
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
diff --git a/src/module/decode/p25/P25_LSMDecoder.java b/src/module/decode/p25/P25_LSMDecoder.java
index 4f86d2c8..1b135eb5 100644
--- a/src/module/decode/p25/P25_LSMDecoder.java
+++ b/src/module/decode/p25/P25_LSMDecoder.java
@@ -42,6 +42,7 @@ import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.gain.ComplexFeedForwardGainControl;
import dsp.psk.LSMDemodulator;
import dsp.psk.QPSKPolarSlicer;
+import source.tuner.frequency.FrequencyChangeEvent;
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
{
@@ -56,8 +57,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
private List mAvailableTaps;
private ComplexFIRFilter_CB_CB mBasebandFilter;
- private ComplexBufferToStreamConverter mStreamConverter =
- new ComplexBufferToStreamConverter();
+ private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
private ComplexFeedForwardGainControl mAGC =
new ComplexFeedForwardGainControl( 32 );
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
@@ -107,8 +107,31 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
mMessageFramer.dispose();
mMessageFramer = null;
}
-
- @Override
+
+ @Override
+ public void setFrequencyChangeListener(Listener listener)
+ {
+ }
+
+ @Override
+ public void removeFrequencyChangeListener()
+ {
+ }
+
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ return new Listener()
+ {
+ @Override
+ public void receive(FrequencyChangeEvent frequencyChangeEvent)
+ {
+ //Ignored
+ }
+ };
+ }
+
+ @Override
public Listener getComplexBufferListener()
{
return mBasebandFilter;
diff --git a/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java b/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java
index 5a4ab33d..fc3b2d95 100644
--- a/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java
+++ b/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java
@@ -41,7 +41,7 @@ public class IdentifierUpdateTDMA extends IdentifierUpdate
sb.append( " ID:" + getIdentifier() );
sb.append( " BASE:" + getBaseFrequency() );
- sb.append( " BW:" + getBandwidth() );
+ sb.append( " BANDWIDTH:" + getBandwidth() );
sb.append( " SPACING:" + getChannelSpacing() );
sb.append( " OFFSET:" + getTransmitOffset() );
sb.append( " TYPE:" + getChannelType().toString() );
@@ -137,7 +137,7 @@ public class IdentifierUpdateTDMA extends IdentifierUpdate
sb.append( mAccessType.name() );
sb.append( " BANDWIDTH:" + mBandwidth );
- sb.append( " SLOT/CARRIER:" + mSlotsPerCarrier );
+ sb.append( " TIMESLOTS PER CARRIER:" + mSlotsPerCarrier );
sb.append( " VOCODER:" + mVocoder.name() );
return sb.toString();
diff --git a/src/playlist/PlaylistManager.java b/src/playlist/PlaylistManager.java
index d0e19c83..6ca21ecf 100644
--- a/src/playlist/PlaylistManager.java
+++ b/src/playlist/PlaylistManager.java
@@ -89,6 +89,7 @@ public class PlaylistManager implements ChannelEventListener
@Override
public void receive(AliasEvent t)
{
+ //Save the playlist for all alias events
schedulePlaylistSave();
}
});
@@ -98,6 +99,7 @@ public class PlaylistManager implements ChannelEventListener
@Override
public void receive(ChannelMapEvent t)
{
+ //Save the playlist for all channel map events
schedulePlaylistSave();
}
});
@@ -107,7 +109,23 @@ public class PlaylistManager implements ChannelEventListener
@Override
public void receive(BroadcastEvent broadcastEvent)
{
- schedulePlaylistSave();
+ switch(broadcastEvent.getEvent())
+ {
+ case CONFIGURATION_ADD:
+ case CONFIGURATION_CHANGE:
+ case CONFIGURATION_DELETE:
+ schedulePlaylistSave();
+ break;
+ case BROADCASTER_ADD:
+ case BROADCASTER_QUEUE_CHANGE:
+ case BROADCASTER_STATE_CHANGE:
+ case BROADCASTER_STREAMED_COUNT_CHANGE:
+ case BROADCASTER_AGED_OFF_COUNT_CHANGE:
+ case BROADCASTER_DELETE:
+ default:
+ //Do nothing
+ break;
+ }
}
});
}
diff --git a/src/record/RecorderManager.java b/src/record/RecorderManager.java
index 871fe914..4d9789e0 100644
--- a/src/record/RecorderManager.java
+++ b/src/record/RecorderManager.java
@@ -25,87 +25,169 @@ import properties.SystemProperties;
import record.wave.ComplexBufferWaveRecorder;
import record.wave.RealBufferWaveRecorder;
import sample.Listener;
+import sample.OverflowableTransferQueue;
+import sample.real.IOverflowListener;
import util.ThreadPool;
import java.io.File;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
public class RecorderManager implements Listener
{
private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class);
public static final int AUDIO_SAMPLE_RATE = 8000;
+ public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
private Map mRecorders = new HashMap<>();
+ private OverflowableTransferQueue mAudioPacketQueue = new OverflowableTransferQueue<>(1000, 100);
+ private ScheduledFuture> mBufferProcessorFuture;
private boolean mCanStartNewRecorders = true;
/**
- * Manages all audio recording for all processing channels. Reconstructs audio streams and distributes channel
- * audio to each of the recorders, starting and stopping the recorders as needed.
+ * Audio recording manager. Monitors stream of audio packets produced by decoding channels and automatically starts
+ * audio recorders when the channel's metadata designates a call as recordable. Routes call audio to each recorder
+ * based on audio packet metadata. Recorders are shutdown when the channel sends an end-call audio packet
+ * indicating that the call is complete. A separate recording monitor periodically checks for idled recorders to
+ * be stopped for cases when the channel fails to send an end-call audio packet.
*/
public RecorderManager()
{
+ mAudioPacketQueue.setOverflowListener(new IOverflowListener()
+ {
+ @Override
+ public void sourceOverflow(boolean overflow)
+ {
+ if(overflow)
+ {
+ mLog.warn("overflow - audio packets will be dropped until recording catches up");
+ }
+ else
+ {
+ mLog.info("audio recorder packet processing has returned to normal");
+ }
+ }
+ });
+
+ mBufferProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferProcessor(), 0,
+ 1, TimeUnit.SECONDS);
}
+ /**
+ * Prepares this class for shutdown
+ */
public void dispose()
{
+ if(mBufferProcessorFuture != null)
+ {
+ mBufferProcessorFuture.cancel(true);
+ }
}
+ /**
+ * Primary ingest point for audio packets from all decoding channels
+ * @param audioPacket to process
+ */
@Override
public void receive(AudioPacket audioPacket)
{
- if(audioPacket.hasMetadata() &&
- audioPacket.getMetadata().isRecordable())
+ if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable())
{
- String identifier = audioPacket.getMetadata().getUniqueIdentifier();
+ mAudioPacketQueue.offer(audioPacket);
+ }
+ }
- if(mRecorders.containsKey(identifier))
+ /**
+ * Process any queued audio buffers and dispatch them to the audio recorders
+ */
+ private void processBuffers()
+ {
+ List audioPackets = new ArrayList<>();
+
+ mAudioPacketQueue.drainTo(audioPackets, 50);
+
+ while(!audioPackets.isEmpty())
+ {
+ for(AudioPacket audioPacket: audioPackets)
{
- RealBufferWaveRecorder recorder = mRecorders.get(identifier);
+ String identifier = audioPacket.getMetadata().getUniqueIdentifier();
- if(audioPacket.getType() == AudioPacket.Type.AUDIO)
+ if(mRecorders.containsKey(identifier))
{
- recorder.receive(audioPacket.getAudioBuffer());
- }
- else if(audioPacket.getType() == AudioPacket.Type.END)
- {
- RealBufferWaveRecorder finished = mRecorders.remove(identifier);
- finished.stop();
- }
- }
- else if(audioPacket.getType() == AudioPacket.Type.AUDIO)
- {
- if(mCanStartNewRecorders)
- {
- String filePrefix = getFilePrefix(audioPacket);
+ RealBufferWaveRecorder recorder = mRecorders.get(identifier);
- RealBufferWaveRecorder recorder = null;
-
- try
+ if(audioPacket.getType() == AudioPacket.Type.AUDIO)
{
- recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix);
-
- recorder.start(ThreadPool.SCHEDULED);
-
recorder.receive(audioPacket.getAudioBuffer());
- mRecorders.put(identifier, recorder);
}
- catch(Exception ioe)
+ else if(audioPacket.getType() == AudioPacket.Type.END)
{
- mCanStartNewRecorders = false;
+ RealBufferWaveRecorder finished = mRecorders.remove(identifier);
+ finished.stop();
+ }
+ }
+ else if(audioPacket.getType() == AudioPacket.Type.AUDIO)
+ {
+ if(mCanStartNewRecorders)
+ {
+ String filePrefix = getFilePrefix(audioPacket);
- mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " +
- "is disabled", ioe);
+ RealBufferWaveRecorder recorder = null;
- if(recorder != null)
+ try
{
- recorder.stop();
+ recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix);
+
+ recorder.start(ThreadPool.SCHEDULED);
+
+ recorder.receive(audioPacket.getAudioBuffer());
+ mRecorders.put(identifier, recorder);
+ }
+ catch(Exception ioe)
+ {
+ mCanStartNewRecorders = false;
+
+ mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " +
+ "is disabled", ioe);
+
+ if(recorder != null)
+ {
+ recorder.stop();
+ }
}
}
}
}
+
+ audioPackets.clear();
+ mAudioPacketQueue.drainTo(audioPackets, 50);
+ }
+ }
+
+ /**
+ * Removes recorders that have not received any new audio buffers in the last 6 seconds.
+ */
+ private void removeIdleRecorders()
+ {
+ Iterator> it = mRecorders.entrySet().iterator();
+
+ while(it.hasNext())
+ {
+ Map.Entry entry = it.next();
+
+ if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis())
+ {
+ mLog.info("Removing idle recorder [" + entry.getKey() + "]");
+ it.remove();
+ entry.getValue().stop();
+ }
}
}
@@ -150,4 +232,18 @@ public class RecorderManager implements Listener
return new ComplexBufferWaveRecorder(AUDIO_SAMPLE_RATE, sb.toString());
}
+
+ /**
+ * Processes queued audio packets and distributes to each of the audio recorders. Removes any idle recorders
+ * that have not been updated according to an idle threshold period
+ */
+ public class BufferProcessor implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ processBuffers();
+ removeIdleRecorders();
+ }
+ }
}
diff --git a/src/record/wave/RealBufferWaveRecorder.java b/src/record/wave/RealBufferWaveRecorder.java
index ac17988a..763c9ded 100644
--- a/src/record/wave/RealBufferWaveRecorder.java
+++ b/src/record/wave/RealBufferWaveRecorder.java
@@ -1,22 +1,33 @@
/*******************************************************************************
- * 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
+ * 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
+ *
******************************************************************************/
package record.wave;
+import module.Module;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sample.ConversionUtils;
+import sample.Listener;
+import sample.real.IFilteredRealBufferListener;
+import sample.real.RealBuffer;
+import util.TimeStamp;
+
+import javax.sound.sampled.AudioFormat;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -26,206 +37,175 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-import javax.sound.sampled.AudioFormat;
-
-import module.Module;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import sample.ConversionUtils;
-import sample.Listener;
-import sample.real.IFilteredRealBufferListener;
-import sample.real.RealBuffer;
-import util.TimeStamp;
-
/**
* WAVE audio recorder module for recording real sample buffers to a wave file
*/
-public class RealBufferWaveRecorder extends Module
- implements IFilteredRealBufferListener, Listener
+public class RealBufferWaveRecorder extends Module
+ implements IFilteredRealBufferListener, Listener
{
- private final static Logger mLog =
- LoggerFactory.getLogger( RealBufferWaveRecorder.class );
+ private final static Logger mLog = LoggerFactory.getLogger(RealBufferWaveRecorder.class);
private WaveWriter mWriter;
private String mFilePrefix;
private Path mFile;
- private AudioFormat mAudioFormat;
-
- private BufferProcessor mBufferProcessor;
- private ScheduledFuture> mProcessorHandle;
- private LinkedBlockingQueue mBuffers =
- new LinkedBlockingQueue<>( 500 );
- private long mLastBufferReceived;
-
- private AtomicBoolean mRunning = new AtomicBoolean();
-
- public RealBufferWaveRecorder( int sampleRate, String filePrefix )
- {
- mAudioFormat = new AudioFormat( sampleRate, //SampleRate
- 16, //Sample Size
- 1, //Channels
- true, //Signed
- false ); //Little Endian
+ private AudioFormat mAudioFormat;
- mFilePrefix = filePrefix;
- }
+ private BufferProcessor mBufferProcessor;
+ private ScheduledFuture> mProcessorHandle;
+ private LinkedBlockingQueue mBuffers = new LinkedBlockingQueue<>(500);
+ private long mLastBufferReceived;
- /**
- * Timestamp of when the latest buffer was received by this recorder
- */
- public long getLastBufferReceived()
- {
- return mLastBufferReceived;
- }
+ private AtomicBoolean mRunning = new AtomicBoolean();
- public Path getFile()
- {
- return mFile;
- }
-
- public void start( ScheduledExecutorService executor )
- {
- if( mRunning.compareAndSet( false, true ) )
- {
- if( mBufferProcessor == null )
- {
- mBufferProcessor = new BufferProcessor();
- }
+ public RealBufferWaveRecorder(int sampleRate, String filePrefix)
+ {
+ mAudioFormat = new AudioFormat(sampleRate, //SampleRate
+ 16, //Sample Size
+ 1, //Channels
+ true, //Signed
+ false); //Little Endian
- try
- {
- StringBuilder sb = new StringBuilder();
- sb.append( mFilePrefix );
- sb.append( "_" );
- sb.append( TimeStamp.getLongTimeStamp( "_" ) );
- sb.append( ".wav" );
-
- mFile = Paths.get( sb.toString() );
+ mFilePrefix = filePrefix;
+ }
- mWriter = new WaveWriter( mAudioFormat, mFile );
+ /**
+ * Indicates if the recorder is currently running.
+ */
+ public boolean isRunning()
+ {
+ return mRunning.get();
+ }
+
+ /**
+ * Timestamp of when the latest buffer was received by this recorder
+ */
+ public long getLastBufferReceived()
+ {
+ return mLastBufferReceived;
+ }
+
+ public Path getFile()
+ {
+ return mFile;
+ }
+
+ public void start(ScheduledExecutorService executor)
+ {
+ if(mRunning.compareAndSet(false, true))
+ {
+ if(mBufferProcessor == null)
+ {
+ mBufferProcessor = new BufferProcessor();
+ }
+
+ try
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mFilePrefix);
+ sb.append("_");
+ sb.append(TimeStamp.getLongTimeStamp("_"));
+ sb.append(".wav");
+
+ mFile = Paths.get(sb.toString());
+
+ mWriter = new WaveWriter(mAudioFormat, mFile);
/* Schedule the processor to run every 500 milliseconds */
- mProcessorHandle = executor.scheduleAtFixedRate(
- mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS );
- }
- catch( IOException io )
- {
- mLog.error( "Error starting real buffer recorder", io );
- }
- }
- }
-
- public void stop()
- {
- /* Signal the buffer processor to end with a poison pill */
- receive( new PoisonPill() );
-
- mRunning.set( false );
- }
-
- @Override
- public void receive( RealBuffer buffer )
- {
- if( mRunning.get() )
- {
- boolean success = mBuffers.offer( buffer );
-
- if( !success )
- {
- mLog.error( "recorder buffer overflow - purging [" +
- mFile.toFile().getAbsolutePath() + "]" );
-
- mBuffers.clear();
- }
-
- mLastBufferReceived = System.currentTimeMillis();
- }
- }
-
- @Override
- public Listener getFilteredRealBufferListener()
- {
- return this;
- }
-
- @Override
- public void dispose()
- {
- stop();
- }
-
- @Override
- public void reset()
- {
- }
-
- public class BufferProcessor implements Runnable
- {
- public void run()
- {
- try
- {
- RealBuffer buffer = mBuffers.poll();
-
- while( buffer != null )
- {
- if( buffer instanceof PoisonPill )
- {
- buffer = null;
-
- mBuffers.clear();
-
- if( mWriter != null )
- {
- try
- {
- mWriter.close();
- }
- catch( IOException io )
- {
- mLog.error( "Error stopping complex wave recorder [" +
- getFile() + "]", io );
- }
- }
-
- if( mProcessorHandle != null )
- {
- mProcessorHandle.cancel( true );
- }
-
- mProcessorHandle = null;
- mWriter = null;
- mFile = null;
- }
- else
- {
- mWriter.write( ConversionUtils.convertToSigned16BitSamples( buffer ) );
- buffer = mBuffers.poll();
- }
- }
+ mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
}
- catch ( IOException ioe )
- {
- /* Stop this module if/when we get an IO exception */
- mBuffers.clear();
- stop();
-
- mLog.error( "IOException while trying to write to the wave "
- + "writer", ioe );
- }
- }
+ catch(IOException io)
+ {
+ mLog.error("Error starting real buffer recorder", io);
+ }
+ }
+ }
+
+ public void stop()
+ {
+ if(mRunning.compareAndSet(true, false))
+ {
+ try
+ {
+ write();
+
+ if(mWriter != null)
+ {
+ mWriter.close();
+ mWriter = null;
+ }
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Error writing final audio buffers to recording during shutdown", ioe);
+ }
+ }
+ }
+
+ @Override
+ public void receive(RealBuffer buffer)
+ {
+ if(mRunning.get())
+ {
+ boolean success = mBuffers.offer(buffer);
+
+ if(!success)
+ {
+ mLog.error("recorder buffer overflow - purging [" + mFile.toFile().getAbsolutePath() + "]");
+ mBuffers.clear();
+ }
+
+ mLastBufferReceived = System.currentTimeMillis();
+ }
+ }
+
+ @Override
+ public Listener getFilteredRealBufferListener()
+ {
+ return this;
+ }
+
+ @Override
+ public void dispose()
+ {
+ stop();
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ /**
+ * Writes all audio currently in the queue to the file
+ * @throws IOException if there are any errors writing the audio
+ */
+ private void write() throws IOException
+ {
+ RealBuffer buffer = mBuffers.poll();
+
+ while(buffer != null)
+ {
+ mWriter.write(ConversionUtils.convertToSigned16BitSamples(buffer));
+ buffer = mBuffers.poll();
+ }
+ }
+
+ public class BufferProcessor implements Runnable
+ {
+ public void run()
+ {
+ try
+ {
+ write();
+ }
+ catch(IOException ioe)
+ {
+ /* Stop this module if/when we get an IO exception */
+ mBuffers.clear();
+ stop();
+
+ mLog.error("IO Exception while trying to write to the wave writer", ioe);
+ }
+ }
}
-
- /**
- * This is used as a sentinel value to signal the buffer processor to end
- */
- public class PoisonPill extends RealBuffer
- {
- public PoisonPill()
- {
- super( new float[ 1 ] );
- }
- }
}
diff --git a/src/record/wave/WaveWriter.java b/src/record/wave/WaveWriter.java
index a7733e69..645cbf5c 100644
--- a/src/record/wave/WaveWriter.java
+++ b/src/record/wave/WaveWriter.java
@@ -1,19 +1,20 @@
/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
+ * 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
+ *
******************************************************************************/
package record.wave;
@@ -34,10 +35,7 @@ import javax.sound.sampled.AudioFormat;
public class WaveWriter implements AutoCloseable
{
-// private final static Logger mLog = LoggerFactory.getLogger( WaveWriter.class );
-
- private static final Pattern FILENAME_PATTERN =
- Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
+ private static final Pattern FILENAME_PATTERN = Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE;
private AudioFormat mAudioFormat;
@@ -58,8 +56,7 @@ public class WaveWriter implements AutoCloseable
* @param maxSize - maximum file size ( range: 1 - 4,294,967,294 bytes )
* @throws IOException - if there are any IO issues
*/
- public WaveWriter( AudioFormat format, Path file, long maxSize )
- throws IOException
+ public WaveWriter( AudioFormat format, Path file, long maxSize ) throws IOException
{
Validate.isTrue(format != null);
Validate.isTrue( file != null );
@@ -102,15 +99,11 @@ public class WaveWriter implements AutoCloseable
while( Files.exists( mFile ) )
{
- mFile = Paths.get( mFile.toFile().getAbsolutePath()
- .replace( ".wav", "_" + version + ".wav" ) );
-
+ mFile = Paths.get( mFile.toFile().getAbsolutePath().replace( ".wav", "_" + version + ".wav" ) );
version++;
}
- Files.createFile( mFile );
-
- mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE ) );
+ mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW ) );
ByteBuffer header = WaveUtils.getWaveHeader( mAudioFormat );
@@ -129,9 +122,18 @@ public class WaveWriter implements AutoCloseable
{
mFileChannel.force( true );
mFileChannel.close();
+ mFileChannel = null;
}
-
- /**
+
+ @Override
+ protected void finalize() throws IOException
+ {
+ mFileChannel.force( true );
+ mFileChannel.close();
+ mFileChannel = null;
+ }
+
+ /**
* Writes the buffer contents to the file. Assumes that the buffer is full
* and the first byte of data is at position 0.
*/
diff --git a/src/sample/Broadcaster.java b/src/sample/Broadcaster.java
index 4fc1f819..d4f9cce1 100644
--- a/src/sample/Broadcaster.java
+++ b/src/sample/Broadcaster.java
@@ -58,6 +58,11 @@ public class Broadcaster implements Listener
public void addListener( Listener listener )
{
+ if(listener == null)
+ {
+ throw new IllegalArgumentException("Listener cannot be null");
+ }
+
mListeners.add( listener );
}
diff --git a/src/source/Source.java b/src/source/Source.java
index 2de310b8..932c2749 100644
--- a/src/source/Source.java
+++ b/src/source/Source.java
@@ -20,11 +20,13 @@ package source;
import module.Module;
import sample.SampleType;
import sample.real.IOverflowListener;
+import source.tuner.frequency.IFrequencyChangeListener;
+import source.tuner.frequency.IFrequencyChangeProvider;
/**
* Abstract class to define the minimum functionality of a sample data provider.
*/
-public abstract class Source extends Module
+public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider
{
protected SampleType mSampleType;
protected IOverflowListener mOverflowListener;
diff --git a/src/source/mixer/RealMixerSource.java b/src/source/mixer/RealMixerSource.java
index 7ea8dd7b..cfc96997 100644
--- a/src/source/mixer/RealMixerSource.java
+++ b/src/source/mixer/RealMixerSource.java
@@ -36,11 +36,11 @@ import sample.real.RealBuffer;
import source.RealSource;
import source.SourceException;
import dsp.gain.AutomaticGainControl;
+import source.tuner.frequency.FrequencyChangeEvent;
public class RealMixerSource extends RealSource
{
- private final static Logger mLog =
- LoggerFactory.getLogger( RealMixerSource.class );
+ private final static Logger mLog = LoggerFactory.getLogger( RealMixerSource.class );
private long mFrequency = 0;
private int mBufferSize = 16384;
@@ -48,13 +48,11 @@ public class RealMixerSource extends RealSource
private BufferReader mBufferReader = new BufferReader();
private TargetDataLine mTargetDataLine;
private AudioFormat mAudioFormat;
- private String mDisplayName;
private int mBytesPerFrame = 0;
private ISampleAdapter mSampleAdapter;
private AutomaticGainControl mAGC = new AutomaticGainControl();
- CopyOnWriteArrayList> mSampleListeners =
- new CopyOnWriteArrayList>();
+ CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>();
/**
* Real Mixer Source - constructs a reader on the mixer/sound card target
@@ -80,8 +78,27 @@ public class RealMixerSource extends RealSource
mAudioFormat = format;
mSampleAdapter = sampleAdapter;
}
-
- @Override
+
+ @Override
+ public void setFrequencyChangeListener(Listener listener)
+ {
+ //Not implemented
+ }
+
+ @Override
+ public void removeFrequencyChangeListener()
+ {
+ //Not implemented
+ }
+
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ //Not implemented
+ return null;
+ }
+
+ @Override
public void reset()
{
}
@@ -133,23 +150,15 @@ public class RealMixerSource extends RealSource
* buffer, otherwise send him a copy of the buffer */
if( it.hasNext() )
{
-// next.receive( mAGC.process( samples.copyOf() ) );
next.receive( samples.copyOf() );
}
else
{
-// next.receive( mAGC.process( samples ) );
next.receive( samples );
}
}
}
- @Override
- public String toString()
- {
- return mDisplayName;
- }
-
public int getSampleRate()
{
if( mTargetDataLine != null )
diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java
index fa0cef46..7556e7b5 100644
--- a/src/source/tuner/TunerChannelSource.java
+++ b/src/source/tuner/TunerChannelSource.java
@@ -34,7 +34,9 @@ import source.ComplexSource;
import source.SourceException;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event;
+import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProcessor;
+import source.tuner.frequency.IFrequencyChangeProvider;
import java.util.ArrayList;
import java.util.List;
@@ -65,6 +67,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private ComplexPrimeCICDecimate mDecimationFilter;
private Listener mListener;
private IFrequencyChangeProcessor mFrequencyChangeProcessor;
+ private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
private ScheduledFuture> mTaskHandle;
private long mTunerFrequency = 0;
@@ -75,7 +78,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private AtomicBoolean mRunning = new AtomicBoolean();
private boolean mExpended = false;
- private int mCount;
/**
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
@@ -109,7 +111,8 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
/* Fire a sample rate change event to setup the decimation chain */
- frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate()));
+ frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
+ mTuner.getTunerController().getSampleRate()));
}
/**
@@ -131,6 +134,10 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
if(mRunning.compareAndSet(false, true))
{
+ //Broadcast current frequency and sample rate settings so all downstream components are aware
+ mDownstreamFrequencyEventProcessor.broadcastCurrentFrequency();
+ mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
+
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
@@ -184,6 +191,20 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
}
}
+ /**
+ * Changes the frequency correction value and broadcasts the change to the registered downstream listener.
+ * @param correction current frequency correction value.
+ */
+ private void setFrequencyCorrection(int correction)
+ {
+ mChannelFrequencyCorrection = correction;
+
+ updateMixerFrequencyOffset();
+
+ mDownstreamFrequencyEventProcessor.broadcast(
+ new FrequencyChangeEvent( Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
+ }
+
public Tuner getTuner()
{
return mTuner;
@@ -242,50 +263,49 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
case NOTIFICATION_FREQUENCY_CHANGE:
mTunerFrequency = event.getValue().longValue();
updateMixerFrequencyOffset();
- break;
- case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
- mChannelFrequencyCorrection = event.getValue().intValue();
- updateMixerFrequencyOffset();
-
- if(mFrequencyChangeProcessor != null)
- {
- mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
- Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
- }
+ //Reset frequency correction so that downstream components can recalculate the value
+ setFrequencyCorrection(0);
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
int sampleRate = event.getValue().intValue();
-
- if(mTunerSampleRate != sampleRate)
- {
- mMixer.setSampleRate(sampleRate);
-
- /* Get new decimation filter */
- mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
- CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
-
- /* re-add the original output listener */
- mDecimationFilter.setListener(mListener);
-
- mTunerSampleRate = sampleRate;
- }
+ setSampleRate(sampleRate);
break;
default:
break;
}
}
+ /**
+ * Updates the sample rate to the requested value and notifies any downstream components of the change
+ * @param sampleRate to set
+ */
+ private void setSampleRate(int sampleRate)
+ {
+ if(mTunerSampleRate != sampleRate)
+ {
+ mMixer.setSampleRate(sampleRate);
+
+ /* Get new decimation filter */
+ mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
+ CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
+
+ /* re-add the original output listener */
+ mDecimationFilter.setListener(mListener);
+
+ mTunerSampleRate = sampleRate;
+
+ mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
+ }
+ }
+
/**
* Calculates the local mixer frequency offset from the tuned frequency,
* channel's requested frequency, and channel frequency correction.
*/
private void updateMixerFrequencyOffset()
{
- long offset = mTunerFrequency -
- mTunerChannel.getFrequency() -
- mChannelFrequencyCorrection;
-
+ long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection;
mMixer.setFrequency(offset);
}
@@ -299,6 +319,140 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
return mTunerChannel.getFrequency();
}
+ /**
+ * Implements IFrequencyChangeProvider to enable this source to broadcast frequency change events to downstream
+ * listeners.
+ *
+ * @param listener to receive downstream events
+ */
+ @Override
+ public void setFrequencyChangeListener(Listener listener)
+ {
+ mDownstreamFrequencyEventProcessor.setFrequencyChangeListener(listener);
+ }
+
+ /**
+ * Implements IFrequencyChangeProvider to remove the frequency change listener from receiving down-stream frequency
+ * change events.
+ */
+ @Override
+ public void removeFrequencyChangeListener()
+ {
+ mDownstreamFrequencyEventProcessor.removeFrequencyChangeListener();
+ }
+
+ /**
+ * Implements IFrequencyChangeListener to receive frequency change events containing requests from downstream
+ * listeners to change frequency values.
+ * @return listener
+ */
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener();
+ }
+
+ /**
+ * Managers frequency change requests and notifications from/to any downstream component. Downstream
+ * components are those that receive samples from this tuner channel source. These downstream components will be
+ * notified of any frequency or sample rate change events and will also be able to request frequency correction
+ * updates.
+ */
+ public class DownstreamProcessor implements IFrequencyChangeListener, IFrequencyChangeProvider,
+ Listener
+ {
+ //Listener to receive downstream events
+ private Listener mListener;
+
+ /**
+ * Broadcasts the frequency change event to the downstream frequency change listener
+ * @param event to broadcast
+ */
+ public void broadcast(FrequencyChangeEvent event)
+ {
+ if(mListener != null)
+ {
+ mListener.receive(event);
+ }
+ }
+
+ /**
+ * Broadcasts the current frequency of this tuner channel source to the downstream listener
+ */
+ public void broadcastCurrentFrequency()
+ {
+ try
+ {
+ long frequency = getFrequency();
+ broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency));
+ }
+ catch(SourceException se)
+ {
+ mLog.error("Error obtaining frequency from tuner to broadcast downstream");
+ }
+ }
+
+ /**
+ * Broadcasts the current decimated sample rate of this tuner channel source
+ */
+ public void broadcastCurrentSampleRate()
+ {
+ try
+ {
+ //Note: downstream sample rate is currently a fixed value -- it will change in the future
+ broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, getSampleRate()));
+ }
+ catch(SourceException se)
+ {
+ mLog.error("Error obtaining sample rate from tuner to broadcast downstream");
+ }
+ }
+
+ /**
+ * Sets the downstream listener to receive frequency change events from this tuner channel source
+ * @param listener to receive events
+ */
+ @Override
+ public void setFrequencyChangeListener(Listener listener)
+ {
+ mListener = listener;
+ }
+
+ /**
+ * Removes the downstream listener from receiving frequency change events.
+ */
+ @Override
+ public void removeFrequencyChangeListener()
+ {
+ mListener = null;
+ }
+
+ /**
+ * Listener for receiving frequency change events from downstream components
+ */
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ return this;
+ }
+
+ /**
+ * Processes frequency change events from downstream components.
+ * @param event to process
+ */
+ @Override
+ public void receive(FrequencyChangeEvent event)
+ {
+ switch(event.getEvent())
+ {
+ //Frequency correction requests are the only change requests supported from downstream components
+ case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
+ setFrequencyCorrection(event.getValue().intValue());
+ break;
+ }
+ }
+ }
+
/**
* Decimates an inbound buffer of I/Q samples from the source down to the
* standard 48000 channel sample rate
diff --git a/src/source/tuner/frequency/IFrequencyChangeProvider.java b/src/source/tuner/frequency/IFrequencyChangeProvider.java
index 919126e7..354e9af9 100644
--- a/src/source/tuner/frequency/IFrequencyChangeProvider.java
+++ b/src/source/tuner/frequency/IFrequencyChangeProvider.java
@@ -1,9 +1,30 @@
+/*******************************************************************************
+ * 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
+ *
+ ******************************************************************************/
package source.tuner.frequency;
import sample.Listener;
+/**
+ * Provider of Frequency Change Events
+ */
public interface IFrequencyChangeProvider
{
- public void setFrequencyChangeListener( Listener listener );
- public void removeFrequencyChangeListener();
+ void setFrequencyChangeListener( Listener listener );
+ void removeFrequencyChangeListener();
}
diff --git a/src/source/wave/ComplexWaveSource.java b/src/source/wave/ComplexWaveSource.java
index efa8b14b..8d80ce1d 100644
--- a/src/source/wave/ComplexWaveSource.java
+++ b/src/source/wave/ComplexWaveSource.java
@@ -1,19 +1,20 @@
/*******************************************************************************
- * 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
+ * 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
+ *
******************************************************************************/
package source.wave;
@@ -36,6 +37,7 @@ import sample.complex.ComplexBuffer;
import source.ComplexSource;
import source.IControllableFileSource;
import source.IFrameLocationListener;
+import source.tuner.frequency.FrequencyChangeEvent;
public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
{
@@ -54,8 +56,27 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
{
mFile = file;
}
-
- @Override
+
+ @Override
+ public void setFrequencyChangeListener(Listener listener)
+ {
+ //Not implemented
+ }
+
+ @Override
+ public void removeFrequencyChangeListener()
+ {
+ //Not implemented
+ }
+
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ //Not implemented
+ return null;
+ }
+
+ @Override
public void reset()
{
stop();
diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java
index 06e4fc25..4b9b120b 100644
--- a/src/source/wave/RealWaveSource.java
+++ b/src/source/wave/RealWaveSource.java
@@ -36,6 +36,7 @@ import sample.real.RealBuffer;
import source.IControllableFileSource;
import source.IFrameLocationListener;
import source.RealSource;
+import source.tuner.frequency.FrequencyChangeEvent;
public class RealWaveSource extends RealSource
implements IControllableFileSource, AutoCloseable
@@ -55,8 +56,27 @@ public class RealWaveSource extends RealSource
{
mFile = file;
}
-
-
+
+ @Override
+ public void setFrequencyChangeListener(Listener listener)
+ {
+ //Not implemented
+ }
+
+ @Override
+ public void removeFrequencyChangeListener()
+ {
+ //Not implemented
+ }
+
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ //Not implemented
+ return null;
+ }
+
+
@Override
public void reset()
{
diff --git a/src/spectrum/OverlayPanel.java b/src/spectrum/OverlayPanel.java
index ae0c23fc..b064b804 100644
--- a/src/spectrum/OverlayPanel.java
+++ b/src/spectrum/OverlayPanel.java
@@ -50,6 +50,7 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class);
+ private final DecimalFormat PPM_FORMATTER = new DecimalFormat( "#.0" );
private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
@@ -187,7 +188,6 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
* 6 64x Zoom
*
* @param zoom level, 0 - 6.
- * @param offset into the FFT bins for start zoom window
*/
public void setZoom(int zoom)
{
@@ -412,20 +412,41 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
}
/**
- * Draws a vertical line at the xaxis
+ * Draws the Automatic Frequency Control (AFC) channel center offset
*/
- private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis)
+ private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis, double bandwidth,
+ int correction, long frequency)
{
double height = getSize().getHeight() - mSpectrumInset;
- double lineHeight = height * 0.75d;
+ double verticalAxisTop = height * 0.88d;
+ double verticalAxisBottom = height * 0.98d;
+
+ double halfBandwidth = bandwidth / 2.0;
+ double errorEdgeStart = errorAxis - halfBandwidth;
+ double errorEdgeStop = errorAxis + halfBandwidth;
graphics.setColor(Color.YELLOW);
- //Vertical frequency error line
- graphics.draw(new Line2D.Double(errorAxis, lineHeight, errorAxis, height - 1.0d));
+ //Horizontal line connecting frequency and error line
+ graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisBottom, errorEdgeStop, verticalAxisBottom));
- //Horizontal line connecting frequency and error lines
- graphics.draw(new Line2D.Double(frequencyAxis, lineHeight, errorAxis, lineHeight));
+ //Vertical band edge lines
+ graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisTop, errorEdgeStart, verticalAxisBottom));
+ graphics.draw(new Line2D.Double(errorEdgeStop, verticalAxisTop, errorEdgeStop, verticalAxisBottom));
+
+ double ppm = (double)correction / ((double)frequency / 1E6d);
+
+ String label = "PPM " + PPM_FORMATTER.format(ppm) ;
+
+ FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont());
+
+ Rectangle2D rect = fontMetrics.getStringBounds(label, graphics);
+
+ //Only render the correction value label if the spacing is large enough
+ if(rect.getWidth() <= bandwidth && rect.getHeight() * 5 <= height)
+ {
+ graphics.drawString(label, (float)(errorEdgeStart + 1.0), (float)(verticalAxisBottom - 2.0));
+ }
}
/**
@@ -564,7 +585,8 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
{
long error = frequency + correction;
- drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error));
+ drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error), width, correction,
+ tunerChannel.getFrequency());
}
}
}
diff --git a/src/spectrum/SpectrumPanel.java b/src/spectrum/SpectrumPanel.java
index d0cb7686..156ab50f 100644
--- a/src/spectrum/SpectrumPanel.java
+++ b/src/spectrum/SpectrumPanel.java
@@ -1,487 +1,483 @@
/*******************************************************************************
- * 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
+ * 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
+ *
******************************************************************************/
package spectrum;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.GradientPaint;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.geom.GeneralPath;
-import java.awt.geom.Line2D;
-import java.util.Arrays;
-
-import javax.swing.JPanel;
-
-import org.apache.commons.lang3.Validate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import settings.ColorSetting;
-import settings.ColorSetting.ColorSettingName;
-import settings.Setting;
-import settings.SettingChangeListener;
-import settings.SettingsManager;
import dsp.filter.smoothing.GaussianSmoothingFilter;
import dsp.filter.smoothing.NoSmoothingFilter;
import dsp.filter.smoothing.RectangularSmoothingFilter;
import dsp.filter.smoothing.SmoothingFilter;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
import dsp.filter.smoothing.TriangularSmoothingFilter;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import settings.ColorSetting;
+import settings.ColorSetting.ColorSettingName;
+import settings.Setting;
+import settings.SettingChangeListener;
+import settings.SettingsManager;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.util.Arrays;
public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster
{
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 1L;
- private final static Logger mLog = LoggerFactory.getLogger( SpectrumPanel.class );
-
- private static final RenderingHints RENDERING_HINTS =
- new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
+ private final static Logger mLog = LoggerFactory.getLogger(SpectrumPanel.class);
- static
- {
- RENDERING_HINTS.put( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
- }
-
- private Color mColorSpectrumBackground;
- private Color mColorSpectrumGradientTop;
- private Color mColorSpectrumGradientBottom;
- private Color mColorSpectrumLine;
+ private static final RenderingHints RENDERING_HINTS =
+ new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- //Defines the panel inset along the bottom for frequency display
- private float mSpectrumInset = 20.0f;
-
- //Current DFT output bins in dB
- private float[] mDisplayFFTBins = new float[ 1 ];
-
- //Averaging across multiple DFT result sets
- private int mAveraging = 1;
-
- //Smoothing across bins in the same DFT result set
- private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
-
- //Reference dB value set according to the source sample size
- private float mDBScale;
-
- private int mZoom = 0;
- private int mZoomWindowOffset = 0;;
-
- private SettingsManager mSettingsManager;
-
- public SpectrumPanel( SettingsManager settingsManager )
+ static
{
- mSettingsManager = settingsManager;
-
- if( mSettingsManager != null )
- {
- mSettingsManager.addListener( this );
- }
-
- setSampleSize( 16.0 );
-
- mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT );
-
- getColors();
-
- setAveraging( mAveraging );
+ RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
-
- public void dispose()
- {
- if( mSettingsManager != null )
- {
- mSettingsManager.removeListener( this );
- }
-
- mSettingsManager = null;
- }
-
+
+ private Color mColorSpectrumBackground;
+ private Color mColorSpectrumGradientTop;
+ private Color mColorSpectrumGradientBottom;
+ private Color mColorSpectrumLine;
+
+ //Defines the panel inset along the bottom for frequency display
+ private float mSpectrumInset = 20.0f;
+
+ //Current DFT output bins in dB
+ private float[] mDisplayFFTBins = new float[1];
+
+ //Averaging across multiple DFT result sets
+ private int mAveraging = 4;
+
+ //Smoothing across bins in the same DFT result set
+ private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
+
+ //Reference dB value set according to the source sample size
+ private float mDBScale;
+
+ private int mZoom = 0;
+ private int mZoomWindowOffset = 0;
+ ;
+
+ private SettingsManager mSettingsManager;
+
+ public SpectrumPanel(SettingsManager settingsManager)
+ {
+ mSettingsManager = settingsManager;
+
+ if(mSettingsManager != null)
+ {
+ mSettingsManager.addListener(this);
+ }
+
+ setSampleSize(16.0);
+
+ mSmoothingFilter.setPointSize(SmoothingFilter.SMOOTHING_DEFAULT);
+
+ getColors();
+
+ setAveraging(mAveraging);
+ }
+
+ public void dispose()
+ {
+ if(mSettingsManager != null)
+ {
+ mSettingsManager.removeListener(this);
+ }
+
+ mSettingsManager = null;
+ }
+
/**
* DFTResultsListener interface for receiving the processed data
* to display
*/
- public void receive( float[] currentFFTBins )
+ public void receive(float[] currentFFTBins)
{
- //Prevent arrays of NaN values from being rendered. The first few
- //DFT result sets on startup will contain NaN values
- if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) )
- {
- currentFFTBins = new float[ currentFFTBins.length ];
- }
+ //Prevent arrays of NaN values from being rendered. The first few
+ //DFT result sets on startup will contain NaN values
+ if(Float.isInfinite(currentFFTBins[0]) || Float.isNaN(currentFFTBins[0]))
+ {
+ currentFFTBins = new float[currentFFTBins.length];
+ }
- //Construct and/or resize our DFT results variables
- if( mDisplayFFTBins == null ||
- mDisplayFFTBins.length != currentFFTBins.length )
- {
- mDisplayFFTBins = currentFFTBins;
- }
+ //Construct and/or resize our DFT results variables
+ if(mDisplayFFTBins == null ||
+ mDisplayFFTBins.length != currentFFTBins.length)
+ {
+ mDisplayFFTBins = currentFFTBins;
+ }
- //Apply smoothing across the bins of the DFT results
- float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins );
+ //Apply smoothing across the bins of the DFT results
+ float[] smoothedBins = mSmoothingFilter.filter(currentFFTBins);
- //Apply averaging over multiple DFT output frames
- if( mAveraging > 1 )
- {
- float gain = 1.0f / (float)mAveraging;
-
- for( int x = 0; x < mDisplayFFTBins.length; x++ )
- {
- mDisplayFFTBins[ x ] +=
- ( smoothedBins[ x ] - mDisplayFFTBins[ x ] ) * gain;
- }
- }
- else
- {
- mDisplayFFTBins = smoothedBins;
- }
-
- repaint();
+ //Apply averaging over multiple DFT output frames
+ if(mAveraging > 1)
+ {
+ float gain = 1.0f / (float)mAveraging;
+
+ for(int x = 0; x < mDisplayFFTBins.length; x++)
+ {
+ mDisplayFFTBins[x] +=
+ (smoothedBins[x] - mDisplayFFTBins[x]) * gain;
+ }
+ }
+ else
+ {
+ mDisplayFFTBins = smoothedBins;
+ }
+
+ repaint();
}
@Override
- public void paintComponent( Graphics g )
+ public void paintComponent(Graphics g)
{
- super.paintComponent( g );
-
- Graphics2D graphics = (Graphics2D) g;
+ super.paintComponent(g);
- graphics.setBackground( mColorSpectrumBackground );
+ Graphics2D graphics = (Graphics2D)g;
- graphics.setRenderingHints( RENDERING_HINTS );
-
- drawSpectrum( graphics );
+ graphics.setBackground(mColorSpectrumBackground);
+
+ graphics.setRenderingHints(RENDERING_HINTS);
+
+ drawSpectrum(graphics);
}
-
+
/**
* Draws the current fft spectrum with a line and a gradient fill.
*/
- private void drawSpectrum( Graphics2D graphics )
+ private void drawSpectrum(Graphics2D graphics)
{
- Dimension size = getSize();
+ Dimension size = getSize();
- //Draw the background
- Rectangle background = new Rectangle( 0, 0, size.width, size.height );
- graphics.setColor( mColorSpectrumBackground );
- graphics.draw( background );
- graphics.fill( background );
+ //Draw the background
+ Rectangle background = new Rectangle(0, 0, size.width, size.height);
+ graphics.setColor(mColorSpectrumBackground);
+ graphics.draw(background);
+ graphics.fill(background);
- //Define the gradient
- GradientPaint gradient = new GradientPaint( 0,
- ( getSize().height - mSpectrumInset ) / 2,
- mColorSpectrumGradientTop,
- 0,
- getSize().height,
- mColorSpectrumGradientBottom );
-
- graphics.setBackground( mColorSpectrumBackground );
-
- GeneralPath spectrumShape = new GeneralPath();
+ //Define the gradient
+ GradientPaint gradient = new GradientPaint(0,
+ (getSize().height - mSpectrumInset) / 2,
+ mColorSpectrumGradientTop,
+ 0,
+ getSize().height,
+ mColorSpectrumGradientBottom);
- //Start at the lower right inset point
- spectrumShape.moveTo( size.getWidth(),
- size.getHeight() - mSpectrumInset );
-
- //Draw to the lower left
- spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset );
+ graphics.setBackground(mColorSpectrumBackground);
- float[] bins = getBins();
-
- //If we have FFT data to display ...
- if( bins != null )
- {
- float insideHeight = size.height - mSpectrumInset;
+ GeneralPath spectrumShape = new GeneralPath();
- float scalor = insideHeight / -mDBScale;
+ //Start at the lower right inset point
+ spectrumShape.moveTo(size.getWidth(),
+ size.getHeight() - mSpectrumInset);
+
+ //Draw to the lower left
+ spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
+
+ float[] bins = getBins();
+
+ //If we have FFT data to display ...
+ if(bins != null)
+ {
+ float insideHeight = size.height - mSpectrumInset;
+
+ float scalor = insideHeight / -mDBScale;
/* Calculate based on bin size - 1, since bin 0 is rendered at zero
- * and the last bin is rendered at the width */
- float binSize = (float)size.width / ( (float)( bins.length ) );
+ * and the last bin is rendered at the width */
+ float binSize = (float)size.width / ((float)(bins.length));
- for( int x = 0; x < bins.length; x++ )
- {
- float height;
-
- height = bins[ x ] * scalor;
-
- if( height > insideHeight )
- {
- height = insideHeight;
- }
-
- if( height < 0 )
- {
- height = 0;
- }
-
- float xAxis = (float)x * binSize;
+ for(int x = 0; x < bins.length; x++)
+ {
+ float height;
- spectrumShape.lineTo( xAxis, height );
- }
- }
- //Otherwise show an empty spectrum
- else
- {
- //Draw Left Size
- graphics.setPaint( gradient );
- spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset );
- //Draw Middle
- spectrumShape.lineTo( size.getWidth(),
- size.getHeight() - mSpectrumInset );
- }
-
- //Draw Right Side
- spectrumShape.lineTo( size.getWidth(),
- size.getHeight() - mSpectrumInset );
-
- graphics.setPaint( gradient );
- graphics.draw( spectrumShape );
- graphics.fill( spectrumShape );
-
- graphics.setPaint( mColorSpectrumLine );
+ height = bins[x] * scalor;
- //Draw the bottom line under the spectrum
- graphics.draw( new Line2D.Float( 0,
- size.height - mSpectrumInset,
- size.width,
- size.height - mSpectrumInset ) );
+ if(height > insideHeight)
+ {
+ height = insideHeight;
+ }
+
+ if(height < 0)
+ {
+ height = 0;
+ }
+
+ float xAxis = (float)x * binSize;
+
+ spectrumShape.lineTo(xAxis, height);
+ }
+ }
+ //Otherwise show an empty spectrum
+ else
+ {
+ //Draw Left Size
+ graphics.setPaint(gradient);
+ spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
+ //Draw Middle
+ spectrumShape.lineTo(size.getWidth(),
+ size.getHeight() - mSpectrumInset);
+ }
+
+ //Draw Right Side
+ spectrumShape.lineTo(size.getWidth(),
+ size.getHeight() - mSpectrumInset);
+
+ graphics.setPaint(gradient);
+ graphics.draw(spectrumShape);
+ graphics.fill(spectrumShape);
+
+ graphics.setPaint(mColorSpectrumLine);
+
+ //Draw the bottom line under the spectrum
+ graphics.draw(new Line2D.Float(0,
+ size.height - mSpectrumInset,
+ size.width,
+ size.height - mSpectrumInset));
}
/**
* Sets the current zoom level
- *
+ *
* 0 No Zoom
* 1 2x Zoom
* 2 4x Zoom
* 3 8x Zoom
* 4 16x Zoom
* 5 32x Zoom
- *
- * @param zoom level, 0 - 5.
+ * 6 64x Zoom
+ *
+ * @param zoom level, 0 - 6.
*/
- public void setZoom( int zoom )
+ public void setZoom(int zoom)
{
- Validate.isTrue( 0 <= zoom && zoom <= 5 );
-
- mZoom = zoom;
+ Validate.isTrue(0 <= zoom && zoom <= 6, "Unrecognized Zoom Level: " + zoom);
+
+ mZoom = zoom;
}
/**
* Sets the offset to define which subset of zoomed bins to display
- *
+ *
* @param offset
*/
- public void setZoomWindowOffset( int offset )
+ public void setZoomWindowOffset(int offset)
{
- mZoomWindowOffset = offset;
+ mZoomWindowOffset = offset;
}
- /**
- * Sets the source sample size in bits which defines the lowest dB value to
- * display in the spectrum
- *
- * @param sampleSize in bits
- */
- public void setSampleSize( double sampleSize )
- {
- Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
-
- mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) );
- }
-
- /**
- * Defines the number of DFT result sets to average across
- */
- public void setAveraging( int size )
- {
- mAveraging = size;
- }
-
- /**
- * DFT result sets averaging value
- */
- public int getAveraging()
- {
- return mAveraging;
- }
-
- /**
- * Clears the spectral display
- */
- public void clearSpectrum()
- {
- mDisplayFFTBins = null;
-
- repaint();
- }
-
- private void getColors()
- {
- mColorSpectrumBackground =
- getColor( ColorSettingName.SPECTRUM_BACKGROUND );
-
- mColorSpectrumGradientBottom =
- getColor( ColorSettingName.SPECTRUM_GRADIENT_BOTTOM );
-
- mColorSpectrumGradientTop =
- getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP );
-
- mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE );
- }
-
- /**
- * Retrieves the current setting for the named color setting
- */
- private Color getColor( ColorSettingName name )
- {
- ColorSetting setting = mSettingsManager.getColorSetting( name );
-
- return setting.getColor();
- }
-
- /**
- * Listener interface to receive setting change notifications
- */
- @Override
- public void settingChanged( Setting setting )
+ /**
+ * Sets the source sample size in bits which defines the lowest dB value to
+ * display in the spectrum
+ *
+ * @param sampleSize in bits
+ */
+ public void setSampleSize(double sampleSize)
{
- if( setting instanceof ColorSetting )
- {
- ColorSetting colorSetting = (ColorSetting)setting;
-
- switch( ( (ColorSetting) setting ).getColorSettingName() )
- {
- case SPECTRUM_BACKGROUND:
- mColorSpectrumBackground = colorSetting.getColor();
- break;
- case SPECTRUM_GRADIENT_BOTTOM:
- mColorSpectrumGradientBottom = colorSetting.getColor();
- break;
- case SPECTRUM_GRADIENT_TOP:
- mColorSpectrumGradientTop = colorSetting.getColor();
- break;
- case SPECTRUM_LINE:
- mColorSpectrumLine = colorSetting.getColor();
- break;
- default:
- break;
- }
- }
+ Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
+
+ mDBScale = (float)(20.0 * Math.log10(Math.pow(2.0, sampleSize - 1)));
}
-
- private int getZoomMultiplier()
- {
- return (int)Math.pow( 2.0, mZoom );
- }
-
- /**
- * Returns the DFT result bins, or a zoomed and offset version of the bins
- * when the display is zoomed.
- */
+
+ /**
+ * Defines the number of DFT result sets to average across
+ */
+ public void setAveraging(int size)
+ {
+ mAveraging = size;
+ }
+
+ /**
+ * DFT result sets averaging value
+ */
+ public int getAveraging()
+ {
+ return mAveraging;
+ }
+
+ /**
+ * Clears the spectral display
+ */
+ public void clearSpectrum()
+ {
+ mDisplayFFTBins = null;
+
+ repaint();
+ }
+
+ private void getColors()
+ {
+ mColorSpectrumBackground =
+ getColor(ColorSettingName.SPECTRUM_BACKGROUND);
+
+ mColorSpectrumGradientBottom =
+ getColor(ColorSettingName.SPECTRUM_GRADIENT_BOTTOM);
+
+ mColorSpectrumGradientTop =
+ getColor(ColorSettingName.SPECTRUM_GRADIENT_TOP);
+
+ mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
+ }
+
+ /**
+ * Retrieves the current setting for the named color setting
+ */
+ private Color getColor(ColorSettingName name)
+ {
+ ColorSetting setting = mSettingsManager.getColorSetting(name);
+
+ return setting.getColor();
+ }
+
+ /**
+ * Listener interface to receive setting change notifications
+ */
+ @Override
+ public void settingChanged(Setting setting)
+ {
+ if(setting instanceof ColorSetting)
+ {
+ ColorSetting colorSetting = (ColorSetting)setting;
+
+ switch(((ColorSetting)setting).getColorSettingName())
+ {
+ case SPECTRUM_BACKGROUND:
+ mColorSpectrumBackground = colorSetting.getColor();
+ break;
+ case SPECTRUM_GRADIENT_BOTTOM:
+ mColorSpectrumGradientBottom = colorSetting.getColor();
+ break;
+ case SPECTRUM_GRADIENT_TOP:
+ mColorSpectrumGradientTop = colorSetting.getColor();
+ break;
+ case SPECTRUM_LINE:
+ mColorSpectrumLine = colorSetting.getColor();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private int getZoomMultiplier()
+ {
+ return (int)Math.pow(2.0, mZoom);
+ }
+
+ /**
+ * Returns the DFT result bins, or a zoomed and offset version of the bins
+ * when the display is zoomed.
+ */
private float[] getBins()
{
- if( mZoom == 0 )
- {
- return mDisplayFFTBins;
- }
- else
- {
- int length = mDisplayFFTBins.length / getZoomMultiplier();
-
- int offset = mZoomWindowOffset;
-
- if( ( offset + length ) >= mDisplayFFTBins.length )
- {
- offset = mDisplayFFTBins.length - length;
- }
-
- if( offset < 0 )
- {
- offset = 0;
- }
+ if(mZoom == 0)
+ {
+ return mDisplayFFTBins;
+ }
+ else
+ {
+ int length = mDisplayFFTBins.length / getZoomMultiplier();
- return Arrays.copyOfRange( mDisplayFFTBins, offset, offset + length );
- }
+ int offset = mZoomWindowOffset;
+
+ if((offset + length) >= mDisplayFFTBins.length)
+ {
+ offset = mDisplayFFTBins.length - length;
+ }
+
+ if(offset < 0)
+ {
+ offset = 0;
+ }
+
+ return Arrays.copyOfRange(mDisplayFFTBins, offset, offset + length);
+ }
}
- @Override
- public void settingDeleted( Setting setting ) { /* not implemented */ }
+ @Override
+ public void settingDeleted(Setting setting)
+ { /* not implemented */ }
- /**
- * Returns the current inter-bin smoothing setting
- */
- @Override
- public int getSmoothing()
- {
- return mSmoothingFilter.getPointSize();
- }
+ /**
+ * Returns the current inter-bin smoothing setting
+ */
+ @Override
+ public int getSmoothing()
+ {
+ return mSmoothingFilter.getPointSize();
+ }
- /**
- * Sets the bin smoothing value to define how wide the smoothing window is
- * when averaging across DFT bins
- */
- @Override
- public void setSmoothing( int smoothing )
- {
- mSmoothingFilter.setPointSize( smoothing );
- }
+ /**
+ * Sets the bin smoothing value to define how wide the smoothing window is
+ * when averaging across DFT bins
+ */
+ @Override
+ public void setSmoothing(int smoothing)
+ {
+ mSmoothingFilter.setPointSize(smoothing);
+ }
- /**
- * Returns the current smoothing filter type
- */
- @Override
- public SmoothingType getSmoothingType()
- {
- return mSmoothingFilter.getSmoothingType();
- }
+ /**
+ * Returns the current smoothing filter type
+ */
+ @Override
+ public SmoothingType getSmoothingType()
+ {
+ return mSmoothingFilter.getSmoothingType();
+ }
- /**
- * Sets the smoothing filter type
- */
- @Override
- public void setSmoothingType( SmoothingType type )
- {
- if( mSmoothingFilter.getSmoothingType() != type )
- {
- int pointSize = getSmoothing();
+ /**
+ * Sets the smoothing filter type
+ */
+ @Override
+ public void setSmoothingType(SmoothingType type)
+ {
+ if(mSmoothingFilter.getSmoothingType() != type)
+ {
+ int pointSize = getSmoothing();
- synchronized ( mSmoothingFilter )
- {
- switch( type )
- {
- case GAUSSIAN:
- mSmoothingFilter = new GaussianSmoothingFilter();
- break;
- case RECTANGLE:
- mSmoothingFilter = new RectangularSmoothingFilter();
- break;
- case TRIANGLE:
- mSmoothingFilter = new TriangularSmoothingFilter();
- break;
- case NONE:
- default:
- mSmoothingFilter = new NoSmoothingFilter();
- break;
- }
- }
-
- setSmoothing( pointSize );
- }
- }
+ synchronized(mSmoothingFilter)
+ {
+ switch(type)
+ {
+ case GAUSSIAN:
+ mSmoothingFilter = new GaussianSmoothingFilter();
+ break;
+ case RECTANGLE:
+ mSmoothingFilter = new RectangularSmoothingFilter();
+ break;
+ case TRIANGLE:
+ mSmoothingFilter = new TriangularSmoothingFilter();
+ break;
+ case NONE:
+ default:
+ mSmoothingFilter = new NoSmoothingFilter();
+ break;
+ }
+ }
+
+ setSmoothing(pointSize);
+ }
+ }
}