editor ) throws EditorValidationException
- {
- /**
- * Enforce CQPSK modulation uses a Tuner Source for I/Q sample data
- */
- if( editor instanceof SourceConfigurationEditor &&
- ((P25Decoder.Modulation)mComboModulation.getSelectedItem()) == P25Decoder.Modulation.CQPSK )
- {
- SourceConfiguration config = ((SourceConfigurationEditor)editor).getSourceConfiguration();
+ private void init()
+ {
+ setLayout(new MigLayout("insets 0 0 0 0,wrap 3", "[right][grow,fill][]", ""));
- if( config != null && !( config instanceof SourceConfigTuner ) )
- {
- throw new EditorValidationException( editor,
- "LSM Simulcast
"
- + "P25 LSM Simulcast decoder can only be used with "
- + "a tuner source. Please change the Source to use a tuner"
- + " or change the P25 Decoder to C4FM modulation
" );
- }
- }
-
- if( editor instanceof AuxDecodeConfigurationEditor)
- {
- AuxDecodeConfiguration config = ((AuxDecodeConfigurationEditor)editor).getConfiguration();
-
- if( config != null && !config.getAuxDecoders().isEmpty() )
- {
- throw new EditorValidationException( editor,
- "Auxilary Decoders
"
- + "The auxiliary decoders work with analog audio and are not "
- + "compatible with the P25 Decoder. Please uncheck any auxiliary "
- + "decoders that you have selected for this channel
" );
- }
- }
- }
+ mComboModulation = new JComboBox();
+ mComboModulation.setModel(new DefaultComboBoxModel(
+ P25DecoderLSM.Modulation.values()));
+ mComboModulation.setEnabled(false);
+ mComboModulation.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ setModified(true);
+ }
+ });
- private void setControlsEnabled( boolean enabled )
- {
- if( mComboModulation.isEnabled() != enabled )
- {
- mComboModulation.setEnabled( enabled );
- }
-
- if( mTrafficChannelPoolSize.isEnabled() != enabled )
- {
- mTrafficChannelPoolSize.setEnabled( enabled );
- }
+ add(new JLabel("Modulation:"));
+ add(mComboModulation);
- if( mIgnoreDataCalls.isEnabled() != enabled )
- {
- mIgnoreDataCalls.setEnabled( enabled );
- }
- }
+ mIgnoreDataCalls = new JCheckBox("Ignore Data Calls");
+ mIgnoreDataCalls.setEnabled(false);
+ mIgnoreDataCalls.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ setModified(true);
+ }
+ });
- @Override
- public void setItem( Channel item )
- {
- super.setItem( item );
-
- if( hasItem() )
- {
- setControlsEnabled( true );
-
- DecodeConfiguration config = getItem().getDecodeConfiguration();
-
- if( config instanceof DecodeConfigP25Phase1 )
- {
- DecodeConfigP25Phase1 p25 = (DecodeConfigP25Phase1)config;
-
- mComboModulation.setSelectedItem( p25.getModulation() );
- mIgnoreDataCalls.setSelected( p25.getIgnoreDataCalls() );
- mTrafficChannelPoolSize.setValue( p25.getTrafficChannelPoolSize() );
- setModified( false );
- }
- else
- {
- mComboModulation.setSelectedItem( P25Decoder.Modulation.C4FM );
- mIgnoreDataCalls.setSelected( false );
- mTrafficChannelPoolSize.setValue( DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT );
- setModified( true );
- }
- }
- else
- {
- setControlsEnabled( false );
- setModified( false );
- }
- }
+ add(mIgnoreDataCalls);
+
+ mTrafficChannelPoolSize = new JSlider(JSlider.HORIZONTAL, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MINIMUM,
+ DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MAXIMUM, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT);
+ mTrafficChannelPoolSize.setEnabled(false);
+ mTrafficChannelPoolSize.setMajorTickSpacing(10);
+ mTrafficChannelPoolSize.setMinorTickSpacing(5);
+ mTrafficChannelPoolSize.setPaintTicks(true);
+ mTrafficChannelPoolSize.addChangeListener(new ChangeListener()
+ {
+ @Override
+ public void stateChanged(ChangeEvent e)
+ {
+ mTrafficChannelPoolSizeLabel.setText("Traffic Pool: " + mTrafficChannelPoolSize.getValue());
+ setModified(true);
+ }
+ });
+
+ mTrafficChannelPoolSizeLabel = new JLabel("Traffic Pool: " + mTrafficChannelPoolSize.getValue() + " ");
+
+ add(mTrafficChannelPoolSizeLabel);
+ add(mTrafficChannelPoolSize);
+ }
+
+ @Override
+ public void save()
+ {
+ if(hasItem() && isModified())
+ {
+ DecodeConfigP25Phase1 p25 = new DecodeConfigP25Phase1();
+
+ p25.setModulation((P25Decoder.Modulation)mComboModulation.getSelectedItem());
+ p25.setIgnoreDataCalls(mIgnoreDataCalls.isSelected());
+ p25.setTrafficChannelPoolSize(mTrafficChannelPoolSize.getValue());
+
+ getItem().setDecodeConfiguration(p25);
+ }
+
+ setModified(false);
+ }
+
+ @Override
+ public void validate(Editor editor) throws EditorValidationException
+ {
+ /**
+ * Enforce CQPSK modulation uses a Tuner Source for I/Q sample data
+ */
+ if(editor instanceof SourceConfigurationEditor)
+ {
+ SourceConfiguration config = ((SourceConfigurationEditor)editor).getSourceConfiguration();
+
+ if(!(config instanceof SourceConfigTuner || config instanceof SourceConfigTunerMultipleFrequency))
+ {
+ throw new EditorValidationException(editor,
+ "LSM Simulcast
"
+ + "P25 LSM Simulcast decoder can only be used with "
+ + "a tuner source. Please change the Source to use a tuner"
+ + " or change the P25 Decoder to C4FM modulation
");
+ }
+ }
+ else if(editor instanceof AuxDecodeConfigurationEditor)
+ {
+ AuxDecodeConfiguration config = ((AuxDecodeConfigurationEditor)editor).getConfiguration();
+
+ if(config != null && !config.getAuxDecoders().isEmpty())
+ {
+ throw new EditorValidationException(editor,
+ "Auxilary Decoders
"
+ + "The auxiliary decoders work with analog audio and are not "
+ + "compatible with the P25 Decoder. Please uncheck any auxiliary "
+ + "decoders that you have selected for this channel
");
+ }
+ }
+ }
+
+ private void setControlsEnabled(boolean enabled)
+ {
+ if(mComboModulation.isEnabled() != enabled)
+ {
+ mComboModulation.setEnabled(enabled);
+ }
+
+ if(mTrafficChannelPoolSize.isEnabled() != enabled)
+ {
+ mTrafficChannelPoolSize.setEnabled(enabled);
+ }
+
+ if(mIgnoreDataCalls.isEnabled() != enabled)
+ {
+ mIgnoreDataCalls.setEnabled(enabled);
+ }
+ }
+
+ @Override
+ public void setItem(Channel item)
+ {
+ super.setItem(item);
+
+ if(hasItem())
+ {
+ setControlsEnabled(true);
+
+ DecodeConfiguration config = getItem().getDecodeConfiguration();
+
+ if(config instanceof DecodeConfigP25Phase1)
+ {
+ DecodeConfigP25Phase1 p25 = (DecodeConfigP25Phase1)config;
+
+ mComboModulation.setSelectedItem(p25.getModulation());
+ mIgnoreDataCalls.setSelected(p25.getIgnoreDataCalls());
+ mTrafficChannelPoolSize.setValue(p25.getTrafficChannelPoolSize());
+ setModified(false);
+ }
+ else
+ {
+ mComboModulation.setSelectedItem(P25Decoder.Modulation.C4FM);
+ mIgnoreDataCalls.setSelected(false);
+ mTrafficChannelPoolSize.setValue(DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT);
+ setModified(true);
+ }
+ }
+ else
+ {
+ setControlsEnabled(false);
+ setModified(false);
+ }
+ }
}
diff --git a/src/main/java/io/github/dsheirer/preference/PreferenceType.java b/src/main/java/io/github/dsheirer/preference/PreferenceType.java
index 5f00a600..dd5d5bc1 100644
--- a/src/main/java/io/github/dsheirer/preference/PreferenceType.java
+++ b/src/main/java/io/github/dsheirer/preference/PreferenceType.java
@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -28,5 +28,6 @@ public enum PreferenceType
DECODE_EVENT,
DIRECTORY,
IDENTIFIER,
+ MULTI_FREQUENCY,
TUNER;
}
diff --git a/src/main/java/io/github/dsheirer/preference/UserPreferences.java b/src/main/java/io/github/dsheirer/preference/UserPreferences.java
index bef3682a..e206cb23 100644
--- a/src/main/java/io/github/dsheirer/preference/UserPreferences.java
+++ b/src/main/java/io/github/dsheirer/preference/UserPreferences.java
@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -25,7 +25,8 @@ import io.github.dsheirer.preference.directory.DirectoryPreference;
import io.github.dsheirer.preference.event.DecodeEventPreference;
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
import io.github.dsheirer.preference.playlist.PlaylistPreference;
-import io.github.dsheirer.preference.tuner.TunerPreference;
+import io.github.dsheirer.preference.source.ChannelMultiFrequencyPreference;
+import io.github.dsheirer.preference.source.TunerPreference;
import io.github.dsheirer.sample.Listener;
/**
@@ -48,6 +49,7 @@ public class UserPreferences implements Listener
{
private DecodeEventPreference mDecodeEventPreference;
private DirectoryPreference mDirectoryPreference;
+ private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference;
private PlaylistPreference mPlaylistPreference;
private TalkgroupFormatPreference mTalkgroupFormatPreference;
private TunerPreference mTunerPreference;
@@ -76,6 +78,14 @@ public class UserPreferences implements Listener
return mDirectoryPreference;
}
+ /**
+ * Multiple frequency channel source preferences
+ */
+ public ChannelMultiFrequencyPreference getChannelMultiFrequencyPreference()
+ {
+ return mChannelMultiFrequencyPreference;
+ }
+
/**
* Playlist preferences
*/
@@ -106,7 +116,8 @@ public class UserPreferences implements Listener
private void loadPreferenceTypes()
{
mDecodeEventPreference = new DecodeEventPreference(this);
- mDirectoryPreference = new DirectoryPreference(this::receive);
+ mDirectoryPreference = new DirectoryPreference(this);
+ mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this);
mPlaylistPreference = new PlaylistPreference(this, mDirectoryPreference);
mTalkgroupFormatPreference = new TalkgroupFormatPreference(this);
mTunerPreference = new TunerPreference(this);
diff --git a/src/main/java/io/github/dsheirer/preference/source/ChannelMultiFrequencyPreference.java b/src/main/java/io/github/dsheirer/preference/source/ChannelMultiFrequencyPreference.java
new file mode 100644
index 00000000..04888b29
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/preference/source/ChannelMultiFrequencyPreference.java
@@ -0,0 +1,96 @@
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 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 io.github.dsheirer.preference.source;
+
+import io.github.dsheirer.preference.Preference;
+import io.github.dsheirer.preference.PreferenceType;
+import io.github.dsheirer.sample.Listener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.prefs.Preferences;
+
+/**
+ * Preferences for multiple frequency tuner channel sources.
+ */
+public class ChannelMultiFrequencyPreference extends Preference
+{
+ private final static Logger mLog = LoggerFactory.getLogger(ChannelMultiFrequencyPreference.class);
+ private Preferences mPreferences = Preferences.userNodeForPackage(ChannelMultiFrequencyPreference.class);
+ private static final String PREFERENCE_KEY_ROTATION_DELAY = "rotation.delay";
+ private static final long DEFAULT_ROTATION_DELAY = 5000; //5 seconds
+ private Long mRotationDelay = DEFAULT_ROTATION_DELAY;
+
+ /**
+ * Constructs a tuner preference with the update listener
+ *
+ * @param updateListener
+ */
+ public ChannelMultiFrequencyPreference(Listener updateListener)
+ {
+ super(updateListener);
+ }
+
+ @Override
+ public PreferenceType getPreferenceType()
+ {
+ return PreferenceType.MULTI_FREQUENCY;
+ }
+
+ /**
+ * Rotation delay value in milliseconds. This value is used for channel configurations where there source
+ * defines multiple frequencies for a system that employs rotating control channels.
+ */
+ public long getRotationDelay()
+ {
+ if(mRotationDelay == null)
+ {
+ mRotationDelay = mPreferences.getLong(PREFERENCE_KEY_ROTATION_DELAY, DEFAULT_ROTATION_DELAY);
+ }
+
+ return mRotationDelay;
+ }
+
+ /**
+ * Sets the rotation delay value in milliseconds.
+ * @param rotationDelay in milliseconds (1,000 - 60,000)
+ */
+ public void setRotationDelay(long rotationDelay)
+ {
+ if(1000 <= rotationDelay && rotationDelay <= 60000)
+ {
+ mRotationDelay = rotationDelay;
+ mPreferences.putLong(PREFERENCE_KEY_ROTATION_DELAY, mRotationDelay);
+ notifyPreferenceUpdated();
+ }
+ }
+
+ /**
+ * Resets the rotation delay and removes it from the preferences store, so that it can be recreated with
+ * a default value.
+ */
+ public void resetRotationDelay()
+ {
+ mPreferences.remove(PREFERENCE_KEY_ROTATION_DELAY);
+ mRotationDelay = null;
+ notifyPreferenceUpdated();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/preference/tuner/ChannelizerType.java b/src/main/java/io/github/dsheirer/preference/source/ChannelizerType.java
similarity index 93%
rename from src/main/java/io/github/dsheirer/preference/tuner/ChannelizerType.java
rename to src/main/java/io/github/dsheirer/preference/source/ChannelizerType.java
index 610f1b4d..69d5a11d 100644
--- a/src/main/java/io/github/dsheirer/preference/tuner/ChannelizerType.java
+++ b/src/main/java/io/github/dsheirer/preference/source/ChannelizerType.java
@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -18,7 +18,7 @@
* *****************************************************************************
*/
-package io.github.dsheirer.preference.tuner;
+package io.github.dsheirer.preference.source;
/**
* Channelizer Type - identifies the type of channelizer that the tuner will use to provide DDR channels
diff --git a/src/main/java/io/github/dsheirer/preference/tuner/TunerPreference.java b/src/main/java/io/github/dsheirer/preference/source/TunerPreference.java
similarity index 97%
rename from src/main/java/io/github/dsheirer/preference/tuner/TunerPreference.java
rename to src/main/java/io/github/dsheirer/preference/source/TunerPreference.java
index a45de274..fcd13d93 100644
--- a/src/main/java/io/github/dsheirer/preference/tuner/TunerPreference.java
+++ b/src/main/java/io/github/dsheirer/preference/source/TunerPreference.java
@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -18,7 +18,7 @@
* *****************************************************************************
*/
-package io.github.dsheirer.preference.tuner;
+package io.github.dsheirer.preference.source;
import io.github.dsheirer.preference.Preference;
import io.github.dsheirer.preference.PreferenceType;
diff --git a/src/main/java/io/github/dsheirer/sample/Broadcaster.java b/src/main/java/io/github/dsheirer/sample/Broadcaster.java
index c705bf1d..5605a00f 100644
--- a/src/main/java/io/github/dsheirer/sample/Broadcaster.java
+++ b/src/main/java/io/github/dsheirer/sample/Broadcaster.java
@@ -1,22 +1,27 @@
-/*******************************************************************************
- * 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-2019 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 io.github.dsheirer.sample;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -25,8 +30,23 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public class Broadcaster implements Listener
{
+ private final static Logger mLog = LoggerFactory.getLogger(Broadcaster.class);
+ private boolean mDebug;
+
protected List> mListeners = new CopyOnWriteArrayList<>();
+ public Broadcaster()
+ {
+ }
+
+ /**
+ * Turns on/off debugging to troubleshoot and log which listeners are receiving an item.
+ */
+ public void setDebug(boolean debug)
+ {
+ mDebug = debug;
+ }
+
/**
* Implements the Listener interface to receive an element and broadcast that element to all registered
* listeners.
@@ -116,9 +136,21 @@ public class Broadcaster implements Listener
*/
public void broadcast(T t)
{
- for(Listener listener : mListeners)
+ if(mDebug)
{
- listener.receive(t);
+ for(Listener listener : mListeners)
+ {
+ mLog.debug("Sending [" + t + "] to listener [" + listener.getClass() + "]");
+ listener.receive(t);
+ mLog.debug("Finished sending to listener [" + listener.getClass() + "]");
+ }
+ }
+ else
+ {
+ for(Listener listener : mListeners)
+ {
+ listener.receive(t);
+ }
}
}
}
diff --git a/src/main/java/io/github/dsheirer/source/Source.java b/src/main/java/io/github/dsheirer/source/Source.java
index 5e378606..948f17cc 100644
--- a/src/main/java/io/github/dsheirer/source/Source.java
+++ b/src/main/java/io/github/dsheirer/source/Source.java
@@ -1,25 +1,29 @@
-/*******************************************************************************
- * 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-2019 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 io.github.dsheirer.source;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.IOverflowListener;
+import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
+import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
@@ -39,8 +43,29 @@ public abstract class Source extends Module implements ISourceEventListener, ISo
}
+ /**
+ * Buffer overflow listener
+ */
protected IOverflowListener mOverflowListener;
+ /**
+ * Adds the listener to receive heartbeats from this source
+ */
+ @Override
+ public void addHeartbeatListener(Listener listener)
+ {
+ getHeartbeatManager().addHeartbeatListener(listener);
+ }
+
+ /**
+ * Removes the listener from receiving heartbeats from this source
+ */
+ @Override
+ public void removeHeartbeatListener(Listener listener)
+ {
+ getHeartbeatManager().removeHeartbeatListener(listener);
+ }
+
/**
* Indicates the type of samples provided by this source: real or complex
*/
diff --git a/src/main/java/io/github/dsheirer/source/SourceConfigurationEditor.java b/src/main/java/io/github/dsheirer/source/SourceConfigurationEditor.java
index 8a903f3e..4a0c28e2 100644
--- a/src/main/java/io/github/dsheirer/source/SourceConfigurationEditor.java
+++ b/src/main/java/io/github/dsheirer/source/SourceConfigurationEditor.java
@@ -1,20 +1,22 @@
-/*******************************************************************************
- * 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-2019 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 io.github.dsheirer.source;
import io.github.dsheirer.controller.channel.Channel;
@@ -23,6 +25,7 @@ import io.github.dsheirer.gui.editor.EmptyEditor;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.mixer.MixerSourceEditor;
import io.github.dsheirer.source.tuner.TunerSourceEditor;
+import io.github.dsheirer.source.tuner.TunerSourceMultipleFrequencyEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.DefaultComboBoxModel;
@@ -37,99 +40,106 @@ public class SourceConfigurationEditor extends Editor
private JComboBox mComboSources;
private MixerSourceEditor mMixerSourceEditor;
private TunerSourceEditor mTunerSourceEditor;
+ private TunerSourceMultipleFrequencyEditor mTunerSourceMultipleFrequencyEditor;
private Editor mCurrentEditor;
-
- public SourceConfigurationEditor( SourceManager sourceManager )
- {
- mMixerSourceEditor = new MixerSourceEditor( sourceManager );
- mMixerSourceEditor.setSaveRequestListener( this );
-
- mTunerSourceEditor = new TunerSourceEditor(sourceManager.getTunerModel());
- mTunerSourceEditor.setSaveRequestListener( this );
-
- init();
- }
-
+
+ public SourceConfigurationEditor(SourceManager sourceManager)
+ {
+ mMixerSourceEditor = new MixerSourceEditor(sourceManager);
+ mMixerSourceEditor.setSaveRequestListener(this);
+
+ mTunerSourceEditor = new TunerSourceEditor(sourceManager.getTunerModel());
+ mTunerSourceEditor.setSaveRequestListener(this);
+
+ mTunerSourceMultipleFrequencyEditor = new TunerSourceMultipleFrequencyEditor(sourceManager.getTunerModel());
+ mTunerSourceMultipleFrequencyEditor.setSaveRequestListener(this);
+
+ init();
+ }
+
public SourceConfiguration getSourceConfiguration()
{
- return hasItem() ? getItem().getSourceConfiguration() : null;
+ return hasItem() ? getItem().getSourceConfiguration() : null;
}
private void init()
{
- setLayout( new MigLayout( "wrap 2", "[][grow,fill]", "[align top][grow]" ) );
+ setLayout(new MigLayout("wrap 2", "[][grow,fill]", "[align top][grow,fill]"));
- mComboSources = new JComboBox();
- mComboSources.setModel( new DefaultComboBoxModel( SourceType.getTypes() ) );
- mComboSources.addActionListener( new ActionListener()
- {
- @Override
- public void actionPerformed( ActionEvent e )
- {
- SourceType selected = (SourceType)mComboSources.getSelectedItem();
-
- switch( selected )
- {
- case MIXER:
- setEditor( mMixerSourceEditor );
- break;
- case TUNER:
- setEditor( mTunerSourceEditor );
- break;
- default:
- setEditor( new EmptyEditor() );
- break;
- }
- }
- });
-
- add( mComboSources );
-
- mCurrentEditor = mTunerSourceEditor;
- add( mCurrentEditor );
+ mComboSources = new JComboBox();
+ mComboSources.setModel(new DefaultComboBoxModel(SourceType.getTypes()));
+ mComboSources.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ SourceType selected = (SourceType) mComboSources.getSelectedItem();
+
+ switch(selected)
+ {
+ case MIXER:
+ setEditor(mMixerSourceEditor);
+ break;
+ case TUNER:
+ setEditor(mTunerSourceEditor);
+ break;
+ case TUNER_MULTIPLE_FREQUENCIES:
+ setEditor(mTunerSourceMultipleFrequencyEditor);
+ break;
+ default:
+ setEditor(new EmptyEditor());
+ break;
+ }
+ }
+ });
+
+ add(mComboSources);
+
+ mCurrentEditor = mTunerSourceEditor;
+ add(mCurrentEditor, "span 1 2");
}
-
- private void setEditor( Editor editor )
+
+ private void setEditor(Editor editor)
{
- if( mCurrentEditor != editor )
- {
- //Set channel to null to force a save prompt as required
- mCurrentEditor.setItem( null );
-
- remove( mCurrentEditor );
- mCurrentEditor = editor;
- mCurrentEditor.setSaveRequestListener( this );
- add( mCurrentEditor );
-
- mCurrentEditor.setItem( getItem() );
-
- revalidate();
- }
+ if(mCurrentEditor != editor)
+ {
+ //Set channel to null to force a save prompt as required
+ mCurrentEditor.setItem(null);
+
+ remove(mCurrentEditor);
+ mCurrentEditor = editor;
+ mCurrentEditor.setSaveRequestListener(this);
+ add(mCurrentEditor, "span 1 2");
+
+ mCurrentEditor.setItem(getItem());
+
+ revalidate();
+ }
}
-
- @Override
+
+ @Override
public void save()
{
- if( hasItem() )
- {
- mCurrentEditor.save();
- }
+ if(hasItem())
+ {
+ mCurrentEditor.save();
+ }
}
- /**
- * Sets the channel configuration. Note: this method must be invoked from
- * the swing event dispatch thread.
- */
- @Override
- public void setItem( Channel channel )
- {
- super.setItem( channel );
-
- if( hasItem() )
- {
- mComboSources.setSelectedItem( getItem().getSourceConfiguration().getSourceType() );
- repaint();
- mCurrentEditor.setItem( channel );
- }
- }
+ /**
+ * Sets the channel configuration. Note: this method must be invoked from
+ * the swing event dispatch thread.
+ */
+ @Override
+ public void setItem(Channel channel)
+ {
+ super.setItem(channel);
+
+ if(hasItem())
+ {
+ mComboSources.setSelectedItem(getItem().getSourceConfiguration().getSourceType());
+ repaint();
+ mCurrentEditor.setItem(channel);
+ }
+ }
}
diff --git a/src/main/java/io/github/dsheirer/source/SourceEvent.java b/src/main/java/io/github/dsheirer/source/SourceEvent.java
index d3cc2890..fe489237 100644
--- a/src/main/java/io/github/dsheirer/source/SourceEvent.java
+++ b/src/main/java/io/github/dsheirer/source/SourceEvent.java
@@ -1,6 +1,7 @@
-/*******************************************************************************
+/*
+ * ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2017 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -14,8 +15,8 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * *****************************************************************************
+ */
package io.github.dsheirer.source;
import java.util.EnumSet;
@@ -31,6 +32,8 @@ public class SourceEvent
NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED,
NOTIFICATION_FREQUENCY_CHANGE,
NOTIFICATION_FREQUENCY_CORRECTION_CHANGE,
+ NOTIFICATION_FREQUENCY_ROTATION_SUCCESS,
+ NOTIFICATION_FREQUENCY_ROTATION_FAILURE,
NOTIFICATION_MEASURED_FREQUENCY_ERROR,
NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED,
NOTIFICATION_SAMPLE_RATE_CHANGE,
@@ -38,6 +41,7 @@ public class SourceEvent
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
REQUEST_FREQUENCY_CHANGE,
+ REQUEST_FREQUENCY_ROTATION,
REQUEST_SOURCE_DISPOSE,
REQUEST_START_SAMPLE_STREAM,
REQUEST_STOP_SAMPLE_STREAM;
@@ -64,6 +68,14 @@ public class SourceEvent
mEventDescription = eventDescription;
}
+ /**
+ * Private constructor. Use the static constructor methods to create an event.
+ */
+ private SourceEvent(Event event)
+ {
+ this(event, null, null, null);
+ }
+
/**
* Private constructor. Use the static constructor methods to create an event.
*/
@@ -175,9 +187,9 @@ public class SourceEvent
*
* @param frequency in hertz
*/
- public static SourceEvent frequencyChange(long frequency)
+ public static SourceEvent frequencyChange(Source source, long frequency)
{
- return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency);
+ return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, source, frequency);
}
/**
@@ -186,9 +198,9 @@ public class SourceEvent
* @param frequency in hertz
* @param eventDescription to add a tag to the event
*/
- public static SourceEvent frequencyChange(long frequency, String eventDescription)
+ public static SourceEvent frequencyChange(Source source, long frequency, String eventDescription)
{
- return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency, eventDescription);
+ return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, source, frequency, eventDescription);
}
/**
@@ -338,6 +350,33 @@ public class SourceEvent
return new SourceEvent(Event.REQUEST_SOURCE_DISPOSE, source);
}
+ /**
+ * Creates a request to cycle to the next source frequency. This is normally used for decoders to request the
+ * next frequency in a list when a multiple-frequency source configuration is defined.
+ */
+ public static SourceEvent frequencyRotationRequest()
+ {
+ return new SourceEvent(Event.REQUEST_FREQUENCY_ROTATION);
+ }
+
+ /**
+ * Creates a notification that the request to rotate the source frequency to the next frequency in the list
+ * was successful.
+ */
+ public static SourceEvent frequencyRotationSuccessNotification(Source source, long frequency)
+ {
+ return new SourceEvent(Event.NOTIFICATION_FREQUENCY_ROTATION_SUCCESS, source, frequency);
+ }
+
+ /**
+ * Creates a notification that the request to rotate the source frequency to the next frequency in the list
+ * was unsuccessful.
+ */
+ public static SourceEvent frequencyRotationFailureNotification(Source source, long frequency)
+ {
+ return new SourceEvent(Event.NOTIFICATION_FREQUENCY_ROTATION_FAILURE, source, frequency);
+ }
+
@Override
public String toString()
{
diff --git a/src/main/java/io/github/dsheirer/source/SourceManager.java b/src/main/java/io/github/dsheirer/source/SourceManager.java
index 5166f82f..2ea29479 100644
--- a/src/main/java/io/github/dsheirer/source/SourceManager.java
+++ b/src/main/java/io/github/dsheirer/source/SourceManager.java
@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -22,12 +22,16 @@ package io.github.dsheirer.source;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.config.SourceConfigTuner;
+import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.mixer.MixerManager;
import io.github.dsheirer.source.recording.RecordingSourceManager;
import io.github.dsheirer.source.tuner.TunerManager;
import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
+import io.github.dsheirer.source.tuner.channel.MultiFrequencyTunerChannelSource;
+import io.github.dsheirer.source.tuner.channel.TunerChannel;
+import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
public class SourceManager
{
@@ -86,7 +90,29 @@ public class SourceManager
retVal = mMixerManager.getSource(config);
break;
case TUNER:
- retVal = mTunerModel.getSource((SourceConfigTuner) config, channelSpecification);
+ if(config instanceof SourceConfigTuner)
+ {
+ SourceConfigTuner sourceConfigTuner = (SourceConfigTuner)config;
+ TunerChannel tunerChannel = sourceConfigTuner.getTunerChannel(channelSpecification.getBandwidth());
+ String preferredTuner = sourceConfigTuner.getPreferredTuner();
+ retVal = mTunerModel.getSource(tunerChannel, channelSpecification, preferredTuner);
+ }
+ break;
+ case TUNER_MULTIPLE_FREQUENCIES:
+ if(config instanceof SourceConfigTunerMultipleFrequency)
+ {
+ SourceConfigTunerMultipleFrequency sourceConfigTuner = (SourceConfigTunerMultipleFrequency)config;
+ TunerChannel tunerChannel = sourceConfigTuner.getFirstTunerChannel(channelSpecification.getBandwidth());
+ String preferredTuner = sourceConfigTuner.getPreferredTuner();
+
+ Source source = mTunerModel.getSource(tunerChannel, channelSpecification, preferredTuner);
+
+ if(source instanceof TunerChannelSource)
+ {
+ retVal = new MultiFrequencyTunerChannelSource(getTunerModel(), (TunerChannelSource)source,
+ sourceConfigTuner.getFrequencies(), channelSpecification, sourceConfigTuner.getPreferredTuner());
+ }
+ }
break;
case RECORDING:
retVal = mRecordingSourceManager.getSource(config, channelSpecification);
diff --git a/src/main/java/io/github/dsheirer/source/SourceType.java b/src/main/java/io/github/dsheirer/source/SourceType.java
index 1145bcf7..fb1173ae 100644
--- a/src/main/java/io/github/dsheirer/source/SourceType.java
+++ b/src/main/java/io/github/dsheirer/source/SourceType.java
@@ -1,54 +1,51 @@
/*******************************************************************************
- * 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
+ * sdr-trunk
+ * Copyright (C) 2014-2018 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 io.github.dsheirer.source;
-
+/**
+ * Source Configuration Types enumeration.
+ */
public enum SourceType
{
- NONE( "No Source" ),
- MIXER( "Sound Card" ),
- TUNER( "Tuner" ),
- RECORDING( "IQ Recording" );
-
+ NONE("No Source"),
+ MIXER("Sound Card"),
+ TUNER("Tuner"),
+ TUNER_MULTIPLE_FREQUENCIES("Tuner - Multiple Frequencies"),
+ RECORDING("IQ Recording");
+
private String mDisplayString;
-
- SourceType( String displayString )
+
+ SourceType(String displayString)
{
mDisplayString = displayString;
}
-
+
public static SourceType[] getTypes()
{
- SourceType[] types = new SourceType[ 2 ];
- types[ 0 ] = SourceType.TUNER;
- types[ 1 ] = SourceType.MIXER;
+ SourceType[] types = new SourceType[3];
+ types[0] = SourceType.TUNER;
+ types[1] = SourceType.TUNER_MULTIPLE_FREQUENCIES;
+ types[2] = SourceType.MIXER;
- return types;
+ return types;
}
-
- public String getDisplayString()
- {
- return mDisplayString;
- }
-
+
@Override
public String toString()
{
- return mDisplayString;
+ return mDisplayString;
}
}
diff --git a/src/main/java/io/github/dsheirer/source/config/SourceConfigFactory.java b/src/main/java/io/github/dsheirer/source/config/SourceConfigFactory.java
index 851f3bff..2dfb2ead 100644
--- a/src/main/java/io/github/dsheirer/source/config/SourceConfigFactory.java
+++ b/src/main/java/io/github/dsheirer/source/config/SourceConfigFactory.java
@@ -1,19 +1,17 @@
/*******************************************************************************
- * 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
+ * sdr-trunk
+ * Copyright (C) 2014-2018 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 io.github.dsheirer.source.config;
@@ -21,64 +19,74 @@ import io.github.dsheirer.source.SourceType;
public class SourceConfigFactory
{
- public static SourceConfiguration getDefaultSourceConfiguration()
- {
- return getSourceConfiguration( SourceType.TUNER );
- }
-
- public static SourceConfiguration getSourceConfiguration( SourceType source )
- {
- SourceConfiguration retVal;
+ public static SourceConfiguration getDefaultSourceConfiguration()
+ {
+ return getSourceConfiguration(SourceType.TUNER);
+ }
- switch( source )
- {
- case TUNER:
- retVal = new SourceConfigTuner();
- break;
- case MIXER:
- retVal = new SourceConfigMixer();
- break;
- case NONE:
- default:
- retVal = new SourceConfigNone();
- break;
- }
-
- return retVal;
- }
+ public static SourceConfiguration getSourceConfiguration(SourceType source)
+ {
+ SourceConfiguration retVal;
- /**
- * Creates a copy of the configuration
- */
- public static SourceConfiguration copy( SourceConfiguration config )
- {
- if( config != null )
- {
- switch( config.getSourceType() )
- {
- case MIXER:
- SourceConfigMixer originalMixer = (SourceConfigMixer)config;
- SourceConfigMixer copyMixer = new SourceConfigMixer();
- copyMixer.setChannel( originalMixer.getChannel() );
- copyMixer.setMixer( originalMixer.getMixer() );
- return copyMixer;
- case RECORDING:
- SourceConfigRecording originalRec = (SourceConfigRecording)config;
- SourceConfigRecording copyRec = new SourceConfigRecording();
- copyRec.setFrequency( originalRec.getFrequency() );
- copyRec.setRecordingAlias( originalRec.getRecordingAlias() );
- return copyRec;
- case TUNER:
- SourceConfigTuner originalTuner = (SourceConfigTuner)config;
- SourceConfigTuner copyTuner = new SourceConfigTuner();
- copyTuner.setFrequency( originalTuner.getFrequency() );
- return copyTuner;
- case NONE:
- default:
- return new SourceConfigNone();
- }
- }
-
- return null;
- }
+ switch(source)
+ {
+ case TUNER:
+ retVal = new SourceConfigTuner();
+ break;
+ case TUNER_MULTIPLE_FREQUENCIES:
+ retVal = new SourceConfigTunerMultipleFrequency();
+ break;
+ case MIXER:
+ retVal = new SourceConfigMixer();
+ break;
+ case NONE:
+ default:
+ retVal = new SourceConfigNone();
+ break;
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Creates a copy of the configuration
+ */
+ public static SourceConfiguration copy(SourceConfiguration config)
+ {
+ if(config != null)
+ {
+ switch(config.getSourceType())
+ {
+ case MIXER:
+ SourceConfigMixer originalMixer = (SourceConfigMixer) config;
+ SourceConfigMixer copyMixer = new SourceConfigMixer();
+ copyMixer.setChannel(originalMixer.getChannel());
+ copyMixer.setMixer(originalMixer.getMixer());
+ return copyMixer;
+ case RECORDING:
+ SourceConfigRecording originalRec = (SourceConfigRecording) config;
+ SourceConfigRecording copyRec = new SourceConfigRecording();
+ copyRec.setFrequency(originalRec.getFrequency());
+ copyRec.setRecordingAlias(originalRec.getRecordingAlias());
+ return copyRec;
+ case TUNER:
+ SourceConfigTuner originalTuner = (SourceConfigTuner) config;
+ SourceConfigTuner copyTuner = new SourceConfigTuner();
+ copyTuner.setPreferredTuner(originalTuner.getPreferredTuner());
+ copyTuner.setFrequency(originalTuner.getFrequency());
+ return copyTuner;
+ case TUNER_MULTIPLE_FREQUENCIES:
+ SourceConfigTunerMultipleFrequency originalMulti = (SourceConfigTunerMultipleFrequency) config;
+ SourceConfigTunerMultipleFrequency copyMulti = new SourceConfigTunerMultipleFrequency();
+ copyMulti.setPreferredTuner(originalMulti.getPreferredTuner());
+ copyMulti.setFrequencies(originalMulti.getFrequencies());
+ return copyMulti;
+ case NONE:
+ default:
+ return new SourceConfigNone();
+ }
+ }
+
+ return null;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/source/config/SourceConfigTuner.java b/src/main/java/io/github/dsheirer/source/config/SourceConfigTuner.java
index 3c467c6e..9c397320 100644
--- a/src/main/java/io/github/dsheirer/source/config/SourceConfigTuner.java
+++ b/src/main/java/io/github/dsheirer/source/config/SourceConfigTuner.java
@@ -1,20 +1,22 @@
-/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 Dennis Sheirer
*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * *****************************************************************************
+ */
package io.github.dsheirer.source.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -31,7 +33,6 @@ public class SourceConfigTuner extends SourceConfiguration
private static DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private long mFrequency = 0;
- private int mBandwidth = 12500;
private String mPreferredTuner;
public SourceConfigTuner()
@@ -42,9 +43,7 @@ public class SourceConfigTuner extends SourceConfiguration
public SourceConfigTuner(TunerChannel tunerChannel)
{
this();
-
mFrequency = tunerChannel.getFrequency();
- mBandwidth = tunerChannel.getBandwidth();
}
@JacksonXmlProperty(isAttribute = true, localName = "frequency")
@@ -94,8 +93,8 @@ public class SourceConfigTuner extends SourceConfiguration
}
@JsonIgnore
- public TunerChannel getTunerChannel()
+ public TunerChannel getTunerChannel(int bandwidth)
{
- return new TunerChannel(mFrequency, mBandwidth);
+ return new TunerChannel(mFrequency, bandwidth);
}
}
diff --git a/src/main/java/io/github/dsheirer/source/config/SourceConfigTunerMultipleFrequency.java b/src/main/java/io/github/dsheirer/source/config/SourceConfigTunerMultipleFrequency.java
new file mode 100644
index 00000000..e0f34549
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/config/SourceConfigTunerMultipleFrequency.java
@@ -0,0 +1,127 @@
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 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 io.github.dsheirer.source.config;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import io.github.dsheirer.source.SourceType;
+import io.github.dsheirer.source.tuner.channel.TunerChannel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Multiple frequency tuner source configuration is used for a system with a rolling/rotating control
+ * channel where the control channel rotates across a pre-defined set of channel frequencies.
+ */
+@JsonSubTypes.Type(value = SourceConfigTunerMultipleFrequency.class, name = "sourceConfigTunerMultipleFrequency")
+public class SourceConfigTunerMultipleFrequency extends SourceConfiguration
+{
+ private List mFrequencies = new ArrayList<>();
+ private String mPreferredTuner;
+
+ public SourceConfigTunerMultipleFrequency()
+ {
+ super(SourceType.TUNER_MULTIPLE_FREQUENCIES);
+ }
+
+ /**
+ * List of frequencies for this configuration
+ */
+ @JacksonXmlProperty(isAttribute = false, localName = "frequency")
+ public List getFrequencies()
+ {
+ return mFrequencies;
+ }
+
+ /**
+ * Sets the list of frequencies for this configuration
+ */
+ public void setFrequencies(List frequencies)
+ {
+ mFrequencies = frequencies;
+ }
+
+ /**
+ * Indicates if this configuration has more than one frequency specified
+ */
+ @JsonIgnore
+ public boolean hasMultipleFrequencies()
+ {
+ return mFrequencies.size() > 1;
+ }
+
+ /**
+ * Adds a frequency to the list
+ */
+ public void addFrequency(long frequency)
+ {
+ mFrequencies.add(frequency);
+ }
+
+ /**
+ * Preferred tuner to use for this configuration.
+ * @return tuner name or null
+ */
+ @JacksonXmlProperty(isAttribute = true, localName = "preferred_tuner")
+ public String getPreferredTuner()
+ {
+ return mPreferredTuner;
+ }
+
+ /**
+ * Indicates if this configuration has a specified preferred tuner
+ */
+ @JsonIgnore
+ public boolean hasPreferredTuner()
+ {
+ return mPreferredTuner != null;
+ }
+
+ /**
+ * Specifies the preferred tuner to use for this configuration
+ * @param preferredTuner to use, if available
+ */
+ public void setPreferredTuner(String preferredTuner)
+ {
+ mPreferredTuner = preferredTuner;
+ }
+
+ @JsonIgnore
+ @Override
+ public String getDescription()
+ {
+ return "Frequencies [" + mFrequencies.size() + "]";
+ }
+
+ @JsonIgnore
+ public TunerChannel getFirstTunerChannel(int bandwidth)
+ {
+ if(getFrequencies().size() > 0)
+ {
+ return new TunerChannel(getFrequencies().get(0), bandwidth);
+ }
+ else
+ {
+ return new TunerChannel(0l, bandwidth);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/config/SourceConfiguration.java b/src/main/java/io/github/dsheirer/source/config/SourceConfiguration.java
index 7d4adb66..821f2243 100644
--- a/src/main/java/io/github/dsheirer/source/config/SourceConfiguration.java
+++ b/src/main/java/io/github/dsheirer/source/config/SourceConfiguration.java
@@ -30,7 +30,7 @@ import io.github.dsheirer.source.SourceType;
@JsonSubTypes.Type(value = SourceConfigNone.class, name = "sourceConfigNone"),
@JsonSubTypes.Type(value = SourceConfigRecording.class, name = "sourceConfigRecording"),
@JsonSubTypes.Type(value = SourceConfigTuner.class, name = "sourceConfigTuner"),
-})
+ @JsonSubTypes.Type(value = SourceConfigTunerMultipleFrequency.class, name = "sourceConfigTunerMultipleFrequency")})
@JacksonXmlRootElement(localName = "source_configuration")
public class SourceConfiguration extends Configuration
{
diff --git a/src/main/java/io/github/dsheirer/source/heartbeat/IHeartbeatProvider.java b/src/main/java/io/github/dsheirer/source/heartbeat/IHeartbeatProvider.java
index 076b50a2..b045579f 100644
--- a/src/main/java/io/github/dsheirer/source/heartbeat/IHeartbeatProvider.java
+++ b/src/main/java/io/github/dsheirer/source/heartbeat/IHeartbeatProvider.java
@@ -1,6 +1,7 @@
-/*******************************************************************************
+/*
+ * ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2017 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -14,11 +15,22 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * *****************************************************************************
+ */
package io.github.dsheirer.source.heartbeat;
+import io.github.dsheirer.sample.Listener;
+
public interface IHeartbeatProvider
{
- public HeartbeatManager getHeartbeatManager();
+ /**
+ * Adds the listener to receive heartbeats
+ */
+ void addHeartbeatListener(Listener listener);
+
+ /**
+ * Removes the listener from receiving heartbeats
+ * @param listener
+ */
+ void removeHeartbeatListener(Listener listener);
}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java
index 8e3ae629..f94f11dc 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java
@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -20,7 +20,7 @@
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.preference.UserPreferences;
-import io.github.dsheirer.preference.tuner.ChannelizerType;
+import io.github.dsheirer.preference.source.ChannelizerType;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventProcessor;
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
index a4a12ea1..948c45c4 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
@@ -1,25 +1,28 @@
-/*******************************************************************************
- * sdr-trunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 Dennis Sheirer
*
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
- * later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * *****************************************************************************
+ */
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceException;
-import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
@@ -335,62 +338,60 @@ public class TunerModel extends AbstractTableModel implements Listener it = mTuners.iterator();
-
- Tuner tuner;
-
- if(config.hasPreferredTuner())
+ if(tunerChannel != null && channelSpecification != null)
{
- tuner = getTuner(config.getPreferredTuner());
+ Iterator it = mTuners.iterator();
- if(tuner != null)
+ Tuner tuner;
+
+ if(preferredTuner != null)
{
+ tuner = getTuner(preferredTuner);
+
+ if(tuner != null)
+ {
+ try
+ {
+ source = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
+
+ if(source != null)
+ {
+ return source;
+ }
+ }
+ catch(Exception e)
+ {
+ //Fall through to logger below
+ }
+ }
+
+ mLog.info("Unable to source channel [" + tunerChannel.getFrequency() + "] from preferred tuner [" +
+ preferredTuner + "] - searching for another tuner");
+ }
+
+ while(it.hasNext() && source == null)
+ {
+ tuner = it.next();
+
try
{
- retVal = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
-
- if(retVal != null)
- {
- return retVal;
- }
+ source = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
}
catch(Exception e)
{
- //Fall through to logger below
+ mLog.error("Error obtaining channel from tuner [" + tuner.getName() + "]", e);
}
}
-
- mLog.info("Unable to source channel [" + config.getFrequency() + "] from preferred tuner [" +
- config.getPreferredTuner() + "] - searching for another tuner");
}
- while(it.hasNext() && retVal == null)
- {
- tuner = it.next();
-
- try
- {
- retVal = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
- }
- catch(Exception e)
- {
- mLog.error("error obtaining channel from tuner [" + tuner.getName() + "]", e);
- }
- }
-
- return retVal;
+ return source;
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerSourceMultipleFrequencyEditor.java b/src/main/java/io/github/dsheirer/source/tuner/TunerSourceMultipleFrequencyEditor.java
new file mode 100644
index 00000000..068b3a84
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerSourceMultipleFrequencyEditor.java
@@ -0,0 +1,184 @@
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 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 io.github.dsheirer.source.tuner;
+
+import io.github.dsheirer.controller.channel.Channel;
+import io.github.dsheirer.gui.control.MultipleFrequencyEditor;
+import io.github.dsheirer.gui.editor.Editor;
+import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
+import io.github.dsheirer.source.config.SourceConfiguration;
+import net.miginfocom.swing.MigLayout;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TunerSourceMultipleFrequencyEditor extends Editor
+{
+ private static final long serialVersionUID = 1L;
+ private static final String NO_PREFERRED_TUNER = "(none)";
+ private static final String UNAVAILABLE_TUNER = " (unavailable)";
+ private MultipleFrequencyEditor mFrequencyEditor = new MultipleFrequencyEditor(0.0, 6000.0);
+ private JComboBox mTunerNameComboBox;
+ private TunerModel mTunerModel;
+ private List mCurrentTunerNames;
+
+ public TunerSourceMultipleFrequencyEditor(TunerModel tunerModel)
+ {
+ mTunerModel = tunerModel;
+ init();
+ }
+
+ private void init()
+ {
+ loadTunerNames();
+
+ setLayout(new MigLayout("insets 0 0 0 0",
+ "[grow,fill][][right][grow,fill,left]", "[][grow,fill]"));
+
+ mFrequencyEditor.getFrequencyModel().addTableModelListener(new TableModelListener()
+ {
+ @Override
+ public void tableChanged(TableModelEvent e)
+ {
+ setModified(true);
+ }
+ });
+// mFrequencyEditor.setSize(new Dimension(100, 30));
+ add(mFrequencyEditor, "span 2 2");
+
+ mTunerNameComboBox = new JComboBox<>(mCurrentTunerNames.toArray(new String[mCurrentTunerNames.size()]));
+ mTunerNameComboBox.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ setModified(true);
+ }
+ });
+ add(new JLabel("Preferred Tuner:"));
+ add(mTunerNameComboBox);
+ }
+
+ private void loadTunerNames()
+ {
+ mCurrentTunerNames = new ArrayList<>();
+
+ for(Tuner tuner: mTunerModel.getTuners())
+ {
+ mCurrentTunerNames.add(tuner.getName());
+ }
+
+ Collections.sort(mCurrentTunerNames);
+
+ mCurrentTunerNames.add(0, NO_PREFERRED_TUNER);
+ }
+
+ private void updateTunerNameCombo(String preferredTuner)
+ {
+ if(preferredTuner != null && !mCurrentTunerNames.contains(preferredTuner))
+ {
+ List updatedTunerNameList = new ArrayList<>(mCurrentTunerNames);
+ String unavailableTuner = preferredTuner + UNAVAILABLE_TUNER;
+ updatedTunerNameList.add(unavailableTuner);
+ String[] tunerNameArray = updatedTunerNameList.toArray(new String[updatedTunerNameList.size()]);
+ mTunerNameComboBox.setModel(new DefaultComboBoxModel<>(tunerNameArray));
+ mTunerNameComboBox.setSelectedItem(unavailableTuner);
+ }
+ else
+ {
+ String[] tunerNameArray = mCurrentTunerNames.toArray(new String[mCurrentTunerNames.size()]);
+ mTunerNameComboBox.setModel(new DefaultComboBoxModel<>(tunerNameArray));
+
+ if(preferredTuner == null)
+ {
+ mTunerNameComboBox.setSelectedItem(NO_PREFERRED_TUNER);
+ }
+ else
+ {
+ mTunerNameComboBox.setSelectedItem(preferredTuner);
+ }
+ }
+ }
+
+ public void save()
+ {
+ if(hasItem() && isModified())
+ {
+ SourceConfigTunerMultipleFrequency config = new SourceConfigTunerMultipleFrequency();
+
+ String preferredTuner = (String)mTunerNameComboBox.getSelectedItem();
+
+ if(preferredTuner != null)
+ {
+ if(preferredTuner.equalsIgnoreCase(NO_PREFERRED_TUNER))
+ {
+ config.setPreferredTuner(null);
+ }
+ else if(preferredTuner.contains(UNAVAILABLE_TUNER))
+ {
+ //don't do anything - the config already contains this unavailable tuner name
+ }
+ else
+ {
+ config.setPreferredTuner(preferredTuner);
+ }
+ }
+
+ config.setFrequencies(mFrequencyEditor.getFrequencyModel().getFrequencies());
+
+ getItem().setSourceConfiguration(config);
+ }
+
+ setModified(false);
+ }
+
+ @Override
+ public void setItem(Channel item)
+ {
+ super.setItem(item);
+
+ mFrequencyEditor.getFrequencyModel().clear();
+
+ if(hasItem())
+ {
+ SourceConfiguration config = getItem().getSourceConfiguration();
+
+ if(config instanceof SourceConfigTunerMultipleFrequency)
+ {
+ SourceConfigTunerMultipleFrequency tunerConfig = (SourceConfigTunerMultipleFrequency) config;
+ updateTunerNameCombo(tunerConfig.getPreferredTuner());
+ mFrequencyEditor.getFrequencyModel().setFrequencies(tunerConfig.getFrequencies());
+ setModified(false);
+ }
+ }
+ else
+ {
+ setModified(false);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/ChannelRotationMonitor.java b/src/main/java/io/github/dsheirer/source/tuner/channel/ChannelRotationMonitor.java
new file mode 100644
index 00000000..90b715b0
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/channel/ChannelRotationMonitor.java
@@ -0,0 +1,182 @@
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 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 io.github.dsheirer.source.tuner.channel;
+
+import com.google.common.eventbus.Subscribe;
+import io.github.dsheirer.channel.state.State;
+import io.github.dsheirer.eventbus.MyEventBus;
+import io.github.dsheirer.identifier.Identifier;
+import io.github.dsheirer.identifier.IdentifierUpdateListener;
+import io.github.dsheirer.identifier.IdentifierUpdateNotification;
+import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
+import io.github.dsheirer.module.Module;
+import io.github.dsheirer.preference.PreferenceType;
+import io.github.dsheirer.preference.UserPreferences;
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.source.ISourceEventProvider;
+import io.github.dsheirer.source.SourceEvent;
+import io.github.dsheirer.util.ThreadPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Monitors channel state to detect when a channel is not in an identified active state and issues a request to rotate
+ * the next channel frequency in the list.
+ */
+public class ChannelRotationMonitor extends Module implements ISourceEventProvider, IdentifierUpdateListener,
+ Listener
+{
+ private final static Logger mLog = LoggerFactory.getLogger(ChannelRotationMonitor.class);
+
+ private UserPreferences mUserPreferences;
+ private Collection mActiveStates;
+ private ScheduledFuture> mScheduledFuture;
+ private Listener mSourceEventListener;
+ private long mRotationDelay;
+ private boolean mActive;
+ private long mInactiveTimestamp = System.currentTimeMillis();
+
+ /**
+ * Constructs a channel rotation monitor.
+ *
+ * @param activeStates to monitor
+ * @param userPreferences to receive updates for rotation delay value from user preferences
+ */
+ public ChannelRotationMonitor(Collection activeStates, UserPreferences userPreferences)
+ {
+ mActiveStates = activeStates;
+ mUserPreferences = userPreferences;
+ mRotationDelay = mUserPreferences.getChannelMultiFrequencyPreference().getRotationDelay();
+
+ //Register to receive user preference updates
+ MyEventBus.getEventBus().register(this);
+ }
+
+ /**
+ * Registers the external listener to receive frequency rotation requests from this module
+ */
+ @Override
+ public void setSourceEventListener(Listener listener)
+ {
+ mSourceEventListener = listener;
+ }
+
+ /**
+ * Unregisters the external listener from receiving frequency rotation requests.
+ */
+ @Override
+ public void removeSourceEventListener()
+ {
+ mSourceEventListener = null;
+ }
+
+ @Override
+ public Listener getIdentifierUpdateListener()
+ {
+ return this;
+ }
+
+ /**
+ * Primary method for receiving channel state identifier notifications, enabling this module to track the
+ * current channel state being provided by the Channel State module.
+ */
+ @Override
+ public void receive(IdentifierUpdateNotification identifierUpdateNotification)
+ {
+ Identifier identifier = identifierUpdateNotification.getIdentifier();
+
+ if(identifier instanceof ChannelStateIdentifier)
+ {
+ State currentState = ((ChannelStateIdentifier)identifier).getValue();
+
+ if(mActiveStates.contains(currentState))
+ {
+ mActive = true;
+ }
+ else if(mActive)
+ {
+ mActive = false;
+ mInactiveTimestamp = System.currentTimeMillis();
+ }
+ }
+ }
+
+ /**
+ * Checks the current active state and when inactive for longer than the specified delay, issues a
+ * channel frequency rotation request
+ */
+ private void checkState()
+ {
+ if(mSourceEventListener != null && !mActive &&
+ ((mInactiveTimestamp + mRotationDelay) < System.currentTimeMillis()))
+ {
+ mSourceEventListener.receive(SourceEvent.frequencyRotationRequest());
+ mInactiveTimestamp = System.currentTimeMillis();
+ }
+ }
+
+ /**
+ * Guava event bus method to receive notifications when user preferences are updated. This allows us to
+ * dynamically adjust rotation delay if/when the user changes the rotation delay value.
+ */
+ @Subscribe
+ public void preferenceUpdated(PreferenceType preferenceType)
+ {
+ if(preferenceType == PreferenceType.MULTI_FREQUENCY)
+ {
+ mRotationDelay = mUserPreferences.getChannelMultiFrequencyPreference().getRotationDelay();
+ }
+ }
+
+ @Override
+ public void reset()
+ {
+
+ }
+
+ @Override
+ public void start()
+ {
+ if(mScheduledFuture == null)
+ {
+ mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> checkState(), 1, 1, TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ public void stop()
+ {
+ if(mScheduledFuture != null)
+ {
+ mScheduledFuture.cancel(true);
+ }
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+}
+
diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java
new file mode 100644
index 00000000..eab2d87e
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java
@@ -0,0 +1,315 @@
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 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 io.github.dsheirer.source.tuner.channel;
+
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
+import io.github.dsheirer.source.Source;
+import io.github.dsheirer.source.SourceEvent;
+import io.github.dsheirer.source.SourceException;
+import io.github.dsheirer.source.heartbeat.Heartbeat;
+import io.github.dsheirer.source.tuner.TunerModel;
+import io.github.dsheirer.util.ThreadPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Multiple-frequency tuner channel source. Provides a wrapper around a tuner channel source and listens for external
+ * source events requests to change frequency. Maintains an ordered list of frequencies and automatically tears down
+ * an existing tuner channel source and obtains a new one with the next frequency from the list, on request.
+ */
+public class MultiFrequencyTunerChannelSource extends TunerChannelSource
+{
+ private final static Logger mLog = LoggerFactory.getLogger(MultiFrequencyTunerChannelSource.class);
+
+ private TunerModel mTunerModel;
+ private TunerChannelSource mTunerChannelSource;
+ private List mFrequencies;
+ private int mFrequencyListPointer = 0;
+ private ChannelSpecification mChannelSpecification;
+ private Listener mReusableComplexBufferListener;
+ private Listener mHeartbeatListener;
+ private String mPreferredTuner;
+ private AtomicBoolean mChangingChannels = new AtomicBoolean();
+ private boolean mStarted;
+ private ConsumerSourceEventAdapter mConsumerSourceEventAdapter = new ConsumerSourceEventAdapter();
+
+ public MultiFrequencyTunerChannelSource(TunerModel tunerModel, TunerChannelSource tunerChannelSource,
+ List frequencies, ChannelSpecification channelSpecification,
+ String preferredTuner)
+ {
+ super(null, tunerChannelSource.getTunerChannel());
+ mTunerModel = tunerModel;
+ mTunerChannelSource = tunerChannelSource;
+ mTunerChannelSource.setSourceEventListener(mConsumerSourceEventAdapter);
+ mFrequencies = frequencies;
+ mChannelSpecification = channelSpecification;
+ mPreferredTuner = preferredTuner;
+ }
+
+ /**
+ * Cycles this source to use the next frequency in the list
+ */
+ private void nextFrequency()
+ {
+ if(mChangingChannels.compareAndSet(false, true))
+ {
+ //Only change if we have more than one frequency specified
+ if(mFrequencies.size() > 0)
+ {
+ if(mTunerChannelSource != null)
+ {
+ //Shutdown the existing tuner channel source
+ mTunerChannelSource.stop();
+ mTunerChannelSource.removeListener(mReusableComplexBufferListener);
+ mTunerChannelSource.removeSourceEventListener();
+ mTunerChannelSource.removeHeartbeatListener(mHeartbeatListener);
+ mTunerChannelSource.dispose();
+ mTunerChannelSource = null;
+ }
+
+ //Request the next tuner channel source
+ getNextSource();
+ }
+ }
+ }
+
+ /**
+ * Persistently attempts to get the next tuner channel source using the next frequency in the list. This method
+ * should only be invoked by the nextFrequency() method that protects access via the mChangingChannels flag.
+ *
+ * Note: if this channel source is shutdown by the external consumer, the mStarted flag will be set to false and
+ * the persistent attempts will stop.
+ */
+ private void getNextSource()
+ {
+ if(mStarted)
+ {
+ TunerChannel nextChannel = getNextChannel();
+ Source source = mTunerModel.getSource(nextChannel, mChannelSpecification, mPreferredTuner);
+
+ if(source instanceof TunerChannelSource)
+ {
+ mTunerChannelSource = (TunerChannelSource)source;
+ mTunerChannelSource.setSourceEventListener(mConsumerSourceEventAdapter);
+ mTunerChannelSource.setListener(mReusableComplexBufferListener);
+ mTunerChannelSource.addHeartbeatListener(mHeartbeatListener);
+ mTunerChannelSource.start();
+ mChangingChannels.set(false);
+ getSourceEventListener().receive(SourceEvent.frequencyRotationSuccessNotification(this, nextChannel.getFrequency()));
+ }
+
+ //If we don't get a channel source because a tuner is not available or none of the available tuners can
+ //support the frequency, then persistently attempt to get a source by iterating the frequency list
+ if(mTunerChannelSource == null)
+ {
+ getSourceEventListener().receive(SourceEvent.frequencyRotationFailureNotification(this, nextChannel.getFrequency()));
+ ThreadPool.SCHEDULED.schedule(() -> getNextSource(), 1, TimeUnit.SECONDS);
+ }
+ }
+ }
+
+ @Override
+ public void start()
+ {
+ //The initial source should not be null
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.start();
+ mStarted = true;
+ }
+ }
+
+ @Override
+ public void stop()
+ {
+ mStarted = false;
+
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.stop();
+ mTunerChannelSource.removeSourceEventListener();
+ }
+ }
+
+ /**
+ * Provides the next tuner channel from the frequency list.
+ */
+ private TunerChannel getNextChannel()
+ {
+ mFrequencyListPointer++;
+
+ if(mFrequencyListPointer >= mFrequencies.size())
+ {
+ mFrequencyListPointer = 0;
+ }
+
+ long frequency = mFrequencies.get(mFrequencyListPointer);
+
+ return new TunerChannel(frequency, mChannelSpecification.getBandwidth());
+ }
+
+ /**
+ * Adds the heartbeat listener to receive heartbeats from the enclosed tuner channel source.
+ */
+ @Override
+ public void addHeartbeatListener(Listener listener)
+ {
+ mHeartbeatListener = listener;
+
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.addHeartbeatListener(listener);
+ }
+ }
+
+ /**
+ * Removes the heartbeat listener from receiving heartbeats from the enclosed tuner channel source
+ */
+ @Override
+ public void removeHeartbeatListener(Listener listener)
+ {
+ mHeartbeatListener = null;
+
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.removeHeartbeatListener(listener);
+ }
+ }
+
+ @Override
+ public void setFrequency(long frequency)
+ {
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.setFrequency(frequency);
+ }
+ }
+
+ @Override
+ protected void setSampleRate(double sampleRate)
+ {
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.setSampleRate(sampleRate);
+ }
+ }
+
+ @Override
+ protected void setChannelFrequencyCorrection(long correction)
+ {
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.setChannelFrequencyCorrection(correction);
+ }
+ }
+
+ @Override
+ public long getChannelFrequencyCorrection()
+ {
+ if(mTunerChannelSource != null)
+ {
+ return mTunerChannelSource.getChannelFrequencyCorrection();
+ }
+
+ return 0;
+ }
+
+ @Override
+ protected void processSamples()
+ {
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.processSamples();
+ }
+ }
+
+ @Override
+ public void setListener(Listener complexBufferListener)
+ {
+ mReusableComplexBufferListener = complexBufferListener;
+
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.setListener(complexBufferListener);
+ }
+ }
+
+ @Override
+ public void removeListener(Listener listener)
+ {
+ if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.removeListener(listener);
+ }
+
+ mReusableComplexBufferListener = null;
+ }
+
+ @Override
+ public double getSampleRate()
+ {
+ if(mTunerChannelSource != null)
+ {
+ return mTunerChannelSource.getSampleRate();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Overrides the parent method so that we can intercept external requests to change to the next frequency in the
+ * frequency list.
+ *
+ * @param sourceEvent to process
+ * @throws SourceException
+ */
+ @Override
+ public void process(SourceEvent sourceEvent) throws SourceException
+ {
+ if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_FREQUENCY_ROTATION)
+ {
+ nextFrequency();
+ }
+ else if(mTunerChannelSource != null)
+ {
+ mTunerChannelSource.process(sourceEvent);
+ }
+ }
+
+ /**
+ * Listener for source events produced by the enclosed tuner channel source. This adapter receives events from
+ * the wrapped tuner channel source and rebroadcasts them as consumer source events from this enclosing multi-frequency
+ * tuner channel source and to the registered external consumer source event listener.
+ */
+ public class ConsumerSourceEventAdapter implements Listener
+ {
+ @Override
+ public void receive(SourceEvent sourceEvent)
+ {
+ broadcastConsumerSourceEvent(sourceEvent);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java
index 0f429aed..e5c23226 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java
@@ -1,6 +1,7 @@
-/*******************************************************************************
+/*
+ * ******************************************************************************
* sdrtrunk
- * Copyright (C) 2014-2017 Dennis Sheirer
+ * Copyright (C) 2014-2019 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
@@ -14,8 +15,8 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * *****************************************************************************
+ */
package io.github.dsheirer.source.tuner.channel;
import io.github.dsheirer.sample.Listener;
@@ -117,7 +118,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
public void start()
{
//Broadcast current frequency and sample rate so consumer can configure correctly
- broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency()));
+ broadcastConsumerSourceEvent(SourceEvent.frequencyChange(this, getFrequency(), "Startup"));
broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this));
mScheduledIntervalProcessor.start();
}
@@ -186,6 +187,10 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setChannelFrequencyCorrection(sourceEvent.getValue().longValue());
break;
+ case NOTIFICATION_FREQUENCY_ROTATION_FAILURE:
+ case NOTIFICATION_FREQUENCY_ROTATION_SUCCESS:
+ //Ignore
+ break;
default:
mLog.error("Ignoring unrecognized source event: " + sourceEvent.getEvent() + " from [" +
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "") + "]");
diff --git a/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java b/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java
index 9631c710..9da7037b 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java
@@ -1,18 +1,22 @@
-/*******************************************************************************
- * sdr-trunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 Dennis Sheirer
*
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
- * later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * *****************************************************************************
+ */
package io.github.dsheirer.source.tuner.frequency;
import io.github.dsheirer.source.ISourceEventProcessor;
@@ -267,7 +271,7 @@ public class FrequencyController
*/
protected void broadcastFrequencyChange() throws SourceException
{
- broadcast(SourceEvent.frequencyChange(mFrequency));
+ broadcast(SourceEvent.frequencyChange(null, mFrequency));
}
/**
diff --git a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java
index 963db996..20b73a8e 100644
--- a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java
+++ b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java
@@ -1,18 +1,22 @@
-/*******************************************************************************
- * sdr-trunk
- * Copyright (C) 2014-2017 Dennis Sheirer
+/*
+ * ******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2019 Dennis Sheirer
*
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
- * later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * *****************************************************************************
+ */
package io.github.dsheirer.spectrum;
import com.jidesoft.swing.JideSplitPane;
@@ -445,7 +449,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener