Compare commits

...

47 Commits

Author SHA1 Message Date
Denny
5efd89632e Ensures that any leftover cached LDU1 message is reset when the call ends. 2017-05-28 07:30:05 -04:00
Denny
1dc3863489 Updates build to 0.3.0 Beta 12 2017-05-28 07:22:43 -04:00
Denny Sheirer
441286ba13 Merge pull request #247 from DSheirer/244-ldu1-encryption-flag
244 Prevent P25 Encrypted Audio Decoding/Playback
2017-05-28 11:20:20 +00:00
Denny
e4323df347 Resolves issue #244 - updates the P25 IMBE audio module to correctly
detect encrypted audio state so that encrypted audio is not (incorrectly)
decoded and played back to the user.

Processes HDU and LDU2 messages to identify encrypted call state.  Once
established, all subsequent LDU1/2 messages are decoded only if the call
is unencrypted.  If an LDU1 message is received out-of-sequence, meaning
that the initial HDU message was missed, then the LDU1 is cached until
we receive an LDU2 message and correctly determine the call encryption
state.
2017-05-28 07:17:09 -04:00
Denny
a872e0d664 Updates version to Beta 0.3.0 Beta 11 2017-05-20 07:24:27 -04:00
Denny Sheirer
fbc92cfe31 Merge pull request #246 from DSheirer/245-sound-card-source-issue
Using sound card or mixer as a source throws an error
2017-05-20 10:55:23 +00:00
Denny
1a432197ac Resolves #245 issue with NPE for frequency change event listener when using the sound card/mixer as a source. 2017-05-20 06:51:53 -04:00
Denny
fbc6e508bd Increments build to 0.3.0-beta10 2017-04-14 07:46:18 -04:00
Denny Sheirer
f9b6e67b00 Merge pull request #240 from DSheirer/234-long-alias-names-cause-audio-label-to-wrap
Long alias names cause audio label to wrap
2017-03-31 01:13:17 +00:00
Denny
a628800a71 Resolves #234 - updates the miglayout layout manager to allow jlabel text strings to be automatically truncated (with ellipses) when the alias name is too long. 2017-03-30 21:12:06 -04:00
Denny
25f71a76c4 Resolves #234 - hard codes alias name truncation when name exceeds 33 characters in length. 2017-03-30 20:03:48 -04:00
Denny
5fe8ccd478 Resolves #234 - change the order of assigning an icon and alias text to the audio panel in an attempt to prevent the audio channel panel from wrapping to the next line when the text is too long to display. 2017-03-30 19:52:35 -04:00
Denny Sheirer
069f951a86 Merge pull request #239 from DSheirer/228-traffic-channel-allocation-for-do-not-monitor
Traffic channel manager now allocates traffic channels for do not monitor aliases
2017-03-30 23:27:38 +00:00
Denny
dab6df7836 Resolves #228 - traffic channel manager now allocates traffic channels even when the alias is marked as do not monitor 2017-03-30 19:26:35 -04:00
Denny Sheirer
89da46b63c Merge pull request #238 from DSheirer/235-lojack-decoder-error-on-construction
Resolves error on constructing a LoJack decoder state.
2017-03-30 23:19:29 +00:00
Denny
5f2ce2a9f2 Resolves #235 - fixes error on constructing a LoJack decoder state. 2017-03-30 19:18:39 -04:00
Denny Sheirer
7a0b377971 Merge pull request #232 from DSheirer/229-p25-phase2-event-duplicates
Resolves #229 - duplicate P25 TDMA call events.
2017-03-23 21:52:47 +00:00
Denny
5dbe0ecce3 Resolves #229 - duplicate P25 TDMA call events. 2017-03-23 21:48:52 +00:00
Denny Sheirer
6647c581a6 Merge pull request #231 from DSheirer/230-p25-tdma-frequency-bands
P25 Phase II TDMA frequency bands are now displayed in details summary
2017-03-23 21:01:58 +00:00
Denny
a2ab5bfa95 Resolves #230 - P25 Phase II TDMA frequency bands are now displayed in the channel details summary. 2017-03-23 20:55:42 +00:00
Denny
ed486440b8 Merge remote-tracking branch 'origin/master' 2017-03-12 11:33:17 -04:00
Denny
13464e01fc Updates build to 0.3.0 Beta 9 2017-03-12 11:33:04 -04:00
Denny Sheirer
ea46a8b0e8 Merge pull request #225 from DSheirer/222-spectral-display-of-frequency-correction-offset
Frequency change event handling in the processing chain
2017-03-11 17:03:34 +00:00
Denny
d510c34356 Adds frequency change event listener handler to LSM decoder. 2017-03-11 12:01:45 -05:00
Denny
ae8a44caed Removes cic cleanup filter from processing chain. 2017-03-11 11:53:12 -05:00
Denny
1aa85a86ca Resolves issue with channel frequency correction events not being broadcast within the processing chain and rendering in the spectral display. 2017-03-11 10:16:08 -05:00
Denny Sheirer
17539d4d55 Merge pull request #224 from DSheirer/223-now-playing-frequency-format
Now Playing frequency display formatting
2017-03-11 11:14:58 +00:00
Denny
1db1d28390 Resolves issue with frequency display formating in the Now Playing window. 2017-03-11 06:13:59 -05:00
Denny Sheirer
37a941e904 Merge pull request #219 from DSheirer/218-audio-priority-
Resolves issue with audio playback allowing higher priority audio to preempt lower priority audio
2017-03-05 12:34:09 +00:00
Denny
57f6655824 Resolves issue where higher priority audio was not preempting lower priority audio. 2017-03-05 07:32:42 -05:00
Denny Sheirer
4cd8f8209f Merge pull request #216 from DSheirer/215-audio-recording-handles-part-2
215 audio recording handles part 2
2017-03-03 15:49:24 +00:00
Denny
61bfef5798 Incrementing build to version 0.3.0 beta8 2017-03-03 10:44:50 -05:00
Denny
ef172a04c6 Cleanup - removed synchronized blocks since the buffer processor thread is the only thread managing recorders. 2017-03-03 10:04:21 -05:00
Denny
a877afcceb Updates buffer processing in recorder manager so that we're not blocking any of the decoding channel producer threads that are trying to push audio packets to the recorder manager. Adds an overflowable packet queue and services the queue once a second followed by an idle recorder check (and disposal). 2017-03-03 10:01:32 -05:00
Denny
980ad72d4d Incrementing build to 0.3.0 beta7 2017-03-03 06:01:09 -05:00
Denny Sheirer
9b746cc032 Merge pull request #215 from DSheirer/198-audio-recorder-handles-left-open
Resolves issue audio recorders left open
2017-03-03 10:58:08 +00:00
Denny
f51ae0b5da Resolves issue with periodic instances of audio recorders not being shutdown correctly. Adds recorder monitor as a fail-safe to automatically shutdown recorders that don't receive an end-call indication. 2017-03-03 05:56:32 -05:00
Denny Sheirer
561078e9bf Merge pull request #214 from DSheirer/210-playlist-saving-all-times
Resolves issue with the playlist saving all of the time
2017-03-03 00:34:25 +00:00
Denny
e052b01f9b Resolves issue with the playlist saving all of the time due to audio streaming broadcast events. 2017-03-02 19:33:10 -05:00
Denny
09cf4fcfd7 Resolves issue with empty string list models in channel editor 2017-02-26 10:29:14 -05:00
Denny
5e731d0e9f Resolves issue with alias list and group combo box value editing. 2017-02-26 06:51:58 -05:00
Denny Sheirer
670a02b0ba Merge pull request #209 from DSheirer/153-cursor-frequency-artifacts
Resolves issue with spectral display rendering artifacts
2017-02-26 10:48:59 +00:00
Denny
9ef156b061 Resolves issue with spectral display rendering artifacts from cursor frequency readout when accessing menu items directly above the spectral display. 2017-02-26 05:47:26 -05:00
Denny
d9cf1b19f3 Removes assert() usage throughout project and replaces with Validate from apache commons. 2017-02-25 16:03:35 -05:00
Denny
a4a339b505 Resolves issue with FCD2 tuner frequency. 2017-02-25 14:58:51 -05:00
Denny
119370c46e Resolves issue with FCD2 tuner frequency. 2017-02-25 14:57:25 -05:00
Denny
4eb66e6045 Updated readme file with screen shot and link to new Version 0.3.0 user's manual. 2017-02-25 06:18:59 -05:00
62 changed files with 3526 additions and 3030 deletions

View File

@@ -2,18 +2,10 @@
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR). A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted) * [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/UserManual) * [User's Manual Version 0.2.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_Version0.3.0)
* [Download](https://github.com/DSheirer/sdrtrunk/releases) * [Download](https://github.com/DSheirer/sdrtrunk/releases)
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk) * [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
![](https://github.com/DSheirer/sdrtrunk/wiki/images/SDRTrunk_application.png) ![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/images/ApplicationOverview_V0.3.0.png)
<script> **Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-67489234-1', 'auto');
ga('send', 'pageview');
</script>

View File

@@ -7,7 +7,7 @@
<property name="major" value="0"/> <property name="major" value="0"/>
<property name="minor" value="3"/> <property name="minor" value="3"/>
<property name="patch" value="0"/> <property name="patch" value="0"/>
<property name="release" value="-beta6"/> <property name="release" value="-beta12"/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/> <property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<!-- Build properties --> <!-- Build properties -->

View File

@@ -40,10 +40,11 @@ public class AliasNameEditor extends Editor<Alias>
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static ComboBoxModel<String> EMPTY_MODEL = new DefaultComboBoxModel<>(); private static ComboBoxModel<String> EMPTY_LIST_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_GROUP_MODEL = new DefaultComboBoxModel<>();
private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_MODEL); private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_LIST_MODEL);
private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_MODEL); private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_GROUP_MODEL);
private JComboBox<Icon> mIconCombo; private JComboBox<Icon> mIconCombo;
private JTextField mName; private JTextField mName;
private JButton mButtonColor; private JButton mButtonColor;
@@ -110,7 +111,7 @@ public class AliasNameEditor extends Editor<Alias>
if(groups.isEmpty()) if(groups.isEmpty())
{ {
mGroupCombo.setModel(EMPTY_MODEL); mGroupCombo.setModel(EMPTY_GROUP_MODEL);
} }
else else
{ {
@@ -238,7 +239,7 @@ public class AliasNameEditor extends Editor<Alias>
if(listNames.isEmpty()) if(listNames.isEmpty())
{ {
mListCombo.setModel(EMPTY_MODEL); mListCombo.setModel(EMPTY_LIST_MODEL);
} }
else else
{ {
@@ -253,7 +254,7 @@ public class AliasNameEditor extends Editor<Alias>
if(groupNames.isEmpty()) if(groupNames.isEmpty())
{ {
mGroupCombo.setModel(EMPTY_MODEL); mGroupCombo.setModel(EMPTY_GROUP_MODEL);
} }
else else
{ {
@@ -285,8 +286,8 @@ public class AliasNameEditor extends Editor<Alias>
} }
else else
{ {
mListCombo.setModel(EMPTY_MODEL); mListCombo.setModel(EMPTY_LIST_MODEL);
mGroupCombo.setModel(EMPTY_MODEL); mGroupCombo.setModel(EMPTY_GROUP_MODEL);
mName.setText(null); mName.setText(null);
mButtonColor.setBackground(getBackground()); mButtonColor.setBackground(getBackground());

View File

@@ -18,6 +18,8 @@
******************************************************************************/ ******************************************************************************/
package alias.id; package alias.id;
import org.apache.commons.lang3.Validate;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class WildcardID implements Comparable<WildcardID> public class WildcardID implements Comparable<WildcardID>
@@ -45,7 +47,7 @@ public class WildcardID implements Comparable<WildcardID>
*/ */
public WildcardID(String value) public WildcardID(String value)
{ {
assert(value != null && value.contains(WILDCARD) && value.length() < 10); Validate.isTrue(value != null && value.contains(WILDCARD) && value.length() < 10);
mValue = value; mValue = value;

View File

@@ -90,7 +90,8 @@ public class AudioChannelPanel extends JPanel
private void init() 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); setBackground(mBackgroundColor);
mMutedLabel.setFont(mFont); mMutedLabel.setFont(mFont);
@@ -109,7 +110,7 @@ public class AudioChannelPanel extends JPanel
mTo.setFont(mFont); mTo.setFont(mFont);
mTo.setForeground(mValueColor); mTo.setForeground(mValueColor);
add(mTo); add(mTo,"wmin 10lp");
} }
@Override @Override
@@ -159,30 +160,40 @@ public class AudioChannelPanel extends JPanel
* does not occur on the Swing event thread -- wrap any calls to this * does not occur on the Swing event thread -- wrap any calls to this
* method with an event thread call. * 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(value != null)
{ {
if(alias != null) if(alias != null)
{ {
valueLabel.setText(alias.getName()); iconLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18));
aliasLabel.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 else
{ {
valueLabel.setText(value); iconLabel.setIcon(null);
aliasLabel.setIcon(null); textLabel.setText(value);
} }
} }
else else
{ {
valueLabel.setText("-----"); iconLabel.setIcon(null);
aliasLabel.setIcon(null); textLabel.setText("-----");
} }
} }
/** /**
* Processes audio metadata to update this panel's display values * Processes audio metadata to update this panel's display values
*/ */

View File

@@ -33,8 +33,7 @@ public class AudioChannelsPanel extends JPanel
public AudioChannelsPanel(IconManager iconManager, SettingsManager settingsManager, IAudioController controller) 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 abc,grow,fill][][sg abc,grow,fill]", "[grow,fill]"));
setLayout(new MigLayout("insets 0 0 0 0", "[][sg,grow,fill][][sg,grow,fill]", "[grow,fill]"));
setBackground(Color.BLACK); setBackground(Color.BLACK);
@@ -44,7 +43,7 @@ public class AudioChannelsPanel extends JPanel
for(int x = 0; x < outputs.size(); x++) 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) if(x < outputs.size() - 1)
{ {

View File

@@ -86,6 +86,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
loadSettings(); loadSettings();
} }
/**
* Loads the saved mixer configuration or a default configuration for audio playback.
*/
private void loadSettings() private void loadSettings()
{ {
MixerChannelConfiguration configuration = null; MixerChannelConfiguration configuration = null;
@@ -132,6 +135,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
} }
} }
/**
* Creates a default audio playback configuration with a mono audio playback channel.
*/
private MixerChannelConfiguration getDefaultConfiguration() private MixerChannelConfiguration getDefaultConfiguration()
{ {
/* Use the system default mixer and mono channel as default startup */ /* Use the system default mixer and mono channel as default startup */
@@ -161,6 +167,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mAudioOutputConnections.clear(); mAudioOutputConnections.clear();
} }
/**
* Primary ingest point for audio produced by all decoding channels, for distribution to audio playback devices.
*/
@Override @Override
public synchronized void receive(AudioPacket packet) public synchronized void receive(AudioPacket packet)
{ {
@@ -176,11 +185,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
for(AudioOutputConnection connection : mAudioOutputConnections) for(AudioOutputConnection connection : mAudioOutputConnections)
{ {
if(connection.isInactive() && if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
{ {
mChannelConnectionMap.remove(connection.getChannelMetadataID()); mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.disconnect(); connection.disconnect();
mAvailableConnectionCount++;
changed = true; changed = true;
} }
} }
@@ -192,7 +201,7 @@ public class AudioManager implements Listener<AudioPacket>, 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() private void updateLowestPriorityAssignment()
{ {
@@ -201,19 +210,32 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
for(AudioOutputConnection connection : mAudioOutputConnections) for(AudioOutputConnection connection : mAudioOutputConnections)
{ {
if(connection.isConnected() && if(connection.isConnected() &&
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() > connection.getPriority())) (mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() < connection.getPriority()))
{ {
mLowestPriorityConnection = connection; 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 @Override
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
{ {
setMixerChannelConfiguration(entry, true); 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 public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
{ {
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO)) if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
@@ -266,9 +288,8 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
} }
/** /**
* Clears all channel assignments and terminates all audio outputs in * Clears all channel assignments and terminates all audio outputs in preparation for complete shutdown or change
* preparation for complete shutdown or change to another mixer/channel * to another mixer/channel configuration
* configuration
*/ */
private void disposeCurrentConfiguration() private void disposeCurrentConfiguration()
{ {
@@ -288,12 +309,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mLowestPriorityConnection = null; mLowestPriorityConnection = null;
} }
/**
* Current audio playback mixer channel configuration setting.
*/
@Override @Override
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
{ {
return mMixerChannelConfiguration; return mMixerChannelConfiguration;
} }
/**
* List of audio outputs available for the current mixer channel configuration
*/
@Override @Override
public List<AudioOutput> getAudioOutputs() public List<AudioOutput> getAudioOutputs()
{ {
@@ -311,12 +338,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
return outputs; return outputs;
} }
/**
* Adds an audio event listener to receive audio event notifications.
*/
@Override @Override
public void addControllerListener(Listener<AudioEvent> listener) public void addControllerListener(Listener<AudioEvent> listener)
{ {
mControllerBroadcaster.addListener(listener); mControllerBroadcaster.addListener(listener);
} }
/**
* Removes an audio event listener from receiving audio event notifications.
*/
@Override @Override
public void removeControllerListener(Listener<AudioEvent> listener) public void removeControllerListener(Listener<AudioEvent> listener)
{ {
@@ -324,16 +357,15 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
} }
/** /**
* Returns an audio output connection for the packet if one is * Returns an audio output connection for the packet if one is available, or overrides an existing lower priority
* available, or overrides an existing lower priority connection. * connection. Returns null if no connection is available for the audio packet.
* 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 * @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 //Use an existing connection
if(mChannelConnectionMap.containsKey(channelMetadataID)) if(mChannelConnectionMap.containsKey(channelMetadataID))
@@ -341,37 +373,35 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
return mChannelConnectionMap.get(channelMetadataID); return mChannelConnectionMap.get(channelMetadataID);
} }
//Connect to and use an available connection //Connect to an unused, available connection
if(mAvailableConnectionCount > 0) if(mAvailableConnectionCount > 0)
{ {
for(AudioOutputConnection connection : mAudioOutputConnections) for(AudioOutputConnection connection : mAudioOutputConnections)
{ {
if(connection.isDisconnected()) if(connection.isDisconnected())
{ {
connection.connect(channelMetadataID); connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority());
mChannelConnectionMap.put(channelMetadataID, connection); mChannelConnectionMap.put(channelMetadataID, connection);
mAvailableConnectionCount--;
return connection; return connection;
} }
} }
} }
//Preempt an existing connection and connect it to this higher priority source //Preempt an existing lower priority connection and connect when this is a higher priority packet
else //Check for a channel priority override else
{ {
int priority = packet.getMetadata().getAudioPriority(); int priority = audioPacket.getMetadata().getAudioPriority();
if(mLowestPriorityConnection != null && AudioOutputConnection connection = mLowestPriorityConnection;
priority < mLowestPriorityConnection.getPriority())
if(connection != null && priority < connection.getPriority())
{ {
AudioOutputConnection connection = mLowestPriorityConnection;
mChannelConnectionMap.remove(connection.getChannelMetadataID()); mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.connect(channelMetadataID); connection.connect(channelMetadataID, priority);
mChannelConnectionMap.put(channelMetadataID, connection); mChannelConnectionMap.put(channelMetadataID, connection);
updateLowestPriorityAssignment();
return connection; return connection;
} }
} }
@@ -396,7 +426,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
for(AudioPacket packet : packets) 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) if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO)
{ {
AudioOutputConnection connection = getConnection(packet); AudioOutputConnection connection = getConnection(packet);
@@ -494,9 +524,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
* Connects this assignment to the indicated source so that audio * Connects this assignment to the indicated source so that audio
* packets from this source can be sent to the audio output * 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; mChannelMetadataID = source;
mPriority = priority;
updateLowestPriorityAssignment();
mAudioOutput.updateTimestamp(); mAudioOutput.updateTimestamp();
} }

View File

@@ -21,6 +21,7 @@ package audio.broadcast.shoutcast.v2;
import audio.broadcast.BroadcastConfiguration; import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat; import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastServerType; import audio.broadcast.BroadcastServerType;
import org.apache.commons.lang3.Validate;
import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlAttribute;
@@ -90,7 +91,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
*/ */
public void setStreamID(int streamID) public void setStreamID(int streamID)
{ {
assert(1 <= streamID && streamID <= Integer.MAX_VALUE); Validate.isTrue(1 <= streamID && streamID <= Integer.MAX_VALUE);
mStreamID = streamID; mStreamID = streamID;
} }

View File

@@ -18,6 +18,8 @@
******************************************************************************/ ******************************************************************************/
package audio.broadcast.shoutcast.v2.ultravox; package audio.broadcast.shoutcast.v2.ultravox;
import org.apache.commons.lang3.Validate;
public class NegotiateMaxPayloadSize extends UltravoxMessage public class NegotiateMaxPayloadSize extends UltravoxMessage
{ {
/** /**
@@ -47,8 +49,8 @@ public class NegotiateMaxPayloadSize extends UltravoxMessage
*/ */
public void setMaximumPayloadSize(int desiredMaximum, int minimum) public void setMaximumPayloadSize(int desiredMaximum, int minimum)
{ {
assert(desiredMaximum <= 16377); Validate.isTrue(desiredMaximum <= 16377);
assert(minimum <= 16377); Validate.isTrue(minimum <= 16377);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(desiredMaximum).append(":").append(minimum); sb.append(desiredMaximum).append(":").append(minimum);

View File

@@ -19,6 +19,7 @@
package audio.broadcast.shoutcast.v2.ultravox; package audio.broadcast.shoutcast.v2.ultravox;
import bits.BinaryMessage; import bits.BinaryMessage;
import org.apache.commons.lang3.Validate;
import java.util.Arrays; import java.util.Arrays;
@@ -154,7 +155,7 @@ public abstract class UltravoxMessage
*/ */
public void setSendQueuePriority(int priority) public void setSendQueuePriority(int priority)
{ {
assert(0 <= priority && priority <= 7); Validate.isTrue(0 <= priority && priority <= 7);
mMessage.setInt(priority, SEND_QUEUE_PRIORITY); mMessage.setInt(priority, SEND_QUEUE_PRIORITY);
} }

View File

@@ -22,6 +22,7 @@ import java.util.BitSet;
import java.util.logging.LogManager; import java.util.logging.LogManager;
import edac.CRC; import edac.CRC;
import org.apache.commons.lang3.Validate;
public class BinaryMessage extends BitSet public class BinaryMessage extends BitSet
{ {
@@ -456,7 +457,7 @@ public class BinaryMessage extends BitSet
*/ */
public byte getByte(int index) public byte getByte(int index)
{ {
assert((index + 7) <= size()); Validate.isTrue((index + 7) <= size());
int value = 0; int value = 0;
@@ -481,7 +482,7 @@ public class BinaryMessage extends BitSet
*/ */
public void setByte(int index, byte value) public void setByte(int index, byte value)
{ {
assert((index + 8) <= size()); Validate.isTrue((index + 8) <= size());
int mask = 0x80; int mask = 0x80;

View File

@@ -17,6 +17,8 @@
******************************************************************************/ ******************************************************************************/
package bits; package bits;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -39,7 +41,7 @@ public class MultiSyncPatternMatcher
public MultiSyncPatternMatcher( int syncSize ) public MultiSyncPatternMatcher( int syncSize )
{ {
assert( syncSize < 64 ); Validate.isTrue(syncSize < 64);
//Setup a bit mask of all ones the length of the sync pattern //Setup a bit mask of all ones the length of the sync pattern
mMask = (long)( ( Math.pow( 2, syncSize ) ) - 1 ); mMask = (long)( ( Math.pow( 2, syncSize ) ) - 1 );

View File

@@ -32,7 +32,7 @@ import java.util.Map;
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent> public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
{ {
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_STATE = 0;
public static final int COLUMN_DECODER = 1; public static final int COLUMN_DECODER = 1;

View File

@@ -26,6 +26,7 @@ import controller.channel.ChannelUtils;
import icon.IconManager; import icon.IconManager;
import module.ProcessingChain; import module.ProcessingChain;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import properties.SystemProperties; import properties.SystemProperties;
@@ -140,7 +141,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
public AliasedValueCellRenderer(Attribute attribute) public AliasedValueCellRenderer(Attribute attribute)
{ {
assert(attribute != null); Validate.isTrue(attribute != null);
mAttribute = attribute; mAttribute = attribute;
setHorizontalAlignment(JLabel.CENTER); setHorizontalAlignment(JLabel.CENTER);
} }

View File

@@ -36,14 +36,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sample.Listener; import sample.Listener;
import sample.real.IOverflowListener; import sample.real.IOverflowListener;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener, 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); 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 ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener; private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent; private CallEvent mTrafficChannelCallEvent;
private FrequencyChangeListener mFrequencyChangeListener;
private boolean mSquelchLocked = false; private boolean mSquelchLocked = false;
private boolean mSelected = false; private boolean mSelected = false;
@@ -189,6 +195,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mStateMonitor = null; mStateMonitor = null;
} }
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
if(mFrequencyChangeListener == null)
{
mFrequencyChangeListener = new FrequencyChangeListener();
}
return mFrequencyChangeListener;
}
private boolean isStandardChannel() private boolean isStandardChannel()
{ {
return mChannelType == ChannelType.STANDARD; return mChannelType == ChannelType.STANDARD;
@@ -532,6 +549,23 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
broadcast(mTrafficChannelCallEvent); broadcast(mTrafficChannelCallEvent);
} }
/**
* Listener to receive frequency change events and rebroadcast them to the decoder states
*/
public class FrequencyChangeListener implements Listener<FrequencyChangeEvent>
{
@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 * DecoderStateEvent receiver wrapper
*/ */

View File

@@ -63,13 +63,11 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
private Listener<CallEvent> mCallEventListener; private Listener<CallEvent> mCallEventListener;
private ChannelModel mChannelModel; private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private DecodeConfiguration mDecodeConfiguration; private DecodeConfiguration mDecodeConfiguration;
private RecordConfiguration mRecordConfiguration; private RecordConfiguration mRecordConfiguration;
private String mSystem; private String mSystem;
private String mSite; private String mSite;
private String mAliasListName; private String mAliasListName;
private CallEvent mPreviousDoNotMonitorCallEvent;
/** /**
* Monitors call events and allocates traffic decoder channels in response * Monitors call events and allocates traffic decoder channels in response
@@ -77,7 +75,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
* traffic channel allocations. * traffic channel allocations.
* *
* @param channelModel containing channels currently in use * @param channelModel containing channels currently in use
* @param channelProcessingManager
* @param decodeConfiguration - decoder configuration to use for each * @param decodeConfiguration - decoder configuration to use for each
* traffic channel allocation. * traffic channel allocation.
* @param recordConfiguration - recording options for each traffic channel * @param recordConfiguration - recording options for each traffic channel
@@ -88,7 +85,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
* in the pool * in the pool
*/ */
public TrafficChannelManager(ChannelModel channelModel, public TrafficChannelManager(ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
DecodeConfiguration decodeConfiguration, DecodeConfiguration decodeConfiguration,
RecordConfiguration recordConfiguration, RecordConfiguration recordConfiguration,
String system, String system,
@@ -97,7 +93,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
int trafficChannelPoolSize) int trafficChannelPoolSize)
{ {
mChannelModel = channelModel; mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mDecodeConfiguration = decodeConfiguration; mDecodeConfiguration = decodeConfiguration;
mRecordConfiguration = recordConfiguration; mRecordConfiguration = recordConfiguration;
mSystem = system; mSystem = system;
@@ -120,7 +115,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
mCallEventListener = null; mCallEventListener = null;
mDecodeConfiguration = null; mDecodeConfiguration = null;
mPreviousDoNotMonitorCallEvent = null;
} }
/** /**
@@ -197,21 +191,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
long frequency = callEvent.getFrequency(); long frequency = callEvent.getFrequency();
/* Check the from/to aliases for do not monitor priority */ if(frequency > 0)
if(isDoNotMonitor(callEvent))
{
callEvent.setCallEventType(CallEventType.CALL_DO_NOT_MONITOR);
if(isSameCallEvent(mPreviousDoNotMonitorCallEvent, callEvent))
{
return;
}
else
{
mPreviousDoNotMonitorCallEvent = callEvent;
}
}
else if(frequency > 0)
{ {
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(Type.TRAFFIC, Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(Type.TRAFFIC,
frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth())); frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
@@ -330,31 +310,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
return false; 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 @Override
public Listener<DecoderStateEvent> getDecoderStateListener() public Listener<DecoderStateEvent> getDecoderStateListener()
{ {

View File

@@ -1,28 +1,24 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
package controller.channel; package controller.channel;
import javax.xml.bind.annotation.XmlAttribute; import controller.config.Configuration;
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 module.decode.DecoderFactory; import module.decode.DecoderFactory;
import module.decode.DecoderType; import module.decode.DecoderType;
import module.decode.config.AuxDecodeConfiguration; import module.decode.config.AuxDecodeConfiguration;
@@ -31,6 +27,7 @@ import module.log.EventLogType;
import module.log.config.EventLogConfiguration; import module.log.config.EventLogConfiguration;
import record.RecorderType; import record.RecorderType;
import record.config.RecordConfiguration; import record.config.RecordConfiguration;
import sample.Listener;
import source.SourceType; import source.SourceType;
import source.config.SourceConfigFactory; import source.config.SourceConfigFactory;
import source.config.SourceConfigRecording; import source.config.SourceConfigRecording;
@@ -40,424 +37,444 @@ import source.tuner.TunerChannel;
import source.tuner.TunerChannel.Type; import source.tuner.TunerChannel.Type;
import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event; import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeProcessor;
import controller.config.Configuration;
@XmlSeeAlso( { Configuration.class } ) import javax.xml.bind.annotation.XmlAttribute;
@XmlRootElement( name = "channel" ) import javax.xml.bind.annotation.XmlElement;
public class Channel extends Configuration implements IFrequencyChangeProcessor 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<FrequencyChangeEvent>
{ {
// Standard channels are persisted and traffic channels are temporary // Standard channels are persisted and traffic channels are temporary
public enum ChannelType { STANDARD, TRAFFIC }; public enum ChannelType
{
STANDARD, TRAFFIC
}
;
// Static unique channel identifier tracking // Static unique channel identifier tracking
private static int UNIQUE_ID = 0; private static int UNIQUE_ID = 0;
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration(); private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
private AuxDecodeConfiguration mAuxDecodeConfiguration = private AuxDecodeConfiguration mAuxDecodeConfiguration =
new AuxDecodeConfiguration(); new AuxDecodeConfiguration();
private SourceConfiguration mSourceConfiguration = private SourceConfiguration mSourceConfiguration =
SourceConfigFactory.getDefaultSourceConfiguration(); SourceConfigFactory.getDefaultSourceConfiguration();
private EventLogConfiguration mEventLogConfiguration = private EventLogConfiguration mEventLogConfiguration =
new EventLogConfiguration(); new EventLogConfiguration();
private RecordConfiguration mRecordConfiguration = new RecordConfiguration(); private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
private String mAliasListName; private String mAliasListName;
private String mSystem = "System"; private String mSystem = "System";
private String mSite = "Site"; private String mSite = "Site";
private String mName = "Channel"; private String mName = "Channel";
private boolean mEnabled; private boolean mEnabled;
private boolean mSelected; private boolean mSelected;
private TunerChannel mTunerChannel = null; private TunerChannel mTunerChannel = null;
private ChannelType mChannelType = ChannelType.STANDARD; private ChannelType mChannelType = ChannelType.STANDARD;
private int mChannelID; private int mChannelID;
private int mChannelFrequencyCorrection = 0; private int mChannelFrequencyCorrection = 0;
/** /**
* Channel represents a complete set of configurations needed to setup and * 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 * manage a processing chain and/or manage as a set of business objects that
* can be displayed in graphical components. * can be displayed in graphical components.
* *
* @param channelName * @param channelName
* @param channelType * @param channelType
*/ */
public Channel( String channelName, ChannelType channelType ) public Channel(String channelName, ChannelType channelType)
{ {
this( channelName ); this(channelName);
mChannelType = channelType; mChannelType = channelType;
} }
/** /**
* Constructs a new standard channel with the specified channel name * Constructs a new standard channel with the specified channel name
*/ */
public Channel( String channelName ) public Channel(String channelName)
{ {
this(); this();
mName = channelName; mName = channelName;
} }
/** /**
* Constructs a new standard channel with a default name of "Channel" * Constructs a new standard channel with a default name of "Channel"
*/ */
public Channel() public Channel()
{ {
mChannelID = UNIQUE_ID++; mChannelID = UNIQUE_ID++;
} }
/** /**
* Creates a (new) deep copy of this channel * Creates a (new) deep copy of this channel
*/ */
public Channel copyOf() public Channel copyOf()
{ {
Channel channel = new Channel( mName ); Channel channel = new Channel(mName);
channel.setSystem( mSystem ); channel.setSystem(mSystem);
channel.setSite( mSite ); channel.setSite(mSite);
channel.setAliasListName( mAliasListName ); channel.setAliasListName(mAliasListName);
AuxDecodeConfiguration aux = new AuxDecodeConfiguration(); AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
for( DecoderType auxType: aux.getAuxDecoders() ) for(DecoderType auxType : aux.getAuxDecoders())
{ {
aux.addAuxDecoder( auxType ); aux.addAuxDecoder(auxType);
} }
channel.setAuxDecodeConfiguration( aux ); channel.setAuxDecodeConfiguration(aux);
channel.setDecodeConfiguration( DecoderFactory.copy( mDecodeConfiguration ) ); channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
EventLogConfiguration log = new EventLogConfiguration(); EventLogConfiguration log = new EventLogConfiguration();
for( EventLogType logType: mEventLogConfiguration.getLoggers() ) for(EventLogType logType : mEventLogConfiguration.getLoggers())
{ {
log.addLogger( logType ); log.addLogger(logType);
} }
channel.setEventLogConfiguration( log ); channel.setEventLogConfiguration(log);
RecordConfiguration record = new RecordConfiguration(); RecordConfiguration record = new RecordConfiguration();
for( RecorderType recordType: mRecordConfiguration.getRecorders() ) for(RecorderType recordType : mRecordConfiguration.getRecorders())
{ {
record.addRecorder( recordType ); record.addRecorder(recordType);
} }
channel.setRecordConfiguration( record ); channel.setRecordConfiguration(record);
channel.setSourceConfiguration( SourceConfigFactory.copy( mSourceConfiguration ) ); channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
return channel; return channel;
} }
/** /**
* Unique identifier for this channel. Value is transient (not persisted). * Unique identifier for this channel. Value is transient (not persisted).
*/ */
@XmlTransient @XmlTransient
public int getChannelID() public int getChannelID()
{ {
return mChannelID; return mChannelID;
} }
/** /**
* Indicates if this channel has been selected for prioritized audio output * Indicates if this channel has been selected for prioritized audio output
* and display of processing chain products * and display of processing chain products
*/ */
@XmlTransient @XmlTransient
public boolean isSelected() public boolean isSelected()
{ {
return mSelected; return mSelected;
} }
/** /**
* Sets selection status of the channel. * Sets selection status of the channel.
* *
* NOTE: this method is package private so that only the ChannelSelectionManager * NOTE: this method is package private so that only the ChannelSelectionManager
* can toggle the selection state. Use the channel model to broadcast a * can toggle the selection state. Use the channel model to broadcast a
* request select/deselect channel event. * request select/deselect channel event.
* *
* @param selected * @param selected
*/ */
void setSelected( boolean selected ) void setSelected(boolean selected)
{ {
mSelected = selected; mSelected = selected;
} }
@XmlTransient @XmlTransient
public ChannelType getChannelType() public ChannelType getChannelType()
{ {
return mChannelType; return mChannelType;
} }
/** /**
* Returns the owning system for this channel. * Returns the owning system for this channel.
*/ */
@XmlAttribute( name = "system" ) @XmlAttribute(name = "system")
public String getSystem() public String getSystem()
{ {
return mSystem; return mSystem;
} }
public void setSystem( String system ) public void setSystem(String system)
{ {
mSystem = system; mSystem = system;
} }
public boolean hasSystem() public boolean hasSystem()
{ {
return mSystem != null; return mSystem != null;
} }
/** /**
* Returns the owning site for this channel. * Returns the owning site for this channel.
*/ */
@XmlAttribute( name = "site" ) @XmlAttribute(name = "site")
public String getSite() public String getSite()
{ {
return mSite; return mSite;
} }
public void setSite( String site ) public void setSite(String site)
{ {
mSite = site; mSite = site;
} }
public boolean hasSite() public boolean hasSite()
{ {
return mSite != null; return mSite != null;
} }
/** /**
* Returns the name of this channel. * Returns the name of this channel.
*/ */
@XmlAttribute( name = "name" ) @XmlAttribute(name = "name")
public String getName() public String getName()
{ {
return mName; return mName;
} }
/** /**
* Sets the name of this channel. * Sets the name of this channel.
*/ */
public void setName( String name ) public void setName(String name)
{ {
mName = name; mName = name;
} }
/** /**
* Default display string for this channel: SYSTEM_SITE_NAME * Default display string for this channel: SYSTEM_SITE_NAME
*/ */
public String toString() public String toString()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( hasSystem() ? getSystem() : "SYSTEM" ); sb.append(hasSystem() ? getSystem() : "SYSTEM");
sb.append( "_" ); sb.append("_");
sb.append( hasSite() ? getSite() : "SITE" ); sb.append(hasSite() ? getSite() : "SITE");
sb.append( "_" ); sb.append("_");
sb.append( getName() ); sb.append(getName());
return sb.toString(); return sb.toString();
} }
/** /**
* Indicates if this channel is enabled for processing. * Indicates if this channel is enabled for processing.
*/ */
@XmlAttribute( name = "enabled" ) @XmlAttribute(name = "enabled")
public boolean getEnabled() public boolean getEnabled()
{ {
return mEnabled; return mEnabled;
} }
/** /**
* Indicates if this channel will automatically start processing on * Indicates if this channel will automatically start processing on
* application startup or if this channel is currently processing after * application startup or if this channel is currently processing after
* application startup. * application startup.
* *
* Note: this method is package private and is intended to be managed * Note: this method is package private and is intended to be managed
* by a co-package central processing manager * by a co-package central processing manager
* *
* @see controller.ChannelProcessingManager * @see controller.channel.ChannelProcessingManager
*/ */
void setEnabled( boolean enabled ) void setEnabled(boolean enabled)
{ {
mEnabled = enabled; mEnabled = enabled;
} }
/** /**
* Returns the alias list that is used by this channel for looking up alias * Returns the alias list that is used by this channel for looking up alias
* values for the various identifiers produced by the decoder(s). * values for the various identifiers produced by the decoder(s).
*/ */
@XmlElement( name = "alias_list_name" ) @XmlElement(name = "alias_list_name")
public String getAliasListName() public String getAliasListName()
{ {
return mAliasListName; return mAliasListName;
} }
/** /**
* Sets the alias list to be used for looking up the alias values for the * Sets the alias list to be used for looking up the alias values for the
* various identifiers produced by the decoder * various identifiers produced by the decoder
*/ */
public void setAliasListName( String name ) public void setAliasListName(String name)
{ {
mAliasListName = name; mAliasListName = name;
} }
/** /**
* Gets the primary decoder configuration used by this channel * Gets the primary decoder configuration used by this channel
*/ */
@XmlElement( name = "decode_configuration" ) @XmlElement(name = "decode_configuration")
public DecodeConfiguration getDecodeConfiguration() public DecodeConfiguration getDecodeConfiguration()
{ {
return mDecodeConfiguration; return mDecodeConfiguration;
} }
/** /**
* Sets the primary decoder configuration used by this channel * Sets the primary decoder configuration used by this channel
*/ */
public void setDecodeConfiguration( DecodeConfiguration config ) public void setDecodeConfiguration(DecodeConfiguration config)
{ {
mDecodeConfiguration = config; mDecodeConfiguration = config;
} }
/** /**
* Gets the aux decoder configuration used by this channel. Aux decoders * Gets the aux decoder configuration used by this channel. Aux decoders
* operate on demodulated audio * operate on demodulated audio
*/ */
@XmlElement( name = "aux_decode_configuration" ) @XmlElement(name = "aux_decode_configuration")
public AuxDecodeConfiguration getAuxDecodeConfiguration() public AuxDecodeConfiguration getAuxDecodeConfiguration()
{ {
return mAuxDecodeConfiguration; return mAuxDecodeConfiguration;
} }
/** /**
* Sets the decoder configuration used by this channel * Sets the decoder configuration used by this channel
*/ */
public void setAuxDecodeConfiguration( AuxDecodeConfiguration config ) public void setAuxDecodeConfiguration(AuxDecodeConfiguration config)
{ {
mAuxDecodeConfiguration = config; mAuxDecodeConfiguration = config;
} }
/** /**
* Returns the source configuration for this channel. A channel source * Returns the source configuration for this channel. A channel source
* identifies where the processing chain will get source sample data from. * identifies where the processing chain will get source sample data from.
*/ */
@XmlElement( name = "source_configuration" ) @XmlElement(name = "source_configuration")
public SourceConfiguration getSourceConfiguration() public SourceConfiguration getSourceConfiguration()
{ {
return mSourceConfiguration; return mSourceConfiguration;
} }
/** /**
* Sets the source configuration for this channel. * Sets the source configuration for this channel.
*/ */
public void setSourceConfiguration( SourceConfiguration config ) public void setSourceConfiguration(SourceConfiguration config)
{ {
mSourceConfiguration = config; mSourceConfiguration = config;
//Clear the tune channel object so that it can be recreated if the //Clear the tune channel object so that it can be recreated if the
//source configuration changes //source configuration changes
mTunerChannel = null; mTunerChannel = null;
} }
/** /**
* Returns the event logger configuration for this channel. * Returns the event logger configuration for this channel.
*/ */
@XmlElement( name = "event_log_configuration" ) @XmlElement(name = "event_log_configuration")
public EventLogConfiguration getEventLogConfiguration() public EventLogConfiguration getEventLogConfiguration()
{ {
return mEventLogConfiguration; return mEventLogConfiguration;
} }
/** /**
* Sets the event logger configuration for this channel. * Sets the event logger configuration for this channel.
*/ */
public void setEventLogConfiguration( EventLogConfiguration config ) public void setEventLogConfiguration(EventLogConfiguration config)
{ {
mEventLogConfiguration = config; mEventLogConfiguration = config;
} }
/** /**
* Returns the recorder configuration for this channel. * Returns the recorder configuration for this channel.
*/ */
@XmlElement( name = "record_configuration" ) @XmlElement(name = "record_configuration")
public RecordConfiguration getRecordConfiguration() public RecordConfiguration getRecordConfiguration()
{ {
return mRecordConfiguration; return mRecordConfiguration;
} }
/** /**
* Sets the recorder configuration for this channel. * Sets the recorder configuration for this channel.
*/ */
public void setRecordConfiguration( RecordConfiguration config ) public void setRecordConfiguration(RecordConfiguration config)
{ {
mRecordConfiguration = config; mRecordConfiguration = config;
} }
/** /**
* Convenience method to construct a tuner channel object representing a * Convenience method to construct a tuner channel object representing a
* tuner or recording source frequency and bandwidth that can be used by * tuner or recording source frequency and bandwidth that can be used by
* application components for graphically representing this channel. * application components for graphically representing this channel.
* *
* If the source configuration is not a tuner or recording, this method * If the source configuration is not a tuner or recording, this method
* returns null. * returns null.
*/ */
@XmlTransient @XmlTransient
public TunerChannel getTunerChannel() public TunerChannel getTunerChannel()
{ {
if( mTunerChannel == null ) if(mTunerChannel == null)
{ {
if( mSourceConfiguration.getSourceType() == SourceType.TUNER ) if(mSourceConfiguration.getSourceType() == SourceType.TUNER)
{ {
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration; SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(), mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth() ); mDecodeConfiguration.getDecoderType().getChannelBandwidth());
} }
else if( mSourceConfiguration.getSourceType() == SourceType.RECORDING ) else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
{ {
SourceConfigRecording config = SourceConfigRecording config =
(SourceConfigRecording)mSourceConfiguration; (SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(), mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth() ); mDecodeConfiguration.getDecoderType().getChannelBandwidth());
} }
} }
return mTunerChannel; return mTunerChannel;
} }
/** /**
* Convenience method to indicate if any part of this channel is contained * Convenience method to indicate if any part of this channel is contained
* within the minimum and maximum frequency values. * within the minimum and maximum frequency values.
*/ */
public boolean isWithin( long minimum, long maximum ) public boolean isWithin(long minimum, long maximum)
{ {
TunerChannel tunerChannel = getTunerChannel(); TunerChannel tunerChannel = getTunerChannel();
return tunerChannel != null && return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
tunerChannel.overlaps( minimum, maximum ); }
}
/** /**
* Convenience method to make the current channel frequency correction value * Convenience method to make the current channel frequency correction value
* available for use outside of a constructed processing chain while maintaining * available for use outside of a constructed processing chain while maintaining
* linkage to the source channel. This hack allows the correction value to * linkage to the source channel. This hack allows the correction value to
* be used to visually show in the spectral display. * be used to visually show in the spectral display.
*/ */
@Override @Override
public void frequencyChanged( FrequencyChangeEvent event ) public void receive(FrequencyChangeEvent event)
{ {
if( event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE ) if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
{ {
mChannelFrequencyCorrection = event.getValue().intValue(); mChannelFrequencyCorrection = event.getValue().intValue();
} }
} }
@XmlTransient /**
public int getChannelFrequencyCorrection() * Current channel frequency correction value (when this channel is processing)
{ * @return
return mChannelFrequencyCorrection; */
} @XmlTransient
public int getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Resets frequency correction value to 0
*/
public void resetFrequencyCorrection()
{
mChannelFrequencyCorrection = 0;
}
} }

View File

@@ -231,7 +231,7 @@ public class ChannelProcessingManager implements ChannelEventListener
if(processingChain == null) if(processingChain == null)
{ {
processingChain = new ProcessingChain(channel.getName(), channel.getChannelType()); processingChain = new ProcessingChain(channel.getChannelType());
/* Register global listeners */ /* Register global listeners */
for(Listener<AudioPacket> listener : mAudioPacketListeners) for(Listener<AudioPacket> listener : mAudioPacketListeners)
@@ -244,6 +244,9 @@ public class ChannelProcessingManager implements ChannelEventListener
processingChain.addMessageListener(listener); processingChain.addMessageListener(listener);
} }
//Register channel to receive frequency correction events to show in the spectral display (hack!)
processingChain.addFrequencyChangeListener(channel);
/* Processing Modules */ /* Processing Modules */
List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this, List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this,
mAliasModel, channel, processingChain.getChannelState().getMutableMetadata()); mAliasModel, channel, processingChain.getChannelState().getMutableMetadata());
@@ -347,6 +350,9 @@ public class ChannelProcessingManager implements ChannelEventListener
processingChain.removeRecordingModules(); 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)); mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP));

View File

@@ -17,253 +17,251 @@
******************************************************************************/ ******************************************************************************/
package controller.channel; package controller.channel;
import alias.AliasModel;
import gui.editor.Editor; import gui.editor.Editor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.miginfocom.swing.MigLayout;
import alias.AliasModel;
public class NameConfigurationEditor extends Editor<Channel> implements DocumentListener public class NameConfigurationEditor extends Editor<Channel> implements DocumentListener
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( NameConfigurationEditor.class ); private final static Logger mLog = LoggerFactory.getLogger(NameConfigurationEditor.class);
private static ComboBoxModel<String> EMPTY_MODEL = new DefaultComboBoxModel<>(); private static ComboBoxModel<String> EMPTY_SYSTEM_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_SITE_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_ALIAS_LIST_MODEL = new DefaultComboBoxModel<>();
private AliasModel mAliasModel; private AliasModel mAliasModel;
private ChannelModel mChannelModel; private ChannelModel mChannelModel;
private JTextField mChannelName; private JTextField mChannelName;
private JComboBox<String> mSystemNameCombo; private JComboBox<String> mSystemNameCombo;
private JComboBox<String> mSiteNameCombo; private JComboBox<String> mSiteNameCombo;
private JComboBox<String> mAliasListCombo; private JComboBox<String> mAliasListCombo;
public NameConfigurationEditor( AliasModel aliasModel, ChannelModel model ) public NameConfigurationEditor(AliasModel aliasModel, ChannelModel model)
{ {
mAliasModel = aliasModel; mAliasModel = aliasModel;
mChannelModel = model; mChannelModel = model;
init(); init();
} }
private void init() private void init()
{ {
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]" ) ); setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]"));
mChannelName = new JTextField( "" ); mChannelName = new JTextField("");
mChannelName.getDocument().addDocumentListener( this ); mChannelName.getDocument().addDocumentListener(this);
mChannelName.setEnabled( false ); mChannelName.setEnabled(false);
add( new JLabel( "Name:" ) ); add(new JLabel("Name:"));
add( mChannelName ); add(mChannelName);
mSystemNameCombo = new JComboBox<>( EMPTY_MODEL ); mSystemNameCombo = new JComboBox<>(EMPTY_SYSTEM_MODEL);
mSystemNameCombo.setEnabled( false ); mSystemNameCombo.setEnabled(false);
mSystemNameCombo.setEditable( true ); mSystemNameCombo.setEditable(true);
mSystemNameCombo.addActionListener( new ActionListener() mSystemNameCombo.addActionListener(new ActionListener()
{ {
@Override @Override
public void actionPerformed( ActionEvent e ) public void actionPerformed(ActionEvent e)
{ {
Object selected = mSystemNameCombo.getSelectedItem(); Object selected = mSystemNameCombo.getSelectedItem();
if( selected != null ) if(selected != null)
{ {
String system = (String)selected; String system = (String)selected;
List<String> sites = mChannelModel.getSites( system ); List<String> sites = mChannelModel.getSites(system);
if( !sites.contains( getItem().getSite() ) ) if(!sites.contains(getItem().getSite()))
{ {
sites.add( getItem().getSite() ); sites.add(getItem().getSite());
} }
if( sites.isEmpty() ) if(sites.isEmpty())
{ {
mSiteNameCombo.setModel( EMPTY_MODEL ); mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
} }
else else
{ {
mSiteNameCombo.setModel( new DefaultComboBoxModel<String>( mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
sites.toArray( new String[ sites.size() ] ) ) );; sites.toArray(new String[sites.size()])));
} ;
}
mSiteNameCombo.setSelectedItem( getItem().getSite() ); mSiteNameCombo.setSelectedItem(getItem().getSite());
} }
setModified( true ); setModified(true);
} }
} ); });
add( new JLabel( "System:" ) ); add(new JLabel("System:"));
add( mSystemNameCombo ); add(mSystemNameCombo);
mAliasListCombo = new JComboBox<String>(); mAliasListCombo = new JComboBox<String>(EMPTY_ALIAS_LIST_MODEL);
mAliasListCombo.setEnabled( false ); mAliasListCombo.setEnabled(false);
mAliasListCombo.setEditable( true ); mAliasListCombo.setEditable(true);
mAliasListCombo.addActionListener( new ActionListener() mAliasListCombo.addActionListener(new ActionListener()
{ {
@Override @Override
public void actionPerformed( ActionEvent e ) public void actionPerformed(ActionEvent e)
{ {
setModified( true ); setModified(true);
} }
} ); });
add( new JLabel( "Alias List:" ) ); add(new JLabel("Alias List:"));
add( mAliasListCombo ); add(mAliasListCombo);
mSiteNameCombo = new JComboBox<>( EMPTY_MODEL ); mSiteNameCombo = new JComboBox<>(EMPTY_SITE_MODEL);
mSiteNameCombo.setEnabled( false ); mSiteNameCombo.setEnabled(false);
mSiteNameCombo.setEditable( true ); mSiteNameCombo.setEditable(true);
mSiteNameCombo.addActionListener( new ActionListener() mSiteNameCombo.addActionListener(new ActionListener()
{ {
@Override @Override
public void actionPerformed( ActionEvent e ) public void actionPerformed(ActionEvent e)
{ {
setModified( true ); setModified(true);
} }
} ); });
add( new JLabel( "Site:" ) ); add(new JLabel("Site:"));
add( mSiteNameCombo ); add(mSiteNameCombo);
} }
@Override @Override
public void save() public void save()
{ {
if( hasItem() && isModified() ) if(hasItem() && isModified())
{ {
getItem().setName( mChannelName.getText() ); getItem().setName(mChannelName.getText());
Object system = mSystemNameCombo.getSelectedItem(); Object system = mSystemNameCombo.getSelectedItem();
getItem().setSystem( ( system == null ? null : (String)system ) ); getItem().setSystem((system == null ? null : (String)system));
Object site = mSiteNameCombo.getSelectedItem(); Object site = mSiteNameCombo.getSelectedItem();
getItem().setSite( ( site == null ? null : (String)site ) ); getItem().setSite((site == null ? null : (String)site));
Object aliasList = mAliasListCombo.getSelectedItem(); Object aliasList = mAliasListCombo.getSelectedItem();
getItem().setAliasListName( aliasList == null ? null : (String)aliasList ); getItem().setAliasListName(aliasList == null ? null : (String)aliasList);
} }
setModified( false ); setModified(false);
} }
private void setControlsEnabled( boolean enabled ) private void setControlsEnabled(boolean enabled)
{ {
if( mChannelName.isEnabled() != enabled ) if(mChannelName.isEnabled() != enabled)
{ {
mChannelName.setEnabled( enabled ); mChannelName.setEnabled(enabled);
} }
if( mAliasListCombo.isEnabled() != enabled ) if(mAliasListCombo.isEnabled() != enabled)
{ {
mAliasListCombo.setEnabled( enabled ); mAliasListCombo.setEnabled(enabled);
} }
if( mSystemNameCombo.isEnabled() != enabled ) if(mSystemNameCombo.isEnabled() != enabled)
{ {
mSystemNameCombo.setEnabled( enabled ); mSystemNameCombo.setEnabled(enabled);
} }
if( mSiteNameCombo.isEnabled() != enabled ) if(mSiteNameCombo.isEnabled() != enabled)
{ {
mSiteNameCombo.setEnabled( enabled ); mSiteNameCombo.setEnabled(enabled);
} }
} }
@Override @Override
public void setItem( Channel channel ) public void setItem(Channel channel)
{ {
super.setItem( channel ); super.setItem(channel);
if( hasItem() ) if(hasItem())
{ {
setControlsEnabled( true ); setControlsEnabled(true);
mChannelName.setText( channel.getName() ); mChannelName.setText(channel.getName());
List<String> systems = mChannelModel.getSystems(); List<String> systems = mChannelModel.getSystems();
if( systems.isEmpty() ) if(systems.isEmpty())
{ {
mSystemNameCombo.setModel( EMPTY_MODEL ); mSystemNameCombo.setModel(EMPTY_SYSTEM_MODEL);
} }
else else
{ {
mSystemNameCombo.setModel( new DefaultComboBoxModel<String>( mSystemNameCombo.setModel(new DefaultComboBoxModel<String>(
systems.toArray( new String[ systems.size() ] ) ) );; systems.toArray(new String[systems.size()])));
} ;
}
mSystemNameCombo.setSelectedItem( channel.getSystem() ); mSystemNameCombo.setSelectedItem(channel.getSystem());
List<String> sites = mChannelModel.getSites( channel.getSystem() ); List<String> sites = mChannelModel.getSites(channel.getSystem());
if( sites.isEmpty() ) if(sites.isEmpty())
{ {
mSiteNameCombo.setModel( EMPTY_MODEL ); mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
} }
else else
{ {
mSiteNameCombo.setModel( new DefaultComboBoxModel<String>( mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
sites.toArray( new String[ sites.size() ] ) ) );; sites.toArray(new String[sites.size()])));
} ;
}
if( channel.getSite() != null ) if(channel.getSite() != null)
{ {
mSiteNameCombo.setSelectedItem( channel.getSite() ); mSiteNameCombo.setSelectedItem(channel.getSite());
} }
List<String> lists = mAliasModel.getListNames(); List<String> lists = mAliasModel.getListNames();
Collections.sort( lists ); Collections.sort(lists);
mAliasListCombo.setModel( new DefaultComboBoxModel<String>( mAliasListCombo.setModel(new DefaultComboBoxModel<String>(
lists.toArray( new String[ lists.size() ] ) ) ); lists.toArray(new String[lists.size()])));
if( channel.getAliasListName() != null ) if(channel.getAliasListName() != null)
{ {
mAliasListCombo.setSelectedItem( channel.getAliasListName() ); mAliasListCombo.setSelectedItem(channel.getAliasListName());
} }
} }
else else
{ {
setControlsEnabled( false ); setControlsEnabled(false);
mChannelName.setText( null ); mChannelName.setText(null);
} }
setModified( false ); setModified(false);
} }
@Override @Override
public void insertUpdate( DocumentEvent e ) public void insertUpdate(DocumentEvent e)
{ {
setModified( true ); setModified(true);
} }
@Override @Override
public void changedUpdate( DocumentEvent e ) public void changedUpdate(DocumentEvent e)
{ {
setModified( true ); setModified(true);
} }
@Override @Override
public void removeUpdate( DocumentEvent e ) public void removeUpdate(DocumentEvent e)
{ {
setModified( true ); setModified(true);
} }
} }

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList; import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener; import sample.real.RealSampleListener;
public class FloatFIRFilter implements RealSampleListener public class FloatFIRFilter implements RealSampleListener
@@ -139,7 +140,7 @@ public class FloatFIRFilter implements RealSampleListener
private void generateIndexMap( int size ) private void generateIndexMap( int size )
{ {
//Ensure we have an odd size //Ensure we have an odd size
assert( size % 2 == 1 ); Validate.isTrue(size % 2 == 1);
mIndexMap = new int[ size ][ size ]; mIndexMap = new int[ size ][ size ];

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList; import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener; import sample.real.RealSampleListener;
public class FloatHalfBandFilter implements RealSampleListener public class FloatHalfBandFilter implements RealSampleListener
@@ -149,7 +150,7 @@ public class FloatHalfBandFilter implements RealSampleListener
private void generateIndexMap( int size ) private void generateIndexMap( int size )
{ {
//Ensure we have an odd size //Ensure we have an odd size
assert( size % 2 == 1 ); Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1; int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList; import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener; import sample.real.RealSampleListener;
public class FloatHalfBandNoDecimateFilter implements RealSampleListener public class FloatHalfBandNoDecimateFilter implements RealSampleListener
@@ -139,7 +140,7 @@ public class FloatHalfBandNoDecimateFilter implements RealSampleListener
private void generateIndexMap( int size ) private void generateIndexMap( int size )
{ {
//Ensure we have an odd size //Ensure we have an odd size
assert( size % 2 == 1 ); Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1; int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -1,407 +1,412 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
package dsp.filter.cic; package dsp.filter.cic;
import java.util.ArrayList;
import java.util.List;
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.FilterFactory;
import dsp.filter.Filters; import dsp.filter.Filters;
import dsp.filter.Window.WindowType; import dsp.filter.Window.WindowType;
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.filter.halfband.complex.HalfBandFilter_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 java.util.ArrayList;
import java.util.List;
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer> public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
{ {
public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53, 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, 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, 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, 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, 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, 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 }; 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
private ArrayList<DecimatingStage> mDecimatingStages = private ArrayList<DecimatingStage> mDecimatingStages =
new ArrayList<DecimatingStage>(); new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage; private DecimatingStage mFirstDecimatingStage;
private Output mOutput; private Output mOutput;
/** /**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated, single paired i/q float sample output. * decimated, single paired i/q float sample output.
* *
* Implements the CIC filter described in Understanding Digital Signal * Implements the CIC filter described in Understanding Digital Signal
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
* decimating stages each with a prime factor decimation rate. Multiple * decimating stages each with a prime factor decimation rate. Multiple
* stages are cascaded to achieve the overall decimation rate. * stages are cascaded to achieve the overall decimation rate.
* *
* This filter supports a maximum decimation rate of 700. This filter can * This filter supports a maximum decimation rate of 700. This filter can
* be adapted to higher decimation rates by adding additional prime factors * be adapted to higher decimation rates by adding additional prime factors
* to the PRIMES array. * to the PRIMES array.
* *
* @param decimation - overall decimation rate * @param decimation - overall decimation rate
* @param order - filter order * @param order - filter order
*/ */
public ComplexPrimeCICDecimate( int decimation, int order, public ComplexPrimeCICDecimate(int decimation, int order,
int passFrequency, int attenuation, WindowType windowType ) int passFrequency, int attenuation, WindowType windowType)
{
assert( decimation <= 700 );
List<Integer> 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<ComplexBuffer> 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<Integer> getPrimeFactors( int decimation )
{
ArrayList<Integer> stages = new ArrayList<Integer>();
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 ) Validate.isTrue(decimation <= 700);
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x += 2 )
{ List<Integer> stageSizes = getPrimeFactors(decimation);
mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] );
} 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()
* Decimating stage combines multiple CIC stages with a decimator. The {
* number of stages is indicated by the order value and the size indicates for(DecimatingStage stage : mDecimatingStages)
* the decimation rate of this stage.
*/
public class DecimatingStage implements ComplexSampleListener
{
private ArrayList<Stage> mStages = new ArrayList<Stage>();
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 ); stage.dispose();
} }
public void setListener( ComplexSampleListener listener ) mDecimatingStages.clear();
{ mDecimatingStages = null;
mDecimator.setListener( listener ); mFirstDecimatingStage = null;
}
}
/** mOutput.dispose();
* Single non-decimating CIC stage component. Uses a circular buffer and a mOutput = null;
* 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; * Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener(Listener<ComplexBuffer> listener)
{
mOutput.setListener(listener);
}
protected float mISum; /**
protected float mQSum; * Removes listener from output of this CIC decimation filter
*/
public void removeListener()
{
mOutput.removeListener();
}
private int mSamplePointer = 0; /**
private int mSize; * 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<Integer> getPrimeFactors(int decimation)
{
ArrayList<Integer> stages = new ArrayList<Integer>();
protected float mGain; int pointer = 0;
public Stage() while(decimation > 0 && pointer < PRIMES.length)
{ {
} int prime = PRIMES[pointer];
public Stage( int size ) if(decimation % prime == 0)
{ {
mSize = size - 1; stages.add(prime);
mISamples = new float[ mSize ]; decimation /= prime;
mQSamples = new float[ mSize ]; }
else
{
pointer++;
}
}
mGain = 1.0f / (float)size; return stages;
} }
public void dispose() /**
{ * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
mListener = null; */
} @Override
public void receive(ComplexBuffer buffer)
{
if(mFirstDecimatingStage != null)
{
float[] samples = buffer.getSamples();
public void receive( float i, float q ) 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<Stage> mStages = new ArrayList<Stage>();
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 */ /* Subtract the oldest sample and add back in the newest */
mISum = mISum - mISamples[ mSamplePointer ] + i; mISum = mISum - mISamples[mSamplePointer] + i;
mQSum = mQSum - mQSamples[ mSamplePointer ] + q; mQSum = mQSum - mQSamples[mSamplePointer] + q;
/* Overwrite the oldest sample with the newest */ /* Overwrite the oldest sample with the newest */
mISamples[ mSamplePointer ] = i; mISamples[mSamplePointer] = i;
mQSamples[ mSamplePointer ] = q; mQSamples[mSamplePointer] = q;
mSamplePointer++; mSamplePointer++;
if( mSamplePointer >= mSize ) if(mSamplePointer >= mSize)
{ {
mSamplePointer = 0; mSamplePointer = 0;
} }
if( mListener != null ) if(mListener != null)
{ {
mListener.receive( ( mISum * mGain ), ( mQSum * mGain ) ); 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 ) );
}
}
}
/**
* 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 );
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 );
} }
/** public void setListener(ComplexSampleListener listener)
* Adds a listener to receive output samples
*/
public void setListener( Listener<ComplexBuffer> listener )
{ {
mHalfBandFilter.setListener( 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)
* Removes the listener from receiving output samples {
*/ 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<ComplexBuffer> listener)
{
mHalfBandFilter.setListener(listener);
}
/**
* Removes the listener from receiving output samples
*/
public void removeListener() public void removeListener()
{ {
mHalfBandFilter.removeListener(); mHalfBandFilter.removeListener();
} }
} }
} }

View File

@@ -20,6 +20,7 @@ package dsp.filter.cic;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -81,7 +82,7 @@ public class RealPrimeCICDecimate
public RealPrimeCICDecimate( int decimation, int order, int passFrequency, public RealPrimeCICDecimate( int decimation, int order, int passFrequency,
int attenuation, WindowType windowType, int outputBufferSize ) int attenuation, WindowType windowType, int outputBufferSize )
{ {
assert( decimation <= 700 ); Validate.isTrue(decimation <= 700);
mOutputBufferSize = outputBufferSize; mOutputBufferSize = outputBufferSize;

View File

@@ -17,6 +17,8 @@
******************************************************************************/ ******************************************************************************/
package dsp.filter.halfband.real; package dsp.filter.halfband.real;
import org.apache.commons.lang3.Validate;
public class HalfBandFilter public class HalfBandFilter
{ {
private float[] mBuffer; private float[] mBuffer;
@@ -122,7 +124,7 @@ public class HalfBandFilter
private void generateIndexMap( int size ) private void generateIndexMap( int size )
{ {
//Ensure we have an odd size //Ensure we have an odd size
assert( size % 2 == 1 ); Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1; int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -1,5 +1,6 @@
package dsp.filter.interpolator; package dsp.filter.interpolator;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -31,7 +32,7 @@ public class RealInterpolator extends Interpolator
public float filter( float[] samples, int offset, float mu ) public float filter( float[] samples, int offset, float mu )
{ {
/* Ensure we have enough samples in the array */ /* Ensure we have enough samples in the array */
assert( samples.length >= offset + 7 ); Validate.isTrue(samples.length >= offset + 7);
/* Identify the filter bank that corresponds to mu */ /* Identify the filter bank that corresponds to mu */
int index = (int)( NSTEPS * mu ); int index = (int)( NSTEPS * mu );

View File

@@ -18,6 +18,7 @@
******************************************************************************/ ******************************************************************************/
package dsp.filter.polyphase; package dsp.filter.polyphase;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -53,7 +54,7 @@ public class PolyphaseFIRInterpolatingFilter
public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation ) public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation )
{ {
/* Ensure coefficients are an integral multiple of the interpolation factor */ /* Ensure coefficients are an integral multiple of the interpolation factor */
assert( coefficients.length % interpolation == 0 ); Validate.isTrue(coefficients.length % interpolation == 0);
mTaps = coefficients; mTaps = coefficients;

View File

@@ -17,6 +17,7 @@
******************************************************************************/ ******************************************************************************/
package dsp.gain; package dsp.gain;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -49,7 +50,7 @@ public class DirectGainControl implements GainController, RealSampleListener
*/ */
public DirectGainControl( float gain, float minimum, float maximum, float increment ) public DirectGainControl( float gain, float minimum, float maximum, float increment )
{ {
assert( minimum > 0 ); Validate.isTrue(minimum > 0);
mDefaultGain = gain; mDefaultGain = gain;
mGain = gain; mGain = gain;

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
package instrument; package instrument;
import controller.channel.Channel.ChannelType;
import instrument.tap.Tap; import instrument.tap.Tap;
import instrument.tap.TapGroup; import instrument.tap.TapGroup;
import module.Module;
import module.ProcessingChain;
import source.Source;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
{ {
public InstrumentableProcessingChain() public InstrumentableProcessingChain()
{ {
super( "instrumented", ChannelType.STANDARD ); super(ChannelType.STANDARD);
} }
public void setSource( Source source ) throws IllegalStateException public void setSource(Source source) throws IllegalStateException
{ {
mSource = source; mSource = source;
} }
@Override @Override
public List<TapGroup> getTapGroups() public List<TapGroup> getTapGroups()
{ {
List<TapGroup> groups = new ArrayList<>(); List<TapGroup> groups = new ArrayList<>();
for( Module module: getModules() ) for(Module module : getModules())
{ {
if( module instanceof Instrumentable ) if(module instanceof Instrumentable)
{ {
groups.addAll( ((Instrumentable)module).getTapGroups() ); groups.addAll(((Instrumentable)module).getTapGroups());
} }
} }
return groups; return groups;
} }
@Override @Override
public void registerTap( Tap tap ) public void registerTap(Tap tap)
{ {
for( Module module: getModules() ) for(Module module : getModules())
{ {
if( module instanceof Instrumentable ) if(module instanceof Instrumentable)
{ {
((Instrumentable)module).registerTap( tap ); ((Instrumentable)module).registerTap(tap);
} }
} }
} }
@Override @Override
public void unregisterTap( Tap tap ) public void unregisterTap(Tap tap)
{ {
for( Module module: getModules() ) for(Module module : getModules())
{ {
if( module instanceof Instrumentable ) if(module instanceof Instrumentable)
{ {
((Instrumentable)module).unregisterTap( tap ); ((Instrumentable)module).unregisterTap(tap);
} }
} }
} }
} }

View File

@@ -108,7 +108,6 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>(); private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>(); private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
private String mName;
private AtomicBoolean mRunning = new AtomicBoolean(); private AtomicBoolean mRunning = new AtomicBoolean();
protected Source mSource; protected Source mSource;
@@ -120,13 +119,10 @@ public class ProcessingChain implements IChannelEventListener
/** /**
* Creates a processing chain for managing a set of modules * Creates a processing chain for managing a set of modules
* *
* @param name for thread pool naming each thread
* @param channelType * @param channelType
*/ */
public ProcessingChain(String name, ChannelType channelType) public ProcessingChain(ChannelType channelType)
{ {
mName = name;
mChannelState = new ChannelState(channelType); mChannelState = new ChannelState(channelType);
addModule(mChannelState); addModule(mChannelState);
@@ -211,6 +207,8 @@ public class ProcessingChain implements IChannelEventListener
} }
mSource = source; mSource = source;
addModule(mSource);
} }
/** /**
@@ -322,7 +320,13 @@ public class ProcessingChain implements IChannelEventListener
if(module instanceof IFrequencyChangeListener) if(module instanceof IFrequencyChangeListener)
{ {
mFrequencyChangeEventBroadcaster.addListener(((IFrequencyChangeListener) module).getFrequencyChangeListener()); Listener<FrequencyChangeEvent> frequencyChangeEventListener =
((IFrequencyChangeListener)module).getFrequencyChangeListener();
if(frequencyChangeEventListener != null)
{
mFrequencyChangeEventBroadcaster.addListener(frequencyChangeEventListener);
}
} }
if(module instanceof IMessageListener) if(module instanceof IMessageListener)
@@ -557,25 +561,6 @@ public class ProcessingChain implements IChannelEventListener
+ "sample type - cannot start processing chain"); + "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 */ /* Start each of the modules */
for(Module module : mModules) for(Module module : mModules)
{ {
@@ -588,8 +573,6 @@ public class ProcessingChain implements IChannelEventListener
mLog.error("Error starting module", e); mLog.error("Error starting module", e);
} }
} }
mSource.start(ThreadPool.SCHEDULED);
} }
else else
{ {
@@ -606,9 +589,16 @@ public class ProcessingChain implements IChannelEventListener
{ {
if(mRunning.compareAndSet(true, false)) if(mRunning.compareAndSet(true, false))
{ {
/* Stop each of the modules */
for(Module module : mModules)
{
module.stop();
}
if(mSource != null) if(mSource != null)
{ {
mSource.stop(); removeModule(mSource);
mSource.setOverflowListener(null); mSource.setOverflowListener(null);
switch(mSource.getSampleType()) switch(mSource.getSampleType())
@@ -629,11 +619,6 @@ public class ProcessingChain implements IChannelEventListener
mSource = null; mSource = null;
} }
/* Stop each of the modules */
for(Module module : mModules)
{
module.stop();
}
} }
} }
@@ -760,6 +745,24 @@ public class ProcessingChain implements IChannelEventListener
mMessageBroadcaster.removeListener(listener); mMessageBroadcaster.removeListener(listener);
} }
/**
* Adds the listener to receive frequency change events from the processing chain
* @param listener to receive events
*/
public void addFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mFrequencyChangeEventBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving frequency change events
* @param listener to remove
*/
public void removeFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mFrequencyChangeEventBroadcaster.removeListener(listener);
}
/** /**
* Adds the listener to receive call events from all modules. * Adds the listener to receive call events from all modules.
*/ */

View File

@@ -222,7 +222,7 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD) if(channelType == ChannelType.STANDARD)
{ {
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig, modules.add(new TrafficChannelManager(channelModel, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(), channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize())); (aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize()));
} }
@@ -263,7 +263,7 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD) if(channelType == ChannelType.STANDARD)
{ {
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig, modules.add(new TrafficChannelManager(channelModel, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(), channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize())); (aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize()));
} }

View File

@@ -54,7 +54,7 @@ public class LJ1200DecoderState extends DecoderState
super(aliasList); super(aliasList);
mAddressAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO, mAddressAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO,
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.LOJACK); getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE);
} }
@Override @Override

View File

@@ -9,19 +9,19 @@ import instrument.tap.stream.FloatTap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener; import sample.Listener;
import sample.real.RealBuffer; import sample.real.RealBuffer;
import sample.real.RealSampleListener; import sample.real.RealSampleListener;
import sample.real.RealSampleProvider; import sample.real.RealSampleProvider;
import source.tuner.TunerChannelSource;
import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event; import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeListener; import source.tuner.frequency.IFrequencyChangeListener;
import dsp.gain.DirectGainControl; import dsp.gain.DirectGainControl;
public class C4FMSymbolFilter implements Listener<RealBuffer>, public class C4FMSymbolFilter implements Listener<RealBuffer>, IFrequencyChangeListener, RealSampleProvider, Instrumentable
IFrequencyChangeListener,
RealSampleProvider,
Instrumentable
{ {
private static final float TAPS[][] = private static final float TAPS[][] =
{ {
@@ -198,8 +198,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
private RealSampleListener mListener; private RealSampleListener mListener;
private DirectGainControl mGainController = private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
private int mFrequencyAdjustmentRequested = 0; private int mFrequencyAdjustmentRequested = 0;
private int mFrequencyCorrection = 0; private int mFrequencyCorrection = 0;
@@ -258,8 +257,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
correction = -mFrequencyCorrectionMaximum; correction = -mFrequencyCorrectionMaximum;
} }
broadcast( new FrequencyChangeEvent( broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
mFrequencyAdjustmentRequested = 0; mFrequencyAdjustmentRequested = 0;
} }
@@ -452,6 +450,10 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
} }
} }
/**
* Broadcasts a frequency change event to the registered listener
* @param event
*/
public void broadcast( FrequencyChangeEvent event ) public void broadcast( FrequencyChangeEvent event )
{ {
if( mFrequencyChangeListener != null ) if( mFrequencyChangeListener != null )
@@ -460,6 +462,10 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
} }
} }
/**
* Sets the listener for frequency change events.
* @param listener
*/
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener ) public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
{ {
mFrequencyChangeListener = listener; mFrequencyChangeListener = listener;

View File

@@ -4,8 +4,10 @@ import alias.AliasList;
import instrument.Instrumentable; import instrument.Instrumentable;
import module.decode.Decoder; import module.decode.Decoder;
import module.decode.DecoderType; 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; private P25MessageProcessor mMessageProcessor;

View File

@@ -315,6 +315,8 @@ public class P25DecoderState extends DecoderState
*/ */
public void registerCallEvent(P25CallEvent event) public void registerCallEvent(P25CallEvent event)
{ {
String channel = event.getChannel();
if(mChannelCallMap.containsKey(event.getChannel())) if(mChannelCallMap.containsKey(event.getChannel()))
{ {
P25CallEvent previousEvent = mChannelCallMap.remove(event.getChannel()); P25CallEvent previousEvent = mChannelCallMap.remove(event.getChannel());
@@ -419,7 +421,7 @@ public class P25DecoderState extends DecoderState
mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL) mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(mCurrentChannel) .channel(mCurrentChannel)
.details((hdu.isEncrypted() ? "ENCRYPTED " : "")) .details((hdu.isEncryptedAudio() ? "ENCRYPTED " : ""))
.frequency(mCurrentChannelFrequency) .frequency(mCurrentChannelFrequency)
.to(to) .to(to)
.build(); .build();
@@ -1105,7 +1107,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcu.getChannelA()) .channel(gvcu.getChannelA())
.details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcu.getDownlinkFrequencyA()) .frequency(gvcu.getDownlinkFrequencyA())
.to(userA) .to(userA)
.build()); .build());
@@ -1120,7 +1122,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcu.getChannelB()) .channel(gvcu.getChannelB())
.details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcu.getDownlinkFrequencyB()) .frequency(gvcu.getDownlinkFrequencyB())
.to(userB) .to(userB)
.build()); .build());
@@ -1149,7 +1151,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcue.getTransmitChannel()) .channel(gvcue.getTransmitChannel())
.details((gvcue.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcue.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcue.getDownlinkFrequency()) .frequency(gvcue.getDownlinkFrequency())
.to(group) .to(group)
.build()); .build());
@@ -1185,7 +1187,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(gvcuser.isEncrypted() ? "ENCRYPTED " : "") + (gvcuser.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(gvcuser.isEmergency() ? "EMERGENCY " : "")); (gvcuser.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);
@@ -1397,7 +1399,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(tivcu.isEncrypted() ? "ENCRYPTED " : "") + (tivcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(tivcu.isEmergency() ? "EMERGENCY " : "")); (tivcu.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);
@@ -1480,7 +1482,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(uuvcu.isEncrypted() ? "ENCRYPTED " : "") + (uuvcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(uuvcu.isEmergency() ? "EMERGENCY " : "")); (uuvcu.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);
@@ -1557,6 +1559,7 @@ public class P25DecoderState extends DecoderState
break; break;
case IDENTIFIER_UPDATE_NON_VUHF: case IDENTIFIER_UPDATE_NON_VUHF:
case IDENTIFIER_UPDATE_VHF_UHF_BANDS: case IDENTIFIER_UPDATE_VHF_UHF_BANDS:
case IDENTIFIER_UPDATE_TDMA:
IdentifierUpdate iu = (IdentifierUpdate)tsbk; IdentifierUpdate iu = (IdentifierUpdate)tsbk;
if(!mBands.containsKey(iu.getIdentifier())) if(!mBands.containsKey(iu.getIdentifier()))
@@ -2735,6 +2738,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(pgvcg.isTDMAChannel() ? "TDMA " : "");
details.append(pgvcg.isEmergency() ? "EMERGENCY " : ""); details.append(pgvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(pgvcg.getPriority()); details.append("PRI:").append(pgvcg.getPriority());
details.append(pgvcg.isEncryptedChannel() ? " ENCRYPTED" : ""); details.append(pgvcg.isEncryptedChannel() ? " ENCRYPTED" : "");
@@ -2742,7 +2746,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (pgvcg.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(pgvcg.getDownlinkFrequency()) .frequency(pgvcg.getDownlinkFrequency())
.from(from) .from(from)
@@ -2775,8 +2779,9 @@ public class P25DecoderState extends DecoderState
{ {
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (gvcgu.isTDMAChannel1() ? " TDMA" : "")) .channel(channel)
.details((gvcgu.isEncrypted() ? "ENCRYPTED " : "")) .details((gvcgu.isTDMAChannel1() ? "TDMA " : "") +
(gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
.frequency(gvcgu.getDownlinkFrequency1()) .frequency(gvcgu.getDownlinkFrequency1())
.to(to) .to(to)
.build(); .build();
@@ -2801,8 +2806,9 @@ public class P25DecoderState extends DecoderState
{ {
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel2 + (gvcgu.isTDMAChannel2() ? " TDMA" : "")) .channel(channel2)
.details((gvcgu.isEncrypted() ? "ENCRYPTED " : "")) .details((gvcgu.isTDMAChannel2() ? "TDMA " : "") +
(gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
.frequency(gvcgu.getDownlinkFrequency2()) .frequency(gvcgu.getDownlinkFrequency2())
.to(to2) .to(to2)
.build(); .build();
@@ -2866,6 +2872,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(gdcg.isTDMAChannel() ? "TDMA " : "");
details.append(gdcg.isEmergency() ? "EMERGENCY " : ""); details.append(gdcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gdcg.getPriority()).append(" "); details.append("PRI:").append(gdcg.getPriority()).append(" ");
details.append(gdcg.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(gdcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -2873,7 +2880,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (gdcg.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(gdcg.getDownlinkFrequency()) .frequency(gdcg.getDownlinkFrequency())
.from(from) .from(from)
@@ -2904,6 +2911,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(gvcg.isTDMAChannel() ? "TDMA " : "");
details.append(gvcg.isEmergency() ? "EMERGENCY " : ""); details.append(gvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcg.getPriority()).append(" "); details.append("PRI:").append(gvcg.getPriority()).append(" ");
details.append(gvcg.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(gvcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -2911,7 +2919,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (gvcg.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(gvcg.getDownlinkFrequency()) .frequency(gvcg.getDownlinkFrequency())
.from(from) .from(from)
@@ -2928,8 +2936,7 @@ public class P25DecoderState extends DecoderState
} }
break; break;
case GROUP_VOICE_CHANNEL_GRANT_UPDATE: case GROUP_VOICE_CHANNEL_GRANT_UPDATE:
GroupVoiceChannelGrantUpdate gvcgu = GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)message;
(GroupVoiceChannelGrantUpdate)message;
channel = gvcgu.getChannel1(); channel = gvcgu.getChannel1();
from = null; from = null;
@@ -2942,6 +2949,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(gvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgu.isEmergency() ? "EMERGENCY " : ""); details.append(gvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgu.getPriority()).append(" "); details.append("PRI:").append(gvcgu.getPriority()).append(" ");
details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -2949,7 +2957,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcgu.getChannel1() + (gvcgu.isTDMAChannel1() ? " TDMA" : "")) .channel(gvcgu.getChannel1())
.details(details.toString()) .details(details.toString())
.frequency(gvcgu.getDownlinkFrequency1()) .frequency(gvcgu.getDownlinkFrequency1())
.from(from) .from(from)
@@ -2977,6 +2985,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(gvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgu.isEmergency() ? "EMERGENCY " : ""); details.append(gvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgu.getPriority()).append(" "); details.append("PRI:").append(gvcgu.getPriority()).append(" ");
details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -2984,7 +2993,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event2 = new P25CallEvent.Builder(CallEventType.GROUP_CALL) P25CallEvent event2 = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcgu.getChannel2() + (gvcgu.isTDMAChannel2() ? " TDMA" : "")) .channel(gvcgu.getChannel2())
.details(details.toString()) .details(details.toString())
.frequency(gvcgu.getDownlinkFrequency2()) .frequency(gvcgu.getDownlinkFrequency2())
.to(gvcgu.getGroupAddress2()) .to(gvcgu.getGroupAddress2())
@@ -3016,6 +3025,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(gvcgue.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgue.isEmergency() ? "EMERGENCY " : ""); details.append(gvcgue.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgue.getPriority()).append(" "); details.append("PRI:").append(gvcgue.getPriority()).append(" ");
details.append(gvcgue.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(gvcgue.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3023,7 +3033,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (gvcgue.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(gvcgue.getDownlinkFrequency()) .frequency(gvcgue.getDownlinkFrequency())
.from(from) .from(from)
@@ -3053,6 +3063,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(idcg.isTDMAChannel() ? "TDMA " : "");
details.append(idcg.isEmergency() ? "EMERGENCY " : ""); details.append(idcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(idcg.getPriority()).append(" "); details.append("PRI:").append(idcg.getPriority()).append(" ");
details.append(idcg.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(idcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3060,7 +3071,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (idcg.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(idcg.getDownlinkFrequency()) .frequency(idcg.getDownlinkFrequency())
.from(from) .from(from)
@@ -3090,13 +3101,14 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(sdcg.isTDMAChannel() ? "TDMA " : "");
details.append(sdcg.isEmergency() ? "EMERGENCY " : ""); details.append(sdcg.isEmergency() ? "EMERGENCY " : "");
details.append(sdcg.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(sdcg.isEncryptedChannel() ? "ENCRYPTED " : "");
details.append(sdcg.getSessionMode().name()); details.append(sdcg.getSessionMode().name());
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (sdcg.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(sdcg.getDownlinkFrequency()) .frequency(sdcg.getDownlinkFrequency())
.from(from) .from(from)
@@ -3128,6 +3140,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(tivcg.isTDMAChannel() ? "TDMA " : "");
details.append(tivcg.isEmergency() ? "EMERGENCY " : ""); details.append(tivcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(tivcg.getPriority()).append(" "); details.append("PRI:").append(tivcg.getPriority()).append(" ");
details.append(tivcg.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(tivcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3137,7 +3150,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder( P25CallEvent event = new P25CallEvent.Builder(
CallEventType.TELEPHONE_INTERCONNECT) CallEventType.TELEPHONE_INTERCONNECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (tivcg.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(tivcg.getDownlinkFrequency()) .frequency(tivcg.getDownlinkFrequency())
.from(from) .from(from)
@@ -3170,6 +3183,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(tivcgu.isTDMAChannel() ? "TDMA " : "");
details.append(tivcgu.isEmergency() ? "EMERGENCY " : ""); details.append(tivcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(tivcgu.getPriority()).append(" "); details.append("PRI:").append(tivcgu.getPriority()).append(" ");
details.append(tivcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(tivcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3179,7 +3193,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder( P25CallEvent event = new P25CallEvent.Builder(
CallEventType.TELEPHONE_INTERCONNECT) CallEventType.TELEPHONE_INTERCONNECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (tivcgu.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(tivcgu.getDownlinkFrequency()) .frequency(tivcgu.getDownlinkFrequency())
.from(from) .from(from)
@@ -3209,6 +3223,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(uuvcg.isTDMAChannel() ? "TDMA " : "");
details.append(uuvcg.isEmergency() ? "EMERGENCY " : ""); details.append(uuvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(uuvcg.getPriority()).append(" "); details.append("PRI:").append(uuvcg.getPriority()).append(" ");
details.append(uuvcg.isEncryptedChannel() ? " ENCRYPTED " : ""); details.append(uuvcg.isEncryptedChannel() ? " ENCRYPTED " : "");
@@ -3217,7 +3232,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder( P25CallEvent event = new P25CallEvent.Builder(
CallEventType.UNIT_TO_UNIT_CALL) CallEventType.UNIT_TO_UNIT_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (uuvcg.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(uuvcg.getDownlinkFrequency()) .frequency(uuvcg.getDownlinkFrequency())
.from(from) .from(from)
@@ -3247,6 +3262,7 @@ public class P25DecoderState extends DecoderState
else else
{ {
StringBuilder details = new StringBuilder(); StringBuilder details = new StringBuilder();
details.append(uuvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(uuvcgu.isEmergency() ? "EMERGENCY " : ""); details.append(uuvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(uuvcgu.getPriority()).append(" "); details.append("PRI:").append(uuvcgu.getPriority()).append(" ");
details.append(uuvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(uuvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3255,7 +3271,7 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder( P25CallEvent event = new P25CallEvent.Builder(
CallEventType.UNIT_TO_UNIT_CALL) CallEventType.UNIT_TO_UNIT_CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(channel + (uuvcgu.isTDMAChannel() ? " TDMA" : "")) .channel(channel)
.details(details.toString()) .details(details.toString())
.frequency(uuvcgu.getDownlinkFrequency()) .frequency(uuvcgu.getDownlinkFrequency())
.from(from) .from(from)

View File

@@ -689,8 +689,7 @@ public class P25MessageFramer implements Listener<Dibit>
private LSMDemodulator mDemodulator; private LSMDemodulator mDemodulator;
private double mCorrection; private double mCorrection;
public CostasPhaseErrorDetector( FrameSync frameSync, public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction )
LSMDemodulator demodulator, double correction )
{ {
super( frameSync.getSync() ); super( frameSync.getSync() );

View File

@@ -1,19 +1,20 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
package module.decode.p25; package module.decode.p25;
@@ -41,8 +42,7 @@ import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType; import dsp.filter.Window.WindowType;
import dsp.filter.fir.real.RealFIRFilter_RB_RB; import dsp.filter.fir.real.RealFIRFilter_RB_RB;
public class P25_C4FMDecoder extends P25Decoder public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
implements IFrequencyChangeListener, IFrequencyChangeProvider,IFilteredRealBufferListener
{ {
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class ); private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );

View File

@@ -42,6 +42,7 @@ import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.gain.ComplexFeedForwardGainControl; import dsp.gain.ComplexFeedForwardGainControl;
import dsp.psk.LSMDemodulator; import dsp.psk.LSMDemodulator;
import dsp.psk.QPSKPolarSlicer; import dsp.psk.QPSKPolarSlicer;
import source.tuner.frequency.FrequencyChangeEvent;
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
{ {
@@ -56,8 +57,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
private List<TapGroup> mAvailableTaps; private List<TapGroup> mAvailableTaps;
private ComplexFIRFilter_CB_CB mBasebandFilter; private ComplexFIRFilter_CB_CB mBasebandFilter;
private ComplexBufferToStreamConverter mStreamConverter = private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
new ComplexBufferToStreamConverter();
private ComplexFeedForwardGainControl mAGC = private ComplexFeedForwardGainControl mAGC =
new ComplexFeedForwardGainControl( 32 ); new ComplexFeedForwardGainControl( 32 );
private LSMDemodulator mLSMDemodulator = new LSMDemodulator(); private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
@@ -108,7 +108,30 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
mMessageFramer = null; mMessageFramer = null;
} }
@Override @Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
}
@Override
public void removeFrequencyChangeListener()
{
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return new Listener<FrequencyChangeEvent>()
{
@Override
public void receive(FrequencyChangeEvent frequencyChangeEvent)
{
//Ignored
}
};
}
@Override
public Listener<ComplexBuffer> getComplexBufferListener() public Listener<ComplexBuffer> getComplexBufferListener()
{ {
return mBasebandFilter; return mBasebandFilter;

View File

@@ -12,6 +12,9 @@ import jmbe.iface.AudioConverter;
import message.IMessageListener; import message.IMessageListener;
import message.Message; import message.Message;
import module.Module; import module.Module;
import module.decode.p25.message.hdu.HDUMessage;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.ldu.LDU2Message;
import module.decode.p25.message.ldu.LDUMessage; import module.decode.p25.message.ldu.LDUMessage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -25,14 +28,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class); private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class);
private static final String IMBE_CODEC = "IMBE"; private static final String IMBE_CODEC = "IMBE";
private static boolean mLibraryLoadStatusLogged = false; private static boolean mLibraryLoadStatusLogged = false;
private boolean mCanConvertAudio = false; private boolean mCanConvertAudio = false;
private boolean mEncryptedCall = false;
private boolean mEncryptedCallStateEstablished = false;
private AudioConverter mAudioConverter; private AudioConverter mAudioConverter;
private Listener<AudioPacket> mAudioPacketListener; private Listener<AudioPacket> mAudioPacketListener;
private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f); private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
private Metadata mMetadata; private Metadata mMetadata;
private LDU1Message mCachedLDU1Message = null;
public P25AudioModule(Metadata metadata) public P25AudioModule(Metadata metadata)
{ {
@@ -76,32 +83,73 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
} }
/** /**
* Primary processing method for p25 imbe audio frame messages. Each LDU * Processes call header (HDU) and voice frame (LDU1/LDU2) messages to decode audio and to determine the
* audio message contains 9 imbe voice frames. Each frame is converted to * encrypted audio status of a call event. Only the HDU and LDU2 messages convey encrypted call status. If an
* audio when the JMBE library is loaded, processed by auto-gain, and * LDU1 message is received without a preceding HDU message, then the LDU1 message is cached until the first
* broadcast to the audio packet listener. * LDU2 message is received and the encryption state can be determined. Both the LDU1 and the LDU2 message are
* then processed for audio if the call is unencrypted.
*/ */
public void receive(Message message) public void receive(Message message)
{ {
if (mCanConvertAudio && mAudioPacketListener != null) if(mCanConvertAudio && mAudioPacketListener != null)
{ {
if (message instanceof LDUMessage) if(mEncryptedCallStateEstablished)
{ {
LDUMessage ldu = (LDUMessage) message; if(message instanceof LDUMessage)
/* Only process unencrypted audio frames */
if (!(ldu.isValid() && ldu.isEncrypted()))
{ {
for (byte[] frame : ldu.getIMBEFrames()) processAudio((LDUMessage)message);
{
float[] audio = mAudioConverter.decode(frame);
audio = mGain.apply(audio);
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
}
} }
} }
else
{
if(message instanceof HDUMessage)
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = ((HDUMessage)message).isEncryptedAudio();
}
else if(message instanceof LDU1Message)
{
//When we receive an LDU1 message with first receiving the HDU message, cache the LDU1 Message
//until we can determine the encrypted call state from the next LDU2 message
mCachedLDU1Message = (LDU1Message)message;
}
else if(message instanceof LDU2Message)
{
mEncryptedCallStateEstablished = true;
LDU2Message ldu2 = (LDU2Message)message;
mEncryptedCall = ldu2.isEncryptedAudio();
if(mCachedLDU1Message != null)
{
processAudio(mCachedLDU1Message);
mCachedLDU1Message = null;
}
processAudio(ldu2);
}
}
}
}
/**
* Processes an audio packet by decoding the IMBE audio frames and rebroadcasting them as PCM audio packets.
*/
private void processAudio(LDUMessage ldu)
{
if(!mEncryptedCall)
{
for(byte[] frame : ldu.getIMBEFrames())
{
float[] audio = mAudioConverter.decode(frame);
audio = mGain.apply(audio);
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
}
}
else
{
//Encrypted audio processing not implemented
} }
} }
@@ -119,18 +167,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
Class temp = Class.forName("jmbe.JMBEAudioLibrary"); Class temp = Class.forName("jmbe.JMBEAudioLibrary");
library = (AudioConversionLibrary) temp.newInstance(); library = (AudioConversionLibrary)temp.newInstance();
if ((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 && if((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 &&
library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1) library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1)
{ {
mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO); mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
if (mAudioConverter != null) if(mAudioConverter != null)
{ {
mCanConvertAudio = true; mCanConvertAudio = true;
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("JMBE audio conversion library ["); sb.append("JMBE audio conversion library [");
@@ -144,7 +192,7 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
} }
else else
{ {
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
mLog.info("JMBE audio conversion library NOT FOUND"); mLog.info("JMBE audio conversion library NOT FOUND");
mLibraryLoadStatusLogged = true; mLibraryLoadStatusLogged = true;
@@ -156,28 +204,28 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
mLog.warn("JMBE library version 0.3.3 or higher is required - found: " + library.getVersion()); mLog.warn("JMBE library version 0.3.3 or higher is required - found: " + library.getVersion());
} }
} }
catch (ClassNotFoundException e1) catch(ClassNotFoundException e1)
{ {
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
mLog.error("Couldn't find/load JMBE audio conversion library"); mLog.error("Couldn't find/load JMBE audio conversion library");
mLibraryLoadStatusLogged = true; mLibraryLoadStatusLogged = true;
} }
} }
catch (InstantiationException e1) catch(InstantiationException e1)
{ {
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
mLog.error("Couldn't instantiate JMBE audio conversion library class"); mLog.error("Couldn't instantiate JMBE audio conversion library class");
mLibraryLoadStatusLogged = true; mLibraryLoadStatusLogged = true;
} }
} }
catch (IllegalAccessException e1) catch(IllegalAccessException e1)
{ {
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
mLog.error("Couldn't load JMBE audio conversion library due to " mLog.error("Couldn't load JMBE audio conversion library due to "
+ "security restrictions"); + "security restrictions");
mLibraryLoadStatusLogged = true; mLibraryLoadStatusLogged = true;
} }
} }
@@ -196,19 +244,25 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
} }
/** /**
* Wrapper for squelch state listener. Internally, the P25 audio module * Wrapper for squelch state to process end of call actions. At call end the encrypted call state established
* doesn't have a squelch state. If there are IMBE audio frames, we have * flag is reset so that the encrypted audio state for the next call can be properly detected and we send an
* audio. We use this listener to signal to the recorder manager that the * END audio packet so that downstream processors like the audio recorder can properly close out a call sequence.
* channel state is resetting and it should end the recording.
*/ */
public class SquelchStateListener implements Listener<SquelchState> public class SquelchStateListener implements Listener<SquelchState>
{ {
@Override @Override
public void receive(SquelchState state) public void receive(SquelchState state)
{ {
if (state == SquelchState.SQUELCH && mAudioPacketListener != null) if(state == SquelchState.SQUELCH)
{ {
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf())); if(mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
}
mEncryptedCallStateEstablished = false;
mEncryptedCall = false;
mCachedLDU1Message = null;
} }
} }
} }

View File

@@ -245,7 +245,7 @@ public class HDUMessage extends P25Message
return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) );
} }
public boolean isEncrypted() public boolean isEncryptedAudio()
{ {
return getEncryption() != Encryption.UNENCRYPTED; return getEncryption() != Encryption.UNENCRYPTED;
} }

View File

@@ -1,244 +1,246 @@
package module.decode.p25.message.ldu; package module.decode.p25.message.ldu;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList; import alias.AliasList;
import bits.BinaryMessage; import bits.BinaryMessage;
import edac.CRC; import edac.CRC;
import edac.Hamming10; import edac.Hamming10;
import edac.ReedSolomon_63_47_17; import edac.ReedSolomon_63_47_17;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LDU1Message extends LDUMessage public class LDU1Message extends LDUMessage
{ {
private final static Logger mLog = LoggerFactory.getLogger( LDU1Message.class ); private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class);
public static final int ENCRYPTION_FLAG = 352; public static final int ENCRYPTED_LINK_CONTROL_WORD_FLAG = 352;
public static final int IMPLICIT_VENDOR_FLAG = 353; public static final int IMPLICIT_VENDOR_FLAG = 353;
public static final int[] OPCODE = { 354,355,356,357,362,363 }; public static final int[] OPCODE = {354, 355, 356, 357, 362, 363};
public static final int[] VENDOR = { 364,365,366,367,372,373,374,375 }; public static final int[] VENDOR = {364, 365, 366, 367, 372, 373, 374, 375};
public static final int[] GOLAY_WORD_STARTS = { public static final int[] GOLAY_WORD_STARTS = {
352,362,372,382,536,546,556,566,720,730,740,750,904,914,924,934,1088, 352, 362, 372, 382, 536, 546, 556, 566, 720, 730, 740, 750, 904, 914, 924, 934, 1088,
1098,1108,1118,1272,1282,1292,1302 }; 1098, 1108, 1118, 1272, 1282, 1292, 1302};
public static final int[] CW_HEX_0 = { 352,353,354,355,356,357 }; public static final int[] CW_HEX_0 = {352, 353, 354, 355, 356, 357};
public static final int[] CW_HEX_1 = { 362,363,364,365,366,367 }; public static final int[] CW_HEX_1 = {362, 363, 364, 365, 366, 367};
public static final int[] CW_HEX_2 = { 372,373,374,375,376,377 }; public static final int[] CW_HEX_2 = {372, 373, 374, 375, 376, 377};
public static final int[] CW_HEX_3 = { 382,383,384,385,386,387 }; public static final int[] CW_HEX_3 = {382, 383, 384, 385, 386, 387};
public static final int[] CW_HEX_4 = { 536,537,538,539,540,541 }; public static final int[] CW_HEX_4 = {536, 537, 538, 539, 540, 541};
public static final int[] CW_HEX_5 = { 546,547,548,549,550,551 }; public static final int[] CW_HEX_5 = {546, 547, 548, 549, 550, 551};
public static final int[] CW_HEX_6 = { 556,557,558,559,560,561 }; public static final int[] CW_HEX_6 = {556, 557, 558, 559, 560, 561};
public static final int[] CW_HEX_7 = { 566,567,568,569,570,571 }; public static final int[] CW_HEX_7 = {566, 567, 568, 569, 570, 571};
public static final int[] CW_HEX_8 = { 720,721,722,723,724,725 }; public static final int[] CW_HEX_8 = {720, 721, 722, 723, 724, 725};
public static final int[] CW_HEX_9 = { 730,731,732,733,734,735 }; public static final int[] CW_HEX_9 = {730, 731, 732, 733, 734, 735};
public static final int[] CW_HEX_10 = { 740,741,742,743,744,745 }; public static final int[] CW_HEX_10 = {740, 741, 742, 743, 744, 745};
public static final int[] CW_HEX_11 = { 750,751,752,753,754,755 }; public static final int[] CW_HEX_11 = {750, 751, 752, 753, 754, 755};
public static final int[] RS_HEX_0 = { 904,905,906,907,908,909 }; public static final int[] RS_HEX_0 = {904, 905, 906, 907, 908, 909};
public static final int[] RS_HEX_1 = { 914,915,916,917,918,919 }; public static final int[] RS_HEX_1 = {914, 915, 916, 917, 918, 919};
public static final int[] RS_HEX_2 = { 924,925,926,927,928,929 }; public static final int[] RS_HEX_2 = {924, 925, 926, 927, 928, 929};
public static final int[] RS_HEX_3 = { 934,935,936,937,938,939 }; public static final int[] RS_HEX_3 = {934, 935, 936, 937, 938, 939};
public static final int[] RS_HEX_4 = { 1088,1089,1090,1091,1092,1093 }; public static final int[] RS_HEX_4 = {1088, 1089, 1090, 1091, 1092, 1093};
public static final int[] RS_HEX_5 = { 1098,1099,1100,1101,1102,1103 }; public static final int[] RS_HEX_5 = {1098, 1099, 1100, 1101, 1102, 1103};
public static final int[] RS_HEX_6 = { 1108,1109,1110,1111,1112,1113 }; public static final int[] RS_HEX_6 = {1108, 1109, 1110, 1111, 1112, 1113};
public static final int[] RS_HEX_7 = { 1118,1119,1120,1121,1122,1123 }; public static final int[] RS_HEX_7 = {1118, 1119, 1120, 1121, 1122, 1123};
public static final int[] RS_HEX_8 = { 1272,1273,1274,1275,1276,1277 }; public static final int[] RS_HEX_8 = {1272, 1273, 1274, 1275, 1276, 1277};
public static final int[] RS_HEX_9 = { 1282,1283,1284,1285,1286,1287 }; public static final int[] RS_HEX_9 = {1282, 1283, 1284, 1285, 1286, 1287};
public static final int[] RS_HEX_10 = { 1292,1293,1294,1295,1296,1297 }; public static final int[] RS_HEX_10 = {1292, 1293, 1294, 1295, 1296, 1297};
public static final int[] RS_HEX_11 = { 1302,1303,1304,1305,1306,1307 }; public static final int[] RS_HEX_11 = {1302, 1303, 1304, 1305, 1306, 1307};
/* Reed-Solomon(24,12,13) code protects the link control word. Maximum /* Reed-Solomon(24,12,13) code protects the link control word. Maximum
* correctable errors are: Hamming Distance(13) / 2 = 6 */ * correctable errors are: Hamming Distance(13) / 2 = 6 */
public static final ReedSolomon_63_47_17 mReedSolomonDecoder = public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
new ReedSolomon_63_47_17( 6 ); new ReedSolomon_63_47_17(6);
public LDU1Message( BinaryMessage message, DataUnitID duid, public LDU1Message(BinaryMessage message, DataUnitID duid,
AliasList aliasList ) AliasList aliasList)
{ {
super( message, duid, aliasList ); super(message, duid, aliasList);
/* NID CRC is checked in the message framer, thus a constructed message /* NID CRC is checked in the message framer, thus a constructed message
* means it passed the CRC */ * means it passed the CRC */
mCRC = new CRC[ 3 ]; mCRC = new CRC[3];
mCRC[ 0 ] = CRC.PASSED; mCRC[0] = CRC.PASSED;
checkCRC(); checkCRC();
} }
/** /**
* Subclass constructor from an existing LDU1Message * Subclass constructor from an existing LDU1Message
*/ */
protected LDU1Message( LDU1Message message ) protected LDU1Message(LDU1Message message)
{ {
super( message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList() ); super(message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList());
mCRC = message.mCRC; mCRC = message.mCRC;
} }
@Override /**
public boolean isEncrypted() * Indicates if the contents of the Link Control Word are encrypted.
{ *
return mMessage.get( ENCRYPTION_FLAG ); * @return true if the contents of the LCW are encrypted.
} */
public boolean isEncryptedLinkControlWord()
{
return mMessage.get(ENCRYPTED_LINK_CONTROL_WORD_FLAG);
}
private void checkCRC() private void checkCRC()
{ {
mCRC[ 1 ] = CRC.PASSED; mCRC[1] = CRC.PASSED;
/* Hamming( 10,6,3 ) error detection and correction */ /* Hamming( 10,6,3 ) error detection and correction */
for( int index: GOLAY_WORD_STARTS ) for(int index : GOLAY_WORD_STARTS)
{ {
int errors = Hamming10.checkAndCorrect( mMessage, index ); int errors = Hamming10.checkAndCorrect(mMessage, index);
if( errors > 1 ) if(errors > 1)
{ {
mCRC[ 1 ] = CRC.FAILED_CRC; mCRC[1] = CRC.FAILED_CRC;
} }
} }
/* Reed-Solomon( 24,16,9 ) error detection and correction /* Reed-Solomon( 24,16,9 ) error detection and correction
* Check the Reed-Solomon parity bits. The RS decoder expects the code * Check the Reed-Solomon parity bits. The RS decoder expects the code
* words and reed solomon parity hex codewords in reverse order. * words and reed solomon parity hex codewords in reverse order.
* *
* Since this is a truncated RS(63) codes, we pad the code with zeros */ * Since this is a truncated RS(63) codes, we pad the code with zeros */
int[] input = new int [63]; int[] input = new int[63];
int[] output = new int [63]; int[] output = new int[63];
input[ 0 ] = mMessage.getInt( RS_HEX_11 ); input[0] = mMessage.getInt(RS_HEX_11);
input[ 1 ] = mMessage.getInt( RS_HEX_10 ); input[1] = mMessage.getInt(RS_HEX_10);
input[ 2 ] = mMessage.getInt( RS_HEX_9 ); input[2] = mMessage.getInt(RS_HEX_9);
input[ 3 ] = mMessage.getInt( RS_HEX_8 ); input[3] = mMessage.getInt(RS_HEX_8);
input[ 4 ] = mMessage.getInt( RS_HEX_7 ); input[4] = mMessage.getInt(RS_HEX_7);
input[ 5 ] = mMessage.getInt( RS_HEX_6 ); input[5] = mMessage.getInt(RS_HEX_6);
input[ 6 ] = mMessage.getInt( RS_HEX_5 ); input[6] = mMessage.getInt(RS_HEX_5);
input[ 7 ] = mMessage.getInt( RS_HEX_4 ); input[7] = mMessage.getInt(RS_HEX_4);
input[ 8 ] = mMessage.getInt( RS_HEX_3 ); input[8] = mMessage.getInt(RS_HEX_3);
input[ 9 ] = mMessage.getInt( RS_HEX_2 ); input[9] = mMessage.getInt(RS_HEX_2);
input[ 10 ] = mMessage.getInt( RS_HEX_1 ); input[10] = mMessage.getInt(RS_HEX_1);
input[ 11 ] = mMessage.getInt( RS_HEX_0 ); input[11] = mMessage.getInt(RS_HEX_0);
input[ 12 ] = mMessage.getInt( CW_HEX_11 ); input[12] = mMessage.getInt(CW_HEX_11);
input[ 13 ] = mMessage.getInt( CW_HEX_10 ); input[13] = mMessage.getInt(CW_HEX_10);
input[ 14 ] = mMessage.getInt( CW_HEX_9 ); input[14] = mMessage.getInt(CW_HEX_9);
input[ 15 ] = mMessage.getInt( CW_HEX_8 ); input[15] = mMessage.getInt(CW_HEX_8);
input[ 16 ] = mMessage.getInt( CW_HEX_7 ); input[16] = mMessage.getInt(CW_HEX_7);
input[ 17 ] = mMessage.getInt( CW_HEX_6 ); input[17] = mMessage.getInt(CW_HEX_6);
input[ 18 ] = mMessage.getInt( CW_HEX_5 ); input[18] = mMessage.getInt(CW_HEX_5);
input[ 19 ] = mMessage.getInt( CW_HEX_4 ); input[19] = mMessage.getInt(CW_HEX_4);
input[ 20 ] = mMessage.getInt( CW_HEX_3 ); input[20] = mMessage.getInt(CW_HEX_3);
input[ 21 ] = mMessage.getInt( CW_HEX_2 ); input[21] = mMessage.getInt(CW_HEX_2);
input[ 22 ] = mMessage.getInt( CW_HEX_1 ); input[22] = mMessage.getInt(CW_HEX_1);
input[ 23 ] = mMessage.getInt( CW_HEX_0 ); input[23] = mMessage.getInt(CW_HEX_0);
/* indexes 24 - 62 are defaulted to zero */ /* indexes 24 - 62 are defaulted to zero */
boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output ); boolean irrecoverableErrors = mReedSolomonDecoder.decode(input, output);
if( irrecoverableErrors ) if(irrecoverableErrors)
{ {
mCRC[ 2 ] = CRC.FAILED_CRC; mCRC[2] = CRC.FAILED_CRC;
} }
else else
{ {
/* Since we've detected that we can correct the RS words, mark the /* Since we've detected that we can correct the RS words, mark the
* hamming crc as corrected as well, if it is currently failed */ * hamming crc as corrected as well, if it is currently failed */
if( mCRC[ 1 ] == CRC.FAILED_CRC ) if(mCRC[1] == CRC.FAILED_CRC)
{ {
mCRC[ 1 ] = CRC.CORRECTED; mCRC[1] = CRC.CORRECTED;
} }
mCRC[ 2 ] = CRC.PASSED; mCRC[2] = CRC.PASSED;
/* Only fix the codeword data hex codewords */ /* Only fix the codeword data hex codewords */
repairHexCodeword( input, output, 12, CW_HEX_11 ); repairHexCodeword(input, output, 12, CW_HEX_11);
repairHexCodeword( input, output, 13, CW_HEX_10 ); repairHexCodeword(input, output, 13, CW_HEX_10);
repairHexCodeword( input, output, 14, CW_HEX_9 ); repairHexCodeword(input, output, 14, CW_HEX_9);
repairHexCodeword( input, output, 15, CW_HEX_8 ); repairHexCodeword(input, output, 15, CW_HEX_8);
repairHexCodeword( input, output, 16, CW_HEX_7 ); repairHexCodeword(input, output, 16, CW_HEX_7);
repairHexCodeword( input, output, 17, CW_HEX_6 ); repairHexCodeword(input, output, 17, CW_HEX_6);
repairHexCodeword( input, output, 18, CW_HEX_5 ); repairHexCodeword(input, output, 18, CW_HEX_5);
repairHexCodeword( input, output, 19, CW_HEX_4 ); repairHexCodeword(input, output, 19, CW_HEX_4);
repairHexCodeword( input, output, 20, CW_HEX_3 ); repairHexCodeword(input, output, 20, CW_HEX_3);
repairHexCodeword( input, output, 21, CW_HEX_2 ); repairHexCodeword(input, output, 21, CW_HEX_2);
repairHexCodeword( input, output, 22, CW_HEX_1 ); repairHexCodeword(input, output, 22, CW_HEX_1);
repairHexCodeword( input, output, 23, CW_HEX_0 ); repairHexCodeword(input, output, 23, CW_HEX_0);
} }
} }
private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet ) private void repairHexCodeword(int[] input, int[] output, int index, int[] indexSet)
{ {
if( input[ index ] != output[ index ] ) if(input[index] != output[index])
{ {
mMessage.load( indexSet[ 0 ], 6, output[ index ] ); mMessage.load(indexSet[0], 6, output[index]);
mCRC[ 2 ] = CRC.CORRECTED; mCRC[2] = CRC.CORRECTED;
} }
} }
@Override @Override
public String getMessage() public String getMessage()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() ); sb.append(getMessageStub());
sb.append( " " ); sb.append(" ");
sb.append( mMessage.toString() ); sb.append(mMessage.toString());
return sb.toString(); return sb.toString();
} }
public String getMessageStub() public String getMessageStub()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( super.getMessageStub() ); sb.append(super.getMessageStub());
if( isEncrypted() ) if(isEncryptedLinkControlWord())
{ {
sb.append( "ENCRYPTED" ); sb.append("ENCRYPTED");
} }
else else
{ {
if( isImplicitFormat() || getVendor() == Vendor.STANDARD ) if(isImplicitFormat() || getVendor() == Vendor.STANDARD)
{ {
sb.append( getOpcode().getLabel() ); sb.append(getOpcode().getLabel());
} }
else else
{ {
sb.append( "VENDOR:" + getVendor().getLabel() + " " ); sb.append("VENDOR:" + getVendor().getLabel() + " ");
sb.append( getVendorOpcode().getLabel() ); sb.append(getVendorOpcode().getLabel());
} }
} }
return sb.toString(); return sb.toString();
} }
public boolean isImplicitFormat() public boolean isImplicitFormat()
{ {
return mMessage.get( IMPLICIT_VENDOR_FLAG ); return mMessage.get(IMPLICIT_VENDOR_FLAG);
} }
public LinkControlOpcode getOpcode() public LinkControlOpcode getOpcode()
{ {
return LinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) ); return LinkControlOpcode.fromValue(mMessage.getInt(OPCODE));
} }
public VendorLinkControlOpcode getVendorOpcode() public VendorLinkControlOpcode getVendorOpcode()
{ {
return VendorLinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) ); return VendorLinkControlOpcode.fromValue(mMessage.getInt(OPCODE));
} }
public Vendor getVendor() public Vendor getVendor()
{ {
if( isImplicitFormat() ) if(isImplicitFormat())
{ {
return Vendor.STANDARD; return Vendor.STANDARD;
} }
return Vendor.fromValue( mMessage.getInt( VENDOR ) ); return Vendor.fromValue(mMessage.getInt(VENDOR));
} }
} }

View File

@@ -73,8 +73,10 @@ public class LDU2Message extends LDUMessage
checkCRC(); checkCRC();
} }
@Override /**
public boolean isEncrypted() * Indicates if the audio stream is encrypted
*/
public boolean isEncryptedAudio()
{ {
Encryption encryption = getEncryption(); Encryption encryption = getEncryption();

View File

@@ -35,8 +35,6 @@ public abstract class LDUMessage extends P25Message
super( message, duid, aliasList ); super( message, duid, aliasList );
} }
public abstract boolean isEncrypted();
@Override @Override
public String getMessage() public String getMessage()
{ {

View File

@@ -41,7 +41,7 @@ public class IdentifierUpdateTDMA extends IdentifierUpdate
sb.append( " ID:" + getIdentifier() ); sb.append( " ID:" + getIdentifier() );
sb.append( " BASE:" + getBaseFrequency() ); sb.append( " BASE:" + getBaseFrequency() );
sb.append( " BW:" + getBandwidth() ); sb.append( " BANDWIDTH:" + getBandwidth() );
sb.append( " SPACING:" + getChannelSpacing() ); sb.append( " SPACING:" + getChannelSpacing() );
sb.append( " OFFSET:" + getTransmitOffset() ); sb.append( " OFFSET:" + getTransmitOffset() );
sb.append( " TYPE:" + getChannelType().toString() ); sb.append( " TYPE:" + getChannelType().toString() );
@@ -137,7 +137,7 @@ public class IdentifierUpdateTDMA extends IdentifierUpdate
sb.append( mAccessType.name() ); sb.append( mAccessType.name() );
sb.append( " BANDWIDTH:" + mBandwidth ); sb.append( " BANDWIDTH:" + mBandwidth );
sb.append( " SLOT/CARRIER:" + mSlotsPerCarrier ); sb.append( " TIMESLOTS PER CARRIER:" + mSlotsPerCarrier );
sb.append( " VOCODER:" + mVocoder.name() ); sb.append( " VOCODER:" + mVocoder.name() );
return sb.toString(); return sb.toString();

View File

@@ -89,6 +89,7 @@ public class PlaylistManager implements ChannelEventListener
@Override @Override
public void receive(AliasEvent t) public void receive(AliasEvent t)
{ {
//Save the playlist for all alias events
schedulePlaylistSave(); schedulePlaylistSave();
} }
}); });
@@ -98,6 +99,7 @@ public class PlaylistManager implements ChannelEventListener
@Override @Override
public void receive(ChannelMapEvent t) public void receive(ChannelMapEvent t)
{ {
//Save the playlist for all channel map events
schedulePlaylistSave(); schedulePlaylistSave();
} }
}); });
@@ -107,7 +109,23 @@ public class PlaylistManager implements ChannelEventListener
@Override @Override
public void receive(BroadcastEvent broadcastEvent) 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;
}
} }
}); });
} }

View File

@@ -25,87 +25,169 @@ import properties.SystemProperties;
import record.wave.ComplexBufferWaveRecorder; import record.wave.ComplexBufferWaveRecorder;
import record.wave.RealBufferWaveRecorder; import record.wave.RealBufferWaveRecorder;
import sample.Listener; import sample.Listener;
import sample.OverflowableTransferQueue;
import sample.real.IOverflowListener;
import util.ThreadPool; import util.ThreadPool;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class RecorderManager implements Listener<AudioPacket> public class RecorderManager implements Listener<AudioPacket>
{ {
private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class); private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class);
public static final int AUDIO_SAMPLE_RATE = 8000; public static final int AUDIO_SAMPLE_RATE = 8000;
public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>(); private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>();
private OverflowableTransferQueue<AudioPacket> mAudioPacketQueue = new OverflowableTransferQueue<>(1000, 100);
private ScheduledFuture<?> mBufferProcessorFuture;
private boolean mCanStartNewRecorders = true; private boolean mCanStartNewRecorders = true;
/** /**
* Manages all audio recording for all processing channels. Reconstructs audio streams and distributes channel * Audio recording manager. Monitors stream of audio packets produced by decoding channels and automatically starts
* audio to each of the recorders, starting and stopping the recorders as needed. * 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() 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() public void dispose()
{ {
if(mBufferProcessorFuture != null)
{
mBufferProcessorFuture.cancel(true);
}
} }
/**
* Primary ingest point for audio packets from all decoding channels
* @param audioPacket to process
*/
@Override @Override
public void receive(AudioPacket audioPacket) public void receive(AudioPacket audioPacket)
{ {
if(audioPacket.hasMetadata() && if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable())
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<AudioPacket> 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()); RealBufferWaveRecorder recorder = mRecorders.get(identifier);
}
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 = null; if(audioPacket.getType() == AudioPacket.Type.AUDIO)
try
{ {
recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix);
recorder.start(ThreadPool.SCHEDULED);
recorder.receive(audioPacket.getAudioBuffer()); 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 " + RealBufferWaveRecorder recorder = null;
"is disabled", ioe);
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<Map.Entry<String,RealBufferWaveRecorder>> it = mRecorders.entrySet().iterator();
while(it.hasNext())
{
Map.Entry<String,RealBufferWaveRecorder> 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<AudioPacket>
return new ComplexBufferWaveRecorder(AUDIO_SAMPLE_RATE, sb.toString()); 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();
}
}
} }

View File

@@ -18,6 +18,7 @@
******************************************************************************/ ******************************************************************************/
package record.wave; package record.wave;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sample.ConversionUtils; import sample.ConversionUtils;
@@ -58,7 +59,7 @@ public class MonoWaveReader implements AutoCloseable
*/ */
public MonoWaveReader(Path path, boolean realTime) throws IOException public MonoWaveReader(Path path, boolean realTime) throws IOException
{ {
assert( path != null ); Validate.isTrue(path != null);
mPath = path; mPath = path;
mRealTime = realTime; mRealTime = realTime;

View File

@@ -1,22 +1,33 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
package record.wave; 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.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -26,206 +37,175 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; 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 * WAVE audio recorder module for recording real sample buffers to a wave file
*/ */
public class RealBufferWaveRecorder extends Module public class RealBufferWaveRecorder extends Module
implements IFilteredRealBufferListener, Listener<RealBuffer> implements IFilteredRealBufferListener, Listener<RealBuffer>
{ {
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger(RealBufferWaveRecorder.class);
LoggerFactory.getLogger( RealBufferWaveRecorder.class );
private WaveWriter mWriter; private WaveWriter mWriter;
private String mFilePrefix; private String mFilePrefix;
private Path mFile; private Path mFile;
private AudioFormat mAudioFormat; private AudioFormat mAudioFormat;
private BufferProcessor mBufferProcessor; private BufferProcessor mBufferProcessor;
private ScheduledFuture<?> mProcessorHandle; private ScheduledFuture<?> mProcessorHandle;
private LinkedBlockingQueue<RealBuffer> mBuffers = private LinkedBlockingQueue<RealBuffer> mBuffers = new LinkedBlockingQueue<>(500);
new LinkedBlockingQueue<>( 500 ); private long mLastBufferReceived;
private long mLastBufferReceived;
private AtomicBoolean mRunning = new AtomicBoolean(); private AtomicBoolean mRunning = new AtomicBoolean();
public RealBufferWaveRecorder( int sampleRate, String filePrefix ) public RealBufferWaveRecorder(int sampleRate, String filePrefix)
{ {
mAudioFormat = new AudioFormat( sampleRate, //SampleRate mAudioFormat = new AudioFormat(sampleRate, //SampleRate
16, //Sample Size 16, //Sample Size
1, //Channels 1, //Channels
true, //Signed true, //Signed
false ); //Little Endian false); //Little Endian
mFilePrefix = filePrefix; mFilePrefix = filePrefix;
} }
/** /**
* Timestamp of when the latest buffer was received by this recorder * Indicates if the recorder is currently running.
*/ */
public long getLastBufferReceived() public boolean isRunning()
{ {
return mLastBufferReceived; return mRunning.get();
} }
public Path getFile() /**
{ * Timestamp of when the latest buffer was received by this recorder
return mFile; */
} public long getLastBufferReceived()
{
return mLastBufferReceived;
}
public void start( ScheduledExecutorService executor ) public Path getFile()
{ {
if( mRunning.compareAndSet( false, true ) ) return mFile;
{ }
if( mBufferProcessor == null )
{
mBufferProcessor = new BufferProcessor();
}
try public void start(ScheduledExecutorService executor)
{ {
StringBuilder sb = new StringBuilder(); if(mRunning.compareAndSet(false, true))
sb.append( mFilePrefix ); {
sb.append( "_" ); if(mBufferProcessor == null)
sb.append( TimeStamp.getLongTimeStamp( "_" ) ); {
sb.append( ".wav" ); mBufferProcessor = new BufferProcessor();
}
mFile = Paths.get( sb.toString() ); try
{
StringBuilder sb = new StringBuilder();
sb.append(mFilePrefix);
sb.append("_");
sb.append(TimeStamp.getLongTimeStamp("_"));
sb.append(".wav");
mWriter = new WaveWriter( mAudioFormat, mFile ); mFile = Paths.get(sb.toString());
mWriter = new WaveWriter(mAudioFormat, mFile);
/* Schedule the processor to run every 500 milliseconds */ /* Schedule the processor to run every 500 milliseconds */
mProcessorHandle = executor.scheduleAtFixedRate( mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
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<RealBuffer> 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();
}
}
} }
catch ( IOException ioe ) catch(IOException io)
{ {
/* Stop this module if/when we get an IO exception */ mLog.error("Error starting real buffer recorder", io);
mBuffers.clear(); }
stop(); }
mLog.error( "IOException while trying to write to the wave "
+ "writer", ioe );
}
}
} }
/** public void stop()
* This is used as a sentinel value to signal the buffer processor to end {
*/ if(mRunning.compareAndSet(true, false))
public class PoisonPill extends RealBuffer {
{ try
public PoisonPill() {
{ write();
super( new float[ 1 ] );
} 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<RealBuffer> 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);
}
}
}
} }

View File

@@ -1,22 +1,25 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
package record.wave; package record.wave;
import org.apache.commons.lang3.Validate;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@@ -32,10 +35,7 @@ import javax.sound.sampled.AudioFormat;
public class WaveWriter implements AutoCloseable 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; public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE;
private AudioFormat mAudioFormat; private AudioFormat mAudioFormat;
@@ -56,11 +56,10 @@ public class WaveWriter implements AutoCloseable
* @param maxSize - maximum file size ( range: 1 - 4,294,967,294 bytes ) * @param maxSize - maximum file size ( range: 1 - 4,294,967,294 bytes )
* @throws IOException - if there are any IO issues * @throws IOException - if there are any IO issues
*/ */
public WaveWriter( AudioFormat format, Path file, long maxSize ) public WaveWriter( AudioFormat format, Path file, long maxSize ) throws IOException
throws IOException
{ {
assert( format != null ); Validate.isTrue(format != null);
assert( file != null ); Validate.isTrue( file != null );
mAudioFormat = format; mAudioFormat = format;
mFile = file; mFile = file;
@@ -100,15 +99,11 @@ public class WaveWriter implements AutoCloseable
while( Files.exists( mFile ) ) while( Files.exists( mFile ) )
{ {
mFile = Paths.get( mFile.toFile().getAbsolutePath() mFile = Paths.get( mFile.toFile().getAbsolutePath().replace( ".wav", "_" + version + ".wav" ) );
.replace( ".wav", "_" + version + ".wav" ) );
version++; version++;
} }
Files.createFile( mFile ); mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW ) );
mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE ) );
ByteBuffer header = WaveUtils.getWaveHeader( mAudioFormat ); ByteBuffer header = WaveUtils.getWaveHeader( mAudioFormat );
@@ -127,9 +122,18 @@ public class WaveWriter implements AutoCloseable
{ {
mFileChannel.force( true ); mFileChannel.force( true );
mFileChannel.close(); 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 * Writes the buffer contents to the file. Assumes that the buffer is full
* and the first byte of data is at position 0. * and the first byte of data is at position 0.
*/ */

View File

@@ -58,6 +58,11 @@ public class Broadcaster<T> implements Listener<T>
public void addListener( Listener<T> listener ) public void addListener( Listener<T> listener )
{ {
if(listener == null)
{
throw new IllegalArgumentException("Listener cannot be null");
}
mListeners.add( listener ); mListeners.add( listener );
} }

View File

@@ -20,6 +20,7 @@ package sample.adapter;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import org.apache.commons.lang3.Validate;
import source.mixer.MixerChannel; import source.mixer.MixerChannel;
/** /**
@@ -34,7 +35,7 @@ public class ChannelShortAdapter implements ISampleAdapter
public ChannelShortAdapter( MixerChannel channel ) public ChannelShortAdapter( MixerChannel channel )
{ {
/* Only use with LEFT/RIGHT channels */ /* Only use with LEFT/RIGHT channels */
assert( channel != MixerChannel.MONO ); Validate.isTrue(channel != MixerChannel.MONO);
mMixerChannel = channel; mMixerChannel = channel;
} }

View File

@@ -17,6 +17,7 @@
******************************************************************************/ ******************************************************************************/
package sample.complex; package sample.complex;
import org.apache.commons.lang3.Validate;
import sample.Listener; import sample.Listener;
/** /**
@@ -32,7 +33,7 @@ public class ComplexToComplexBufferAssembler implements ComplexSampleListener
public ComplexToComplexBufferAssembler( int bufferSize ) public ComplexToComplexBufferAssembler( int bufferSize )
{ {
assert( bufferSize % 2 == 0 ); Validate.isTrue(bufferSize % 2 == 0);
mBufferSize = bufferSize; mBufferSize = bufferSize;
mBuffer = new float[ mBufferSize ]; mBuffer = new float[ mBufferSize ];

View File

@@ -20,11 +20,13 @@ package source;
import module.Module; import module.Module;
import sample.SampleType; import sample.SampleType;
import sample.real.IOverflowListener; 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. * 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 SampleType mSampleType;
protected IOverflowListener mOverflowListener; protected IOverflowListener mOverflowListener;

View File

@@ -36,11 +36,11 @@ import sample.real.RealBuffer;
import source.RealSource; import source.RealSource;
import source.SourceException; import source.SourceException;
import dsp.gain.AutomaticGainControl; import dsp.gain.AutomaticGainControl;
import source.tuner.frequency.FrequencyChangeEvent;
public class RealMixerSource extends RealSource public class RealMixerSource extends RealSource
{ {
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger( RealMixerSource.class );
LoggerFactory.getLogger( RealMixerSource.class );
private long mFrequency = 0; private long mFrequency = 0;
private int mBufferSize = 16384; private int mBufferSize = 16384;
@@ -48,13 +48,11 @@ public class RealMixerSource extends RealSource
private BufferReader mBufferReader = new BufferReader(); private BufferReader mBufferReader = new BufferReader();
private TargetDataLine mTargetDataLine; private TargetDataLine mTargetDataLine;
private AudioFormat mAudioFormat; private AudioFormat mAudioFormat;
private String mDisplayName;
private int mBytesPerFrame = 0; private int mBytesPerFrame = 0;
private ISampleAdapter mSampleAdapter; private ISampleAdapter mSampleAdapter;
private AutomaticGainControl mAGC = new AutomaticGainControl(); private AutomaticGainControl mAGC = new AutomaticGainControl();
CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = new CopyOnWriteArrayList<Listener<RealBuffer>>();
new CopyOnWriteArrayList<Listener<RealBuffer>>();
/** /**
* Real Mixer Source - constructs a reader on the mixer/sound card target * Real Mixer Source - constructs a reader on the mixer/sound card target
@@ -81,7 +79,26 @@ public class RealMixerSource extends RealSource
mSampleAdapter = sampleAdapter; mSampleAdapter = sampleAdapter;
} }
@Override @Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
@Override
public void reset() public void reset()
{ {
} }
@@ -133,23 +150,15 @@ public class RealMixerSource extends RealSource
* buffer, otherwise send him a copy of the buffer */ * buffer, otherwise send him a copy of the buffer */
if( it.hasNext() ) if( it.hasNext() )
{ {
// next.receive( mAGC.process( samples.copyOf() ) );
next.receive( samples.copyOf() ); next.receive( samples.copyOf() );
} }
else else
{ {
// next.receive( mAGC.process( samples ) );
next.receive( samples ); next.receive( samples );
} }
} }
} }
@Override
public String toString()
{
return mDisplayName;
}
public int getSampleRate() public int getSampleRate()
{ {
if( mTargetDataLine != null ) if( mTargetDataLine != null )

View File

@@ -34,7 +34,9 @@ import source.ComplexSource;
import source.SourceException; import source.SourceException;
import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event; import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProcessor; import source.tuner.frequency.IFrequencyChangeProcessor;
import source.tuner.frequency.IFrequencyChangeProvider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -65,6 +67,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private ComplexPrimeCICDecimate mDecimationFilter; private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ComplexBuffer> mListener; private Listener<ComplexBuffer> mListener;
private IFrequencyChangeProcessor mFrequencyChangeProcessor; private IFrequencyChangeProcessor mFrequencyChangeProcessor;
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
private ScheduledFuture<?> mTaskHandle; private ScheduledFuture<?> mTaskHandle;
private long mTunerFrequency = 0; private long mTunerFrequency = 0;
@@ -75,7 +78,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private AtomicBoolean mRunning = new AtomicBoolean(); private AtomicBoolean mRunning = new AtomicBoolean();
private boolean mExpended = false; private boolean mExpended = false;
private int mCount;
/** /**
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a * 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()); mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
/* Fire a sample rate change event to setup the decimation chain */ /* 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)) 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 //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 //to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS); 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() public Tuner getTuner()
{ {
return mTuner; return mTuner;
@@ -242,50 +263,49 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
case NOTIFICATION_FREQUENCY_CHANGE: case NOTIFICATION_FREQUENCY_CHANGE:
mTunerFrequency = event.getValue().longValue(); mTunerFrequency = event.getValue().longValue();
updateMixerFrequencyOffset(); updateMixerFrequencyOffset();
break;
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
mChannelFrequencyCorrection = event.getValue().intValue();
updateMixerFrequencyOffset(); //Reset frequency correction so that downstream components can recalculate the value
setFrequencyCorrection(0);
if(mFrequencyChangeProcessor != null)
{
mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
}
break; break;
case NOTIFICATION_SAMPLE_RATE_CHANGE: case NOTIFICATION_SAMPLE_RATE_CHANGE:
int sampleRate = event.getValue().intValue(); int sampleRate = event.getValue().intValue();
setSampleRate(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;
}
break; break;
default: default:
break; 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, * Calculates the local mixer frequency offset from the tuned frequency,
* channel's requested frequency, and channel frequency correction. * channel's requested frequency, and channel frequency correction.
*/ */
private void updateMixerFrequencyOffset() private void updateMixerFrequencyOffset()
{ {
long offset = mTunerFrequency - long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection;
mTunerChannel.getFrequency() -
mChannelFrequencyCorrection;
mMixer.setFrequency(offset); mMixer.setFrequency(offset);
} }
@@ -299,6 +319,140 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
return mTunerChannel.getFrequency(); 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<FrequencyChangeEvent> 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<FrequencyChangeEvent> 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<FrequencyChangeEvent>
{
//Listener to receive downstream events
private Listener<FrequencyChangeEvent> 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<FrequencyChangeEvent> 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<FrequencyChangeEvent> 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 * Decimates an inbound buffer of I/Q samples from the source down to the
* standard 48000 channel sample rate * standard 48000 channel sample rate

View File

@@ -113,7 +113,16 @@ public class FCD2TunerController extends FCDTunerController
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config; FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
setFrequencyCorrection(fcd2.getFrequencyCorrection()); setFrequencyCorrection(fcd2.getFrequencyCorrection());
setFrequency(fcd2.getFrequency());
long frequency = MINIMUM_TUNABLE_FREQUENCY;
if(fcd2.getFrequency() >= MINIMUM_TUNABLE_FREQUENCY &&
fcd2.getFrequency() <= MAXIMUM_TUNABLE_FREQUENCY)
{
frequency = fcd2.getFrequency();
}
setFrequency(frequency);
setLNAGain(fcd2.getGainLNA()); setLNAGain(fcd2.getGainLNA());
setMixerGain(fcd2.getGainMixer()); setMixerGain(fcd2.getGainMixer());

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
package source.tuner.frequency; package source.tuner.frequency;
import sample.Listener; import sample.Listener;
/**
* Provider of Frequency Change Events
*/
public interface IFrequencyChangeProvider public interface IFrequencyChangeProvider
{ {
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener ); void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener );
public void removeFrequencyChangeListener(); void removeFrequencyChangeListener();
} }

View File

@@ -1,19 +1,20 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
package source.wave; package source.wave;
@@ -36,6 +37,7 @@ import sample.complex.ComplexBuffer;
import source.ComplexSource; import source.ComplexSource;
import source.IControllableFileSource; import source.IControllableFileSource;
import source.IFrameLocationListener; import source.IFrameLocationListener;
import source.tuner.frequency.FrequencyChangeEvent;
public class ComplexWaveSource extends ComplexSource implements IControllableFileSource public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
{ {
@@ -55,7 +57,26 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
mFile = file; mFile = file;
} }
@Override @Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
@Override
public void reset() public void reset()
{ {
stop(); stop();

View File

@@ -36,6 +36,7 @@ import sample.real.RealBuffer;
import source.IControllableFileSource; import source.IControllableFileSource;
import source.IFrameLocationListener; import source.IFrameLocationListener;
import source.RealSource; import source.RealSource;
import source.tuner.frequency.FrequencyChangeEvent;
public class RealWaveSource extends RealSource public class RealWaveSource extends RealSource
implements IControllableFileSource, AutoCloseable implements IControllableFileSource, AutoCloseable
@@ -56,6 +57,25 @@ public class RealWaveSource extends RealSource
mFile = file; mFile = file;
} }
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
@Override @Override
public void reset() public void reset()

File diff suppressed because it is too large Load Diff

View File

@@ -1,271 +1,260 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
package spectrum; 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.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.GaussianSmoothingFilter;
import dsp.filter.smoothing.NoSmoothingFilter; import dsp.filter.smoothing.NoSmoothingFilter;
import dsp.filter.smoothing.RectangularSmoothingFilter; import dsp.filter.smoothing.RectangularSmoothingFilter;
import dsp.filter.smoothing.SmoothingFilter; import dsp.filter.smoothing.SmoothingFilter;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType; import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
import dsp.filter.smoothing.TriangularSmoothingFilter; 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;
public class SpectrumPanel extends JPanel import javax.swing.*;
implements DFTResultsListener, import java.awt.*;
SettingChangeListener, import java.awt.geom.GeneralPath;
SpectralDisplayAdjuster 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 = private final static Logger mLog = LoggerFactory.getLogger(SpectrumPanel.class);
LoggerFactory.getLogger( SpectrumPanel.class );
private static final RenderingHints RENDERING_HINTS = private static final RenderingHints RENDERING_HINTS =
new RenderingHints( RenderingHints.KEY_ANTIALIASING, new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints.VALUE_ANTIALIAS_ON );
static static
{
RENDERING_HINTS.put( RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY );
}
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; RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
if( mSettingsManager != null )
{
mSettingsManager.addListener( this );
}
setSampleSize( 16.0 );
mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT );
getColors();
setAveraging( mAveraging );
} }
public void dispose() private Color mColorSpectrumBackground;
{ private Color mColorSpectrumGradientTop;
if( mSettingsManager != null ) private Color mColorSpectrumGradientBottom;
{ private Color mColorSpectrumLine;
mSettingsManager.removeListener( this );
}
mSettingsManager = null; //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 * DFTResultsListener interface for receiving the processed data
* to display * to display
*/ */
public void receive( float[] currentFFTBins ) public void receive(float[] currentFFTBins)
{ {
//Prevent arrays of NaN values from being rendered. The first few //Prevent arrays of NaN values from being rendered. The first few
//DFT result sets on startup will contain NaN values //DFT result sets on startup will contain NaN values
if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) ) if(Float.isInfinite(currentFFTBins[0]) || Float.isNaN(currentFFTBins[0]))
{ {
currentFFTBins = new float[ currentFFTBins.length ]; currentFFTBins = new float[currentFFTBins.length];
} }
//Construct and/or resize our DFT results variables //Construct and/or resize our DFT results variables
if( mDisplayFFTBins == null || if(mDisplayFFTBins == null ||
mDisplayFFTBins.length != currentFFTBins.length ) mDisplayFFTBins.length != currentFFTBins.length)
{ {
mDisplayFFTBins = currentFFTBins; mDisplayFFTBins = currentFFTBins;
} }
//Apply smoothing across the bins of the DFT results //Apply smoothing across the bins of the DFT results
float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins ); float[] smoothedBins = mSmoothingFilter.filter(currentFFTBins);
//Apply averaging over multiple DFT output frames //Apply averaging over multiple DFT output frames
if( mAveraging > 1 ) if(mAveraging > 1)
{ {
float gain = 1.0f / (float)mAveraging; float gain = 1.0f / (float)mAveraging;
for( int x = 0; x < mDisplayFFTBins.length; x++ ) for(int x = 0; x < mDisplayFFTBins.length; x++)
{ {
mDisplayFFTBins[ x ] += mDisplayFFTBins[x] +=
( smoothedBins[ x ] - mDisplayFFTBins[ x ] ) * gain; (smoothedBins[x] - mDisplayFFTBins[x]) * gain;
} }
} }
else else
{ {
mDisplayFFTBins = smoothedBins; mDisplayFFTBins = smoothedBins;
} }
repaint(); repaint();
} }
@Override @Override
public void paintComponent( Graphics g ) public void paintComponent(Graphics g)
{ {
super.paintComponent( g ); super.paintComponent(g);
Graphics2D graphics = (Graphics2D) g; Graphics2D graphics = (Graphics2D)g;
graphics.setBackground( mColorSpectrumBackground ); graphics.setBackground(mColorSpectrumBackground);
graphics.setRenderingHints( RENDERING_HINTS ); graphics.setRenderingHints(RENDERING_HINTS);
drawSpectrum( graphics ); drawSpectrum(graphics);
} }
/** /**
* Draws the current fft spectrum with a line and a gradient fill. * 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 //Draw the background
Rectangle background = new Rectangle( 0, 0, size.width, size.height ); Rectangle background = new Rectangle(0, 0, size.width, size.height);
graphics.setColor( mColorSpectrumBackground ); graphics.setColor(mColorSpectrumBackground);
graphics.draw( background ); graphics.draw(background);
graphics.fill( background ); graphics.fill(background);
//Define the gradient //Define the gradient
GradientPaint gradient = new GradientPaint( 0, GradientPaint gradient = new GradientPaint(0,
( getSize().height - mSpectrumInset ) / 2, (getSize().height - mSpectrumInset) / 2,
mColorSpectrumGradientTop, mColorSpectrumGradientTop,
0, 0,
getSize().height, getSize().height,
mColorSpectrumGradientBottom ); mColorSpectrumGradientBottom);
graphics.setBackground( mColorSpectrumBackground ); graphics.setBackground(mColorSpectrumBackground);
GeneralPath spectrumShape = new GeneralPath(); GeneralPath spectrumShape = new GeneralPath();
//Start at the lower right inset point //Start at the lower right inset point
spectrumShape.moveTo( size.getWidth(), spectrumShape.moveTo(size.getWidth(),
size.getHeight() - mSpectrumInset ); size.getHeight() - mSpectrumInset);
//Draw to the lower left //Draw to the lower left
spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset ); spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
float[] bins = getBins(); float[] bins = getBins();
//If we have FFT data to display ... //If we have FFT data to display ...
if( bins != null ) if(bins != null)
{ {
float insideHeight = size.height - mSpectrumInset; float insideHeight = size.height - mSpectrumInset;
float scalor = insideHeight / -mDBScale; float scalor = insideHeight / -mDBScale;
/* Calculate based on bin size - 1, since bin 0 is rendered at zero /* Calculate based on bin size - 1, since bin 0 is rendered at zero
* and the last bin is rendered at the width */ * and the last bin is rendered at the width */
float binSize = (float)size.width / ( (float)( bins.length ) ); float binSize = (float)size.width / ((float)(bins.length));
for( int x = 0; x < bins.length; x++ ) for(int x = 0; x < bins.length; x++)
{ {
float height; float height;
height = bins[ x ] * scalor; height = bins[x] * scalor;
if( height > insideHeight ) if(height > insideHeight)
{ {
height = insideHeight; height = insideHeight;
} }
if( height < 0 ) if(height < 0)
{ {
height = 0; height = 0;
} }
float xAxis = (float)x * binSize; float xAxis = (float)x * binSize;
spectrumShape.lineTo( xAxis, height ); spectrumShape.lineTo(xAxis, height);
} }
} }
//Otherwise show an empty spectrum //Otherwise show an empty spectrum
else else
{ {
//Draw Left Size //Draw Left Size
graphics.setPaint( gradient ); graphics.setPaint(gradient);
spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset ); spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
//Draw Middle //Draw Middle
spectrumShape.lineTo( size.getWidth(), spectrumShape.lineTo(size.getWidth(),
size.getHeight() - mSpectrumInset ); size.getHeight() - mSpectrumInset);
} }
//Draw Right Side //Draw Right Side
spectrumShape.lineTo( size.getWidth(), spectrumShape.lineTo(size.getWidth(),
size.getHeight() - mSpectrumInset ); size.getHeight() - mSpectrumInset);
graphics.setPaint( gradient ); graphics.setPaint(gradient);
graphics.draw( spectrumShape ); graphics.draw(spectrumShape);
graphics.fill( spectrumShape ); graphics.fill(spectrumShape);
graphics.setPaint( mColorSpectrumLine ); graphics.setPaint(mColorSpectrumLine);
//Draw the bottom line under the spectrum //Draw the bottom line under the spectrum
graphics.draw( new Line2D.Float( 0, graphics.draw(new Line2D.Float(0,
size.height - mSpectrumInset, size.height - mSpectrumInset,
size.width, size.width,
size.height - mSpectrumInset ) ); size.height - mSpectrumInset));
} }
/** /**
@@ -277,14 +266,15 @@ public class SpectrumPanel extends JPanel
* 3 8x Zoom * 3 8x Zoom
* 4 16x Zoom * 4 16x Zoom
* 5 32x Zoom * 5 32x Zoom
* 6 64x Zoom
* *
* @param zoom level, 0 - 5. * @param zoom level, 0 - 6.
*/ */
public void setZoom( int zoom ) public void setZoom(int zoom)
{ {
assert( 0 <= zoom && zoom <= 5 ); Validate.isTrue(0 <= zoom && zoom <= 6, "Unrecognized Zoom Level: " + zoom);
mZoom = zoom; mZoom = zoom;
} }
/** /**
@@ -292,201 +282,202 @@ public class SpectrumPanel extends JPanel
* *
* @param offset * @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 * Sets the source sample size in bits which defines the lowest dB value to
* display in the spectrum * display in the spectrum
* *
* @param sampleSize in bits * @param sampleSize in bits
*/ */
public void setSampleSize( double sampleSize ) public void setSampleSize(double sampleSize)
{
assert( 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 )
{ {
if( setting instanceof ColorSetting ) Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( ( (ColorSetting) setting ).getColorSettingName() ) mDBScale = (float)(20.0 * Math.log10(Math.pow(2.0, sampleSize - 1)));
{
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() /**
{ * Defines the number of DFT result sets to average across
return (int)Math.pow( 2.0, mZoom ); */
} public void setAveraging(int size)
{
mAveraging = size;
}
/** /**
* Returns the DFT result bins, or a zoomed and offset version of the bins * DFT result sets averaging value
* when the display is zoomed. */
*/ 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() private float[] getBins()
{ {
if( mZoom == 0 ) if(mZoom == 0)
{ {
return mDisplayFFTBins; return mDisplayFFTBins;
} }
else else
{ {
int length = mDisplayFFTBins.length / getZoomMultiplier(); int length = mDisplayFFTBins.length / getZoomMultiplier();
int offset = mZoomWindowOffset; int offset = mZoomWindowOffset;
if( ( offset + length ) >= mDisplayFFTBins.length ) if((offset + length) >= mDisplayFFTBins.length)
{ {
offset = mDisplayFFTBins.length - length; offset = mDisplayFFTBins.length - length;
} }
if( offset < 0 ) if(offset < 0)
{ {
offset = 0; offset = 0;
} }
return Arrays.copyOfRange( mDisplayFFTBins, offset, offset + length ); return Arrays.copyOfRange(mDisplayFFTBins, offset, offset + length);
} }
} }
@Override @Override
public void settingDeleted( Setting setting ) { /* not implemented */ } public void settingDeleted(Setting setting)
{ /* not implemented */ }
/** /**
* Returns the current inter-bin smoothing setting * Returns the current inter-bin smoothing setting
*/ */
@Override @Override
public int getSmoothing() public int getSmoothing()
{ {
return mSmoothingFilter.getPointSize(); return mSmoothingFilter.getPointSize();
} }
/** /**
* Sets the bin smoothing value to define how wide the smoothing window is * Sets the bin smoothing value to define how wide the smoothing window is
* when averaging across DFT bins * when averaging across DFT bins
*/ */
@Override @Override
public void setSmoothing( int smoothing ) public void setSmoothing(int smoothing)
{ {
mSmoothingFilter.setPointSize( smoothing ); mSmoothingFilter.setPointSize(smoothing);
} }
/** /**
* Returns the current smoothing filter type * Returns the current smoothing filter type
*/ */
@Override @Override
public SmoothingType getSmoothingType() public SmoothingType getSmoothingType()
{ {
return mSmoothingFilter.getSmoothingType(); return mSmoothingFilter.getSmoothingType();
} }
/** /**
* Sets the smoothing filter type * Sets the smoothing filter type
*/ */
@Override @Override
public void setSmoothingType( SmoothingType type ) public void setSmoothingType(SmoothingType type)
{ {
if( mSmoothingFilter.getSmoothingType() != type ) if(mSmoothingFilter.getSmoothingType() != type)
{ {
int pointSize = getSmoothing(); int pointSize = getSmoothing();
synchronized ( mSmoothingFilter ) synchronized(mSmoothingFilter)
{ {
switch( type ) switch(type)
{ {
case GAUSSIAN: case GAUSSIAN:
mSmoothingFilter = new GaussianSmoothingFilter(); mSmoothingFilter = new GaussianSmoothingFilter();
break; break;
case RECTANGLE: case RECTANGLE:
mSmoothingFilter = new RectangularSmoothingFilter(); mSmoothingFilter = new RectangularSmoothingFilter();
break; break;
case TRIANGLE: case TRIANGLE:
mSmoothingFilter = new TriangularSmoothingFilter(); mSmoothingFilter = new TriangularSmoothingFilter();
break; break;
case NONE: case NONE:
default: default:
mSmoothingFilter = new NoSmoothingFilter(); mSmoothingFilter = new NoSmoothingFilter();
break; break;
} }
} }
setSmoothing( pointSize ); setSmoothing(pointSize);
} }
} }
} }

View File

@@ -18,6 +18,8 @@
******************************************************************************/ ******************************************************************************/
package util; package util;
import org.apache.commons.lang3.Validate;
import java.util.Arrays; import java.util.Arrays;
public class XTEA public class XTEA
@@ -58,7 +60,7 @@ public class XTEA
*/ */
public void setKey(byte[] key) public void setKey(byte[] key)
{ {
assert(key.length == 16); Validate.isTrue(key.length == 16);
int keyInts[] = new int[8]; int keyInts[] = new int[8];
@@ -94,7 +96,7 @@ public class XTEA
*/ */
public byte[] encrypt(byte[] plainText) public byte[] encrypt(byte[] plainText)
{ {
assert(plainText.length == 8); Validate.isTrue(plainText.length == 8);
//Convert the 8 bytes of plaintext to 2 integers //Convert the 8 bytes of plaintext to 2 integers
int result0 = (plainText[3] << 0) & MASK0 | (plainText[2] << 8) & MASK1 | int result0 = (plainText[3] << 0) & MASK0 | (plainText[2] << 8) & MASK1 |