Compare commits

...

17 Commits

Author SHA1 Message Date
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
31 changed files with 1371 additions and 1299 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="-beta8"/>
<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

@@ -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

@@ -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

@@ -17,34 +17,29 @@
******************************************************************************/ ******************************************************************************/
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;
@@ -73,7 +68,7 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
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()
@@ -96,12 +91,13 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
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());
@@ -114,7 +110,7 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
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()
@@ -129,7 +125,7 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
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()
@@ -203,12 +199,13 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
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());
@@ -217,12 +214,13 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
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)

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

@@ -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 sample.Listener; import sample.Listener;
import sample.complex.ComplexBuffer; import sample.complex.ComplexBuffer;
import sample.complex.ComplexSampleListener; import sample.complex.ComplexSampleListener;
@@ -67,7 +68,7 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
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 ); Validate.isTrue(decimation <= 700);
List<Integer> stageSizes = getPrimeFactors( decimation ); List<Integer> stageSizes = getPrimeFactors( decimation );

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

@@ -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)
{ {
switch(broadcastEvent.getEvent())
{
case CONFIGURATION_ADD:
case CONFIGURATION_CHANGE:
case CONFIGURATION_DELETE:
schedulePlaylistSave(); 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,39 +25,97 @@ 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()) {
mAudioPacketQueue.offer(audioPacket);
}
}
/**
* 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)
{ {
String identifier = audioPacket.getMetadata().getUniqueIdentifier(); String identifier = audioPacket.getMetadata().getUniqueIdentifier();
@@ -107,6 +165,30 @@ public class RecorderManager implements Listener<AudioPacket>
} }
} }
} }
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,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* 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
@@ -14,9 +14,20 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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,27 +37,13 @@ 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;
@@ -55,8 +52,7 @@ public class RealBufferWaveRecorder extends Module
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();
@@ -72,6 +68,14 @@ public class RealBufferWaveRecorder extends Module
mFilePrefix = filePrefix; mFilePrefix = filePrefix;
} }
/**
* Indicates if the recorder is currently running.
*/
public boolean isRunning()
{
return mRunning.get();
}
/** /**
* Timestamp of when the latest buffer was received by this recorder * Timestamp of when the latest buffer was received by this recorder
*/ */
@@ -107,8 +111,7 @@ public class RealBufferWaveRecorder extends Module
mWriter = new WaveWriter(mAudioFormat, mFile); 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) catch(IOException io)
{ {
@@ -119,10 +122,23 @@ public class RealBufferWaveRecorder extends Module
public void stop() public void stop()
{ {
/* Signal the buffer processor to end with a poison pill */ if(mRunning.compareAndSet(true, false))
receive( new PoisonPill() ); {
try
{
write();
mRunning.set( false ); if(mWriter != null)
{
mWriter.close();
mWriter = null;
}
}
catch(IOException ioe)
{
mLog.error("Error writing final audio buffers to recording during shutdown", ioe);
}
}
} }
@Override @Override
@@ -134,9 +150,7 @@ public class RealBufferWaveRecorder extends Module
if(!success) if(!success)
{ {
mLog.error( "recorder buffer overflow - purging [" + mLog.error("recorder buffer overflow - purging [" + mFile.toFile().getAbsolutePath() + "]");
mFile.toFile().getAbsolutePath() + "]" );
mBuffers.clear(); mBuffers.clear();
} }
@@ -161,50 +175,28 @@ public class RealBufferWaveRecorder extends Module
{ {
} }
/**
* 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 class BufferProcessor implements Runnable
{ {
public void run() public void run()
{ {
try try
{ {
RealBuffer buffer = mBuffers.poll(); write();
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 ioe)
{ {
@@ -212,20 +204,8 @@ public class RealBufferWaveRecorder extends Module
mBuffers.clear(); mBuffers.clear();
stop(); stop();
mLog.error( "IOException while trying to write to the wave " mLog.error("IO Exception while trying to write to the wave writer", ioe);
+ "writer", ioe );
} }
} }
} }
/**
* This is used as a sentinel value to signal the buffer processor to end
*/
public class PoisonPill extends RealBuffer
{
public PoisonPill()
{
super( new float[ 1 ] );
}
}
} }

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* 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
@@ -14,9 +14,12 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * 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,6 +122,15 @@ 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;
} }
/** /**

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

@@ -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,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* 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
@@ -14,31 +14,17 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package spectrum; package spectrum;
import java.awt.BasicStroke; import controller.channel.Channel;
import java.awt.Color; import controller.channel.Channel.ChannelType;
import java.awt.Font; import controller.channel.ChannelEvent;
import java.awt.FontMetrics; import controller.channel.ChannelEventListener;
import java.awt.Graphics; import controller.channel.ChannelModel;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.JPanel;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import settings.ColorSetting; import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName; import settings.ColorSetting.ColorSettingName;
import settings.Setting; import settings.Setting;
@@ -47,34 +33,34 @@ import settings.SettingsManager;
import source.tuner.TunerChannel; import source.tuner.TunerChannel;
import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeProcessor; import source.tuner.frequency.IFrequencyChangeProcessor;
import controller.channel.Channel;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEventListener;
import controller.channel.ChannelModel;
public class OverlayPanel extends JPanel import javax.swing.*;
implements ChannelEventListener, import java.awt.*;
IFrequencyChangeProcessor, import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequencyChangeProcessor,
SettingChangeListener SettingChangeListener
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class);
LoggerFactory.getLogger( OverlayPanel.class );
private final static RenderingHints RENDERING_HINTS = private final static 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, RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
RenderingHints.VALUE_RENDER_QUALITY );
} }
private final static BasicStroke DASHED_STROKE = new BasicStroke(0.8f, private final static BasicStroke DASHED_STROKE = new BasicStroke(0.8f, BasicStroke.CAP_BUTT,
BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 5.0f, new float[] {2.0f, 4.0f}, 0.0f); BasicStroke.JOIN_MITER, 5.0f, new float[]{2.0f, 4.0f}, 0.0f);
private static DecimalFormat CURSOR_FORMAT = new DecimalFormat("000.00000"); private static DecimalFormat CURSOR_FORMAT = new DecimalFormat("000.00000");
private long mFrequency = 0; private long mFrequency = 0;
@@ -107,13 +93,13 @@ public class OverlayPanel extends JPanel
private SettingsManager mSettingsManager; private SettingsManager mSettingsManager;
private ChannelModel mChannelModel; private ChannelModel mChannelModel;
/** /**
* Translucent overlay panel for displaying channel configurations, * Translucent overlay panel for displaying channel configurations,
* processing channels, selected channels, frequency labels and lines, and * processing channels, selected channels, frequency labels and lines, and
* a cursor with a frequency readout. * a cursor with a frequency readout.
*/ */
public OverlayPanel( SettingsManager settingsManager, public OverlayPanel(SettingsManager settingsManager, ChannelModel channelModel)
ChannelModel channelModel )
{ {
mSettingsManager = settingsManager; mSettingsManager = settingsManager;
@@ -222,18 +208,15 @@ public class OverlayPanel extends JPanel
{ {
mColorChannelConfig = getColor(ColorSettingName.CHANNEL_CONFIG); mColorChannelConfig = getColor(ColorSettingName.CHANNEL_CONFIG);
mColorChannelConfigProcessing = mColorChannelConfigProcessing = getColor(ColorSettingName.CHANNEL_CONFIG_PROCESSING);
getColor( ColorSettingName.CHANNEL_CONFIG_PROCESSING );
mColorChannelConfigSelected = mColorChannelConfigSelected = getColor(ColorSettingName.CHANNEL_CONFIG_SELECTED);
getColor( ColorSettingName.CHANNEL_CONFIG_SELECTED );
mColorSpectrumCursor = getColor(ColorSettingName.SPECTRUM_CURSOR); mColorSpectrumCursor = getColor(ColorSettingName.SPECTRUM_CURSOR);
mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE); mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
mColorSpectrumBackground = mColorSpectrumBackground = getColor(ColorSettingName.SPECTRUM_BACKGROUND);
getColor( ColorSettingName.SPECTRUM_BACKGROUND );
} }
/** /**
@@ -310,21 +293,23 @@ public class OverlayPanel extends JPanel
{ {
if(mCursorVisible) if(mCursorVisible)
{ {
drawFrequencyLine( graphics, drawFrequencyLine(graphics, mCursorLocation.x, mColorSpectrumCursor);
mCursorLocation.x,
mColorSpectrumCursor );
String frequency = CURSOR_FORMAT.format( String frequency = CURSOR_FORMAT.format(getFrequencyFromAxis(mCursorLocation.getX()) / 1E6D);
getFrequencyFromAxis( mCursorLocation.getX() ) / 1E6D );
graphics.drawString( frequency , FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont());
mCursorLocation.x + 5,
mCursorLocation.y ); Rectangle2D rect = fontMetrics.getStringBounds(frequency, graphics);
if(mCursorLocation.y > rect.getHeight())
{
graphics.drawString(frequency, mCursorLocation.x + 5, mCursorLocation.y);
}
if(mZoom != 0) if(mZoom != 0)
{ {
graphics.drawString( "Zoom: " + (int)Math.pow( 2.0, mZoom ) + "x", graphics.drawString("Zoom: " + (int)Math.pow(2.0, mZoom) + "x", mCursorLocation.x + 17,
mCursorLocation.x + 17, mCursorLocation.y + 11 ); mCursorLocation.y + 11);
} }
} }
} }
@@ -336,8 +321,6 @@ public class OverlayPanel extends JPanel
{ {
Stroke currentStroke = graphics.getStroke(); Stroke currentStroke = graphics.getStroke();
// graphics.setStroke( DASHED_STROKE );
long minFrequency = getMinDisplayFrequency(); long minFrequency = getMinDisplayFrequency();
long maxFrequency = getMaxDisplayFrequency(); long maxFrequency = getMaxDisplayFrequency();
@@ -372,8 +355,6 @@ public class OverlayPanel extends JPanel
frequency += minor; frequency += minor;
} }
// graphics.setStroke( currentStroke );
} }
/** /**
@@ -415,8 +396,7 @@ public class OverlayPanel extends JPanel
{ {
graphics.setColor(color); graphics.setColor(color);
graphics.draw( new Line2D.Double( xaxis, 0.0d, graphics.draw(new Line2D.Double(xaxis, 0.0d, xaxis, getSize().getHeight() - mSpectrumInset));
xaxis, getSize().getHeight() - mSpectrumInset ) );
} }
/** /**
@@ -428,8 +408,7 @@ public class OverlayPanel extends JPanel
graphics.setColor(Color.LIGHT_GRAY); graphics.setColor(Color.LIGHT_GRAY);
graphics.draw( new Line2D.Double( xaxis, height * 0.65d, graphics.draw(new Line2D.Double(xaxis, height * 0.65d, xaxis, height - 1.0d));
xaxis, height - 1.0d ) );
} }
/** /**
@@ -443,12 +422,10 @@ public class OverlayPanel extends JPanel
graphics.setColor(Color.YELLOW); graphics.setColor(Color.YELLOW);
//Vertical frequency error line //Vertical frequency error line
graphics.draw( new Line2D.Double( errorAxis, lineHeight, graphics.draw(new Line2D.Double(errorAxis, lineHeight, errorAxis, height - 1.0d));
errorAxis, height - 1.0d ) );
//Horizontal line connecting frequency and error lines //Horizontal line connecting frequency and error lines
graphics.draw( new Line2D.Double( frequencyAxis, lineHeight, graphics.draw(new Line2D.Double(frequencyAxis, lineHeight, errorAxis, lineHeight));
errorAxis, lineHeight ) );
} }
/** /**
@@ -484,8 +461,7 @@ public class OverlayPanel extends JPanel
double offset = xAxis / width; double offset = xAxis / width;
long frequency = getMinDisplayFrequency() + long frequency = getMinDisplayFrequency() + Math.round((double)getDisplayBandwidth() * offset);
Math.round( (double)getDisplayBandwidth() * offset );
if(frequency > (getMaxFrequency())) if(frequency > (getMaxFrequency()))
{ {
@@ -498,9 +474,7 @@ public class OverlayPanel extends JPanel
/** /**
* Draws a frequency label at the x-axis position, at the bottom of the panel * Draws a frequency label at the x-axis position, at the bottom of the panel
*/ */
private void drawFrequencyLabel( Graphics2D graphics, private void drawFrequencyLabel(Graphics2D graphics, double xaxis, long frequency)
double xaxis,
long frequency )
{ {
String label = mLabelSizeMonitor.format(frequency); String label = mLabelSizeMonitor.format(frequency);
@@ -510,8 +484,7 @@ public class OverlayPanel extends JPanel
float xOffset = (float)rect.getWidth() / 2; float xOffset = (float)rect.getWidth() / 2;
graphics.drawString( label, (float)( xaxis - xOffset ), graphics.drawString(label, (float)(xaxis - xOffset), (float)(getSize().getHeight() - 2.0f));
(float)( getSize().getHeight() - 2.0f ) );
} }
@@ -523,8 +496,7 @@ public class OverlayPanel extends JPanel
for(Channel channel : mVisibleChannels) for(Channel channel : mVisibleChannels)
{ {
if(mChannelDisplay == ChannelDisplay.ALL || if(mChannelDisplay == ChannelDisplay.ALL ||
( mChannelDisplay == ChannelDisplay.ENABLED && (mChannelDisplay == ChannelDisplay.ENABLED && channel.getEnabled()))
channel.getEnabled() ) )
{ {
//Choose the correct background color to use //Choose the correct background color to use
if(channel.isSelected()) if(channel.isSelected())
@@ -546,12 +518,10 @@ public class OverlayPanel extends JPanel
{ {
double xAxis = getAxisFromFrequency(tunerChannel.getFrequency()); double xAxis = getAxisFromFrequency(tunerChannel.getFrequency());
double width = (double)( tunerChannel.getBandwidth() ) / double width = (double)(tunerChannel.getBandwidth()) / (double)getDisplayBandwidth() * getSize().getWidth();
(double)getDisplayBandwidth() * getSize().getWidth();
Rectangle2D.Double box = Rectangle2D.Double box =
new Rectangle2D.Double( xAxis - ( width / 2.0d ), new Rectangle2D.Double(xAxis - (width / 2.0d), 0.0d, width, getSize().getHeight() - mSpectrumInset);
0.0d, width, getSize().getHeight() - mSpectrumInset );
//Fill the box with the correct color //Fill the box with the correct color
graphics.fill(box); graphics.fill(box);
@@ -567,30 +537,15 @@ public class OverlayPanel extends JPanel
//Draw the system label and adjust the y-axis position //Draw the system label and adjust the y-axis position
String system = channel.hasSystem() ? channel.getSystem() : " "; String system = channel.hasSystem() ? channel.getSystem() : " ";
yAxis += drawLabel( graphics, yAxis += drawLabel(graphics, system, this.getFont(), xAxis, yAxis, width);
system,
this.getFont(),
xAxis,
yAxis,
width );
//Draw the site label and adjust the y-axis position //Draw the site label and adjust the y-axis position
String site = channel.hasSite() ? channel.getSite() : " "; String site = channel.hasSite() ? channel.getSite() : " ";
yAxis += drawLabel( graphics, yAxis += drawLabel(graphics, site, this.getFont(), xAxis, yAxis, width);
site,
this.getFont(),
xAxis,
yAxis,
width );
//Draw the channel label and adjust the y-axis position //Draw the channel label and adjust the y-axis position
yAxis += drawLabel( graphics, yAxis += drawLabel(graphics, channel.getName(), this.getFont(), xAxis, yAxis, width);
channel.getName(),
this.getFont(),
xAxis,
yAxis,
width );
//Draw the decoder label //Draw the decoder label
drawLabel(graphics, channel.getDecodeConfiguration().getDecoderType().getShortDisplayString(), drawLabel(graphics, channel.getDecodeConfiguration().getDecoderType().getShortDisplayString(),
@@ -623,8 +578,7 @@ public class OverlayPanel extends JPanel
* *
* @return height of the drawn label * @return height of the drawn label
*/ */
private double drawLabel( Graphics2D graphics, String text, Font font, private double drawLabel(Graphics2D graphics, String text, Font font, double x, double baseY, double maxWidth)
double x, double baseY, double maxWidth )
{ {
FontMetrics fontMetrics = graphics.getFontMetrics(font); FontMetrics fontMetrics = graphics.getFontMetrics(font);
@@ -639,15 +593,11 @@ public class OverlayPanel extends JPanel
*/ */
if(offset > (maxWidth / 2.0d)) if(offset > (maxWidth / 2.0d))
{ {
label.setRect( x - ( maxWidth / 2.0d ), label.setRect(x - (maxWidth / 2.0d), y - label.getHeight(), maxWidth, label.getHeight());
y - label.getHeight(),
maxWidth,
label.getHeight() );
graphics.setClip(label); graphics.setClip(label);
graphics.drawString( text, graphics.drawString(text, (float)(x - (maxWidth / 2.0d)), (float)y);
(float)( x - ( maxWidth / 2.0d ) ), (float)y );
graphics.setClip(null); graphics.setClip(null);
} }
@@ -683,9 +633,7 @@ public class OverlayPanel extends JPanel
* Reset the visible channels list * Reset the visible channels list
*/ */
mVisibleChannels.clear(); mVisibleChannels.clear();
mVisibleChannels.addAll( mVisibleChannels.addAll(mChannelModel.getChannelsInFrequencyRange(getMinFrequency(), getMaxFrequency()));
mChannelModel.getChannelsInFrequencyRange( getMinFrequency(),
getMaxFrequency() ) );
} }
/** /**
@@ -701,8 +649,7 @@ public class OverlayPanel extends JPanel
{ {
case NOTIFICATION_ADD: case NOTIFICATION_ADD:
case NOTIFICATION_PROCESSING_START: case NOTIFICATION_PROCESSING_START:
if( !mVisibleChannels.contains( channel ) && if(!mVisibleChannels.contains(channel) && channel.isWithin(getMinFrequency(), getMaxFrequency()))
channel.isWithin( getMinFrequency(), getMaxFrequency() ) )
{ {
mVisibleChannels.add(channel); mVisibleChannels.add(channel);
} }
@@ -717,14 +664,12 @@ public class OverlayPanel extends JPanel
} }
break; break;
case NOTIFICATION_CONFIGURATION_CHANGE: case NOTIFICATION_CONFIGURATION_CHANGE:
if( mVisibleChannels.contains( channel ) && if(mVisibleChannels.contains(channel) && !channel.isWithin(getMinFrequency(), getMaxFrequency()))
!channel.isWithin( getMinFrequency(), getMaxFrequency() ) )
{ {
mVisibleChannels.remove(channel); mVisibleChannels.remove(channel);
} }
if( !mVisibleChannels.contains( channel ) && if(!mVisibleChannels.contains(channel) && channel.isWithin(getMinFrequency(), getMaxFrequency()))
channel.isWithin( getMinFrequency(), getMaxFrequency() ) )
{ {
mVisibleChannels.add(channel); mVisibleChannels.add(channel);
} }
@@ -796,9 +741,7 @@ public class OverlayPanel extends JPanel
{ {
TunerChannel channel = config.getTunerChannel(); TunerChannel channel = config.getTunerChannel();
if( channel != null && if(channel != null && channel.getMinFrequency() <= frequency && channel.getMaxFrequency() >= frequency)
channel.getMinFrequency() <= frequency &&
channel.getMaxFrequency() >= frequency )
{ {
configs.add(config); configs.add(config);
} }
@@ -808,7 +751,8 @@ public class OverlayPanel extends JPanel
} }
@Override @Override
public void settingDeleted( Setting setting ) { /* not implemented */ } public void settingDeleted(Setting setting)
{ /* not implemented */ }
/** /**
* Calculates correct spacing and format for frequency labels and major/minor * Calculates correct spacing and format for frequency labels and major/minor
@@ -858,9 +802,7 @@ public class OverlayPanel extends JPanel
int maxLabelWidth = fontMetrics.stringWidth(format(getMaxDisplayFrequency())); int maxLabelWidth = fontMetrics.stringWidth(format(getMaxDisplayFrequency()));
double maxLabels = ( (double)OverlayPanel.this.getWidth() * double maxLabels = ((double)OverlayPanel.this.getWidth() * LABEL_FILL_THRESHOLD) / (double)maxLabelWidth;
LABEL_FILL_THRESHOLD ) /
(double)maxLabelWidth;
//Calculate the next smallest base 10 value for the major increment //Calculate the next smallest base 10 value for the major increment
int power = (int)Math.log10((double)getDisplayBandwidth() / maxLabels); int power = (int)Math.log10((double)getDisplayBandwidth() / maxLabels);
@@ -874,9 +816,7 @@ public class OverlayPanel extends JPanel
int labelIncrement = start; int labelIncrement = start;
while( ( (double)getDisplayBandwidth() / while(((double)getDisplayBandwidth() / (double)labelIncrement) < maxLabels && labelIncrement >= minimum)
(double)labelIncrement ) < maxLabels &&
labelIncrement >= minimum )
{ {
labelIncrement /= 2; labelIncrement /= 2;
precision++; precision++;
@@ -930,9 +870,17 @@ public class OverlayPanel extends JPanel
update(); update();
} }
public void componentHidden( ComponentEvent arg0 ) {} public void componentHidden(ComponentEvent arg0)
public void componentMoved( ComponentEvent arg0 ) {} {
public void componentShown( ComponentEvent arg0 ) {} }
public void componentMoved(ComponentEvent arg0)
{
}
public void componentShown(ComponentEvent arg0)
{
}
} }

View File

@@ -30,6 +30,7 @@ import java.util.Arrays;
import javax.swing.JPanel; import javax.swing.JPanel;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -282,7 +283,7 @@ public class SpectrumPanel extends JPanel
*/ */
public void setZoom( int zoom ) public void setZoom( int zoom )
{ {
assert( 0 <= zoom && zoom <= 5 ); Validate.isTrue( 0 <= zoom && zoom <= 5 );
mZoom = zoom; mZoom = zoom;
} }
@@ -305,7 +306,7 @@ public class SpectrumPanel extends JPanel
*/ */
public void setSampleSize( double sampleSize ) public void setSampleSize( double sampleSize )
{ {
assert( 2.0 <= sampleSize && sampleSize <= 32.0 ); Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) ); mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) );
} }

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 |