Initial commit of redesigned internal processing and audio management.

Note: Several of the decoders are currently non-functioning in this
commit.  If you want the latest functioning codebase, please checkout
the commit prior to this one.  All decoders will eventually be updated
with this new architecture.

Currently broken:
- Logging and recording
- Aux decoders (Fleetsync/MDC/LoJack/Tait)
- LTR/Passport decoders

Changes in this commit:
- Module architecture for decoders and components with central
provisioning by the DecoderFactory
- Transition from Complex sample stream processing to float buffer
processing for improved memory management
- Redesigned filter structures - to support buffers and streams
- Resolved several memory leak issues
- Universal sharing of the thread pool manager
- Added centralized audio management with call/user prioritization and
new gui with mute/volume controls
- Embedded audio metadata for eventual streaming support
- Redesigned ProcessingChain to support independent re-use
- Added new TrafficChannelManager to handle traffic channel allocations
- Traffic Channel pool size and Call timeout available in P25 and
MPT1327 configs
- Redesigned channel and decoder gui components
This commit is contained in:
denny
2015-09-01 07:10:26 -04:00
parent 538a299246
commit f4d8ea281d
570 changed files with 21578 additions and 17012 deletions

4
src/.directory Normal file
View File

@@ -0,0 +1,4 @@
[Dolphin]
Timestamp=2015,9,1,6,42,59
Version=3
ViewMode=1

View File

@@ -23,6 +23,7 @@ import java.util.HashMap;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -488,4 +489,57 @@ public class AliasList implements Comparable<AliasList>
{
return getName().compareTo( otherAliasList.getName() );
}
public Alias getAlias( String value, MetadataType type )
{
switch( type )
{
case ESN:
return getESNAlias( value );
case FLEETSYNC:
return getFleetsyncAlias( value );
case MDC1200:
return getMDC1200Alias( value );
case MOBILE_ID:
return getMobileIDNumberAlias( value );
case MPT1327:
return getMPT1327Alias( value );
case SITE_ID:
return getSiteID( value );
case STATUS:
if( value != null )
{
try
{
return getStatus( Integer.valueOf( value ) );
}
catch( Exception e )
{
//do nothing, we couldn't parse the int value
}
}
break;
case FROM_TALKGROUP:
case TO:
return getTalkgroupAlias( value );
case UNIQUE_ID:
if( value != null )
{
try
{
return getUniqueID( Integer.valueOf( value ) );
}
catch( Exception e )
{
//do nothing, we couldn't parse the int value
}
}
break;
default:
break;
}
return null;
}
}

102
src/alias/Metadata.java Normal file
View File

@@ -0,0 +1,102 @@
package alias;
public class Metadata
{
private MetadataType mMetadataType;
private String mValue;
private Alias mValueAlias;
private boolean mTemporal;
/**
* Constucts a metadata object with the temporal settings. Temporal
* indicates that this metadata will be removed at the end of a reset
* event, or call end event.
*/
public Metadata( MetadataType metadataType, String value, Alias alias, boolean temporal )
{
mMetadataType = metadataType;
mValue = value;
mValueAlias = alias;
mTemporal = temporal;
}
/**
* Constucts a metadata object with the temporal settings. Temporal
* indicates that this metadata will be removed at the end of a reset
* event, or call end event.
*/
public Metadata( MetadataType metadataType, String value, boolean temporal )
{
this( metadataType, value, null, temporal );
}
/**
* Constucts a non-temporal metadata object
*/
public Metadata( MetadataType metadataType, String value, Alias alias )
{
this( metadataType, value, alias, false );
}
/**
* Constucts a non-temporal metadata object
*/
public Metadata( MetadataType metadataType, String value )
{
this( metadataType, value, null, false );
}
public MetadataType getMetadataType()
{
return mMetadataType;
}
public String getKey()
{
return mMetadataType.getLabel();
}
public String getValue()
{
return mValue;
}
public Alias getAlias()
{
return mValueAlias;
}
public boolean hasAlias()
{
return mValueAlias != null;
}
/**
* Indicates that this piece of metadata is only valid for the duration of
* a call event and should be removed during a reset
*/
public boolean isTemporal()
{
return mTemporal;
}
public boolean isReset()
{
return false;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mMetadataType.getLabel() );
sb.append( ":" );
sb.append( mValue );
sb.append( " [" );
sb.append( mMetadataType.name() );
sb.append( "] temporal:" );
sb.append( mTemporal );
return sb.toString();
}
}

View File

@@ -0,0 +1,14 @@
package alias;
public class MetadataReset extends Metadata
{
public MetadataReset()
{
super( MetadataType.TEMPORAL_RESET, "reset", true );
}
public boolean isReset()
{
return true;
}
}

View File

@@ -0,0 +1,38 @@
package alias;
public enum MetadataType
{
CHANNEL( "Channel" ),
DETAILS( "Details" ),
ESN( "ESN" ),
FLEETSYNC( "Fleetsync" ),
FREQUENCY( "Frequency" ),
FROM_TALKGROUP( "From" ),
MDC1200( "MDC-1200" ),
MOBILE_ID( "Mobile ID" ),
MPT1327( "MPT-1327" ),
PROTOCOL( "Protocol" ),
SITE_ID( "Site ID" ),
STATUS( "Status" ),
TO( "To" ),
UNIQUE_ID( "Unique ID" ),
TEMPORAL_RESET( "Reset" );
private String mLabel;
private MetadataType( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return mLabel;
}
}

View File

@@ -6,10 +6,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import message.Message;
import module.decode.p25.P25_LSMDecoder;
import sample.Listener;
import alias.Alias;
import controller.ResourceManager;
import decode.p25.P25Decoder;
/**
* Manages all alias action events. Each received message is interrogated for

View File

@@ -0,0 +1,356 @@
package audio;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.FloatControl;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
import settings.ColorSetting.ColorSettingName;
import alias.Alias;
import alias.Metadata;
import alias.MetadataType;
import audio.AudioEvent.Type;
import audio.metadata.AudioMetadata;
public class AudioChannelPanel extends JPanel
implements Listener<AudioEvent>, SettingChangeListener
{
private static final long serialVersionUID = 1L;
private static final Logger mLog = LoggerFactory.getLogger( AudioChannelPanel.class );
private Font mFont = new Font( Font.MONOSPACED, Font.PLAIN, 12 );
private Color mLabelColor;
private Color mDetailsColor;
private SettingsManager mSettingsManager;
private IAudioController mController;
private String mChannel;
private JLabel mChannelLabel;
private JLabel mChannelName;
private JLabel mMutedLabel;
private JLabel mFromLabel = new JLabel( "From:" );
private JLabel mFrom = new JLabel( "" );
private JLabel mFromAlias = new JLabel( "" );
private JLabel mToLabel = new JLabel( "To:" );
private JLabel mTo = new JLabel( "" );
private JLabel mToAlias = new JLabel( "" );
private boolean mConfigured = false;
private BooleanControl mMuteControl;
private FloatControl mGainControl;
public AudioChannelPanel( SettingsManager settingsManager,
IAudioController controller,
String channel )
{
mSettingsManager = settingsManager;
mSettingsManager.addListener( this );
mLabelColor = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ).getColor();
mDetailsColor = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ).getColor();
mController = controller;
mChannel = channel;
try
{
mMuteControl = mController.getMuteControl( mChannel );
}
catch ( AudioException e )
{
mLog.error( "Couldn't obtain mute control from audio channel [" +
mChannel + "]", e );
}
try
{
mGainControl = mController.getGainControl( mChannel );
}
catch ( AudioException e )
{
mLog.error( "Couldn't obtain gain control from audio channel [" +
mChannel + "]", e );
}
try
{
mController.addAudioEventListener( mChannel, this );
}
catch( AudioException e )
{
mLog.error( "Couldn't register as audio event listener on channel [" +
mChannel + "]", e );
}
mController.setAudioMetadataListener( mChannel, new AudioMetadataProcessor() );
init();
}
private void init()
{
setLayout( new MigLayout( "insets 0 0 0 0", "[right][][grow,fill]", "[grow,fill]0[]0[]" ) );
setBackground( Color.BLACK );
addMouseListener( new MouseSelectionListener() );
mChannelLabel = new JLabel( "Channel:" );
mChannelLabel.setFont( mFont );
mChannelLabel.setForeground( mDetailsColor );
add( mChannelLabel );
mChannelName = new JLabel( mChannel );
mChannelName.setFont( mFont );
mChannelName.setForeground( mDetailsColor );
add( mChannelName );
mMutedLabel = new JLabel( "" );
mMutedLabel.setFont( mFont );
mMutedLabel.setForeground( Color.RED );
add( mMutedLabel, "wrap" );
mFromLabel.setFont( mFont );
mFromLabel.setForeground( mLabelColor );
add( mFromLabel );
mFrom.setFont( mFont );
mFrom.setForeground( mLabelColor );
add( mFrom );
mFromAlias.setFont( mFont );
mFromAlias.setForeground( mLabelColor );
add( mFromAlias,"wrap" );
mToLabel.setFont( mFont );
mToLabel.setForeground( mLabelColor );
add( mToLabel );
mTo.setFont( mFont );
mTo.setForeground( mLabelColor );
add( mTo );
mToAlias.setFont( mFont );
mToAlias.setForeground( mLabelColor );
add( mToAlias );
}
private void updateMuteState()
{
if( mMuteControl != null )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
mMutedLabel.setText( mMuteControl.getValue() ? "Muted" : "" );
}
} );
}
}
@Override
public void receive( final AudioEvent audioEvent )
{
if( audioEvent.getType() == Type.AUDIO_STOPPED )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
resetLabels();
}
} );
}
}
/**
* Resets the from and to labels. Note: this does not happen on the swing
* event thread. Only invoke from the swing thread.
*/
private void resetLabels()
{
mFrom.setText( "" );
updateAlias( mFromAlias, null );
mTo.setText( "" );
updateAlias( mToAlias, null );
mConfigured = false;
}
/**
* Updates the alias label with text and icon from the alias. Note: this
* does not occur on the Swing event thread -- wrap any calls to this
* method with an event thread call.
*/
private void updateAlias( JLabel label, Alias alias )
{
if( alias != null )
{
label.setText( alias.getName() );
String icon = alias.getIconName();
if( icon != null )
{
label.setIcon( mSettingsManager.getImageIcon( icon, 12 ) );
}
else
{
label.setIcon( null );
}
}
else
{
label.setText( "" );
label.setIcon( null );
}
}
public class MouseSelectionListener implements MouseListener
{
@Override
public void mouseClicked( MouseEvent event )
{
JPopupMenu popup = new JPopupMenu();
if( mMuteControl != null )
{
JMenuItem mute = new JMenuItem( mMuteControl.getValue() ?
"Unmute Channel: " + mChannel : "Mute Channel: " + mChannel );
mute.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
mMuteControl.setValue( !mMuteControl.getValue() );
updateMuteState();
}
} );
popup.add( mute );
}
popup.show( event.getComponent(), event.getX(), event.getY() );
}
@Override
public void mousePressed( MouseEvent e ) {}
@Override
public void mouseReleased( MouseEvent e ) {}
@Override
public void mouseEntered( MouseEvent e ) {}
@Override
public void mouseExited( MouseEvent e ) {}
}
/**
* Processes audio metadata to update this panel's display values
*/
public class AudioMetadataProcessor implements Listener<AudioMetadata>
{
@Override
public void receive( AudioMetadata audioMetadata )
{
if( !mConfigured || audioMetadata.isUpdated() )
{
final Metadata from = audioMetadata.getMetadata( MetadataType.FROM_TALKGROUP );
final Metadata to = audioMetadata.getMetadata( MetadataType.TO );
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
mFrom.setText( from.getValue() );
updateAlias( mFromAlias, from.getAlias() );
mTo.setText( to.getValue() );
updateAlias( mToAlias, to.getAlias() );
}
} );
mConfigured = true;
}
}
}
@Override
public void settingChanged( Setting setting )
{
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mFrom != null )
{
mFrom.setForeground( mLabelColor );
}
if( mFromAlias != null )
{
mFromAlias.setForeground( mLabelColor );
}
if( mTo != null )
{
mTo.setForeground( mLabelColor );
}
if( mToAlias != null )
{
mToAlias.setForeground( mLabelColor );
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mFromLabel != null )
{
mFromLabel.setForeground( mDetailsColor );
}
if( mToLabel != null )
{
mToLabel.setForeground( mDetailsColor );
}
break;
}
}
}
@Override
public void settingDeleted( Setting setting ) {}
}

30
src/audio/AudioEvent.java Normal file
View File

@@ -0,0 +1,30 @@
package audio;
public class AudioEvent
{
private Type mType;
private String mChannel;
public AudioEvent( Type event, String channel )
{
mType = event;
mChannel = channel;
}
public Type getType()
{
return mType;
}
public String getChannel()
{
return mChannel;
}
public enum Type
{
AUDIO_STARTED,
AUDIO_STOPPED,
AUDIO_CONFIGURATION_CHANGED;
}
}

View File

@@ -0,0 +1,16 @@
package audio;
public class AudioException extends Exception
{
private static final long serialVersionUID = 1L;
public AudioException( String description )
{
super( description );
}
public AudioException( String description, Throwable throwable )
{
super( description, throwable );
}
}

379
src/audio/AudioManager.java Normal file
View File

@@ -0,0 +1,379 @@
package audio;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.FloatControl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import audio.metadata.AudioMetadata;
import audio.output.AudioOutput;
import audio.output.MonoAudioOutput;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
public class AudioManager implements Listener<AudioPacket>, IAudioController
{
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
public static final int NO_SOURCE = 0;
private LinkedTransferQueue<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
private Map<Integer,AudioChannelAssignment> mChannelAssignments = new HashMap<>();
private int mLowestPrioritySource;
private List<AudioOutput> mAvailableAudioOutputs = new ArrayList<>();
private Map<String,AudioOutput> mChannelMap = new HashMap<>();
private List<String> mChannelNames = new ArrayList<>();
private AudioOutput mAudioOutput;
private boolean mMuted = false;
private Listener<AudioEvent> mConfigurationChangeListener;
private ThreadPoolManager mThreadPoolManager;
private ScheduledFuture<?> mProcessingTask;
/**
* Handles all audio produced by the decoding channels and routes audio
* packets to any combination of outputs based on any alias audio routing
* options specified by the user.
*/
public AudioManager( ThreadPoolManager manager )
{
mThreadPoolManager = manager;
mAudioOutput = new MonoAudioOutput();
mAvailableAudioOutputs.add( mAudioOutput );
mChannelNames.add( mAudioOutput.getChannelName() );
mChannelMap.put( mAudioOutput.getChannelName(), mAudioOutput );
mProcessingTask = mThreadPoolManager.scheduleFixedRate(
ThreadType.AUDIO_PROCESSING, new AudioPacketProcessor(), 15,
TimeUnit.MILLISECONDS );
}
public void dispose()
{
if( mThreadPoolManager != null && mProcessingTask != null )
{
mThreadPoolManager.cancel( mProcessingTask );
}
mAudioPacketQueue.clear();
mThreadPoolManager = null;
mProcessingTask = null;
mAudioOutput.dispose();
}
@Override
public synchronized void receive( AudioPacket packet )
{
mAudioPacketQueue.add( packet );
}
public class AudioPacketProcessor implements Runnable
{
@Override
public void run()
{
if( !mMuted )
{
}
removeCompletedChannelAssignments();
if( mAudioPacketQueue != null )
{
List<AudioPacket> packets = new ArrayList<AudioPacket>();
mAudioPacketQueue.drainTo( packets );
for( AudioPacket packet: packets )
{
int source = packet.getMetadata().getSource();
/* Existing channel assignment */
if( mChannelAssignments.containsKey( source ) )
{
mChannelAssignments.get( source ).getAudioOutput()
.receive( packet );
}
/* Start a new channel assignment */
else if( !mAvailableAudioOutputs.isEmpty() )
{
AudioOutput channel = mAvailableAudioOutputs.remove( 0 );
mChannelAssignments.put( packet.getMetadata().getSource(),
new AudioChannelAssignment( channel, packet.getMetadata().getPriority() ) );
updateLowestPriorityAssignment();
channel.receive( packet );
}
/* Override the lowest priority assignment */
else
{
if( mLowestPrioritySource > -1 )
{
int priority = packet.getMetadata().getPriority();
if( packet.getMetadata().isSelected() ||
priority < mChannelAssignments
.get( mLowestPrioritySource ).getPriority() )
{
AudioChannelAssignment assignment =
mChannelAssignments.remove( mLowestPrioritySource );
if( assignment != null )
{
assignment.setPriority( priority );
int overrideSource = packet.getMetadata().getSource();
mChannelAssignments.put( overrideSource, assignment );
mLowestPrioritySource = overrideSource;
assignment.getAudioOutput().receive( packet );
}
else
{
mLowestPrioritySource = -1;
}
}
}
}
}
}
}
}
/**
* Checks each audio channel assignment and removes any completed channels.
*/
private void removeCompletedChannelAssignments()
{
if( !mChannelAssignments.isEmpty() )
{
Iterator<Entry<Integer,AudioChannelAssignment>> it =
mChannelAssignments.entrySet().iterator();
Entry<Integer,AudioChannelAssignment> entry;
boolean removed = false;
while( it.hasNext() )
{
entry = it.next();
if( entry.getValue().isComplete() )
{
mAvailableAudioOutputs.add( entry.getValue().getAudioOutput() );
entry.getValue().dispose();
it.remove();
removed = true;
}
}
if( removed )
{
updateLowestPriorityAssignment();
}
}
}
/**
* Identifies the lowest priority channel assignment ... the one that can
* be overridden with a higher priority call
*/
private void updateLowestPriorityAssignment()
{
int lowestPriority = -1;
mLowestPrioritySource = -1;
for( int source: mChannelAssignments.keySet() )
{
AudioChannelAssignment assignment = mChannelAssignments.get( source );
if( assignment.getPriority() > lowestPriority )
{
lowestPriority = assignment.getPriority();
mLowestPrioritySource = source;
}
}
}
public class AudioChannelAssignment implements Listener<AudioEvent>
{
private static final long CALL_END_DELAY = 2000; //2 seconds
private AudioOutput mAudioOutput;
private int mPriority;
private long mAudioStoppedTime = Long.MAX_VALUE;
public AudioChannelAssignment( AudioOutput audioOutput, int priority )
{
mAudioOutput = audioOutput;
mPriority = priority;
mAudioOutput.addAudioEventListener( this );
}
public boolean isComplete()
{
return mAudioStoppedTime != Long.MAX_VALUE &&
mAudioStoppedTime + CALL_END_DELAY < System.currentTimeMillis();
}
public void dispose()
{
mAudioOutput.removeAudioEventListener( this );
mAudioOutput = null;
}
public AudioOutput getAudioOutput()
{
return mAudioOutput;
}
public int getPriority()
{
return mPriority;
}
public void setPriority( int priority )
{
mPriority = priority;
}
@Override
public void receive( AudioEvent event )
{
switch( event.getType() )
{
case AUDIO_STARTED:
mAudioStoppedTime = Long.MAX_VALUE;
break;
case AUDIO_STOPPED:
mAudioStoppedTime = System.currentTimeMillis();
break;
default:
break;
}
}
}
@Override
public List<String> getAudioChannels()
{
return mChannelNames;
}
@Override
public BooleanControl getMuteControl( String channel )
throws AudioException
{
if( mChannelMap.containsKey( channel ) )
{
return mChannelMap.get( channel ).getMuteControl();
}
throw new AudioException( "Channel [" + channel + "] is not a valid channel" );
}
@Override
public boolean hasMuteControl( String channel )
{
return mChannelMap.containsKey( channel ) &&
mChannelMap.get( channel ) != null &&
mChannelMap.get( channel ).getMuteControl() != null;
}
@Override
public FloatControl getGainControl( String channel ) throws AudioException
{
if( mChannelMap.containsKey( channel ) )
{
return mChannelMap.get( channel ).getGainControl();
}
throw new AudioException( "Channel [" + channel + "] is not a valid channel" );
}
@Override
public boolean hasGainControl( String channel )
{
return mChannelMap.containsKey( channel ) &&
mChannelMap.get( channel ) != null &&
mChannelMap.get( channel ).getGainControl() != null;
}
/**
* Sets the listener to receive audio events, specifically, audio
* configuration change events
*/
@Override
public void setConfigurationChangeListener( Listener<AudioEvent> listener )
{
mConfigurationChangeListener = listener;
}
@Override
public void addAudioEventListener( String channel, Listener<AudioEvent> listener )
throws AudioException
{
if( mChannelMap.containsKey( channel ) )
{
mChannelMap.get( channel ).addAudioEventListener( listener );
}
else
{
throw new AudioException( "Channel [" + channel + "] is not a valid "
+ "channel. Audio event listener was not registered" );
}
}
@Override
public void removeAudioEventListener( String channel, Listener<AudioEvent> listener )
{
if( mChannelMap.containsKey( channel ) )
{
mChannelMap.get( channel ).removeAudioEventListener( listener );
}
}
@Override
public void setAudioMetadataListener( String channel,
Listener<AudioMetadata> listener )
{
if( mChannelMap.containsKey( channel ) )
{
mChannelMap.get( channel ).setAudioMetadataListener( listener );
}
}
@Override
public void removeAudioMetadataListener( String channel )
{
if( mChannelMap.containsKey( channel ) )
{
mChannelMap.get( channel ).removeAudioMetadataListener();
}
}
}

191
src/audio/AudioModule.java Normal file
View File

@@ -0,0 +1,191 @@
package audio;
import module.Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.IRealBufferListener;
import sample.real.RealBuffer;
import alias.Metadata;
import audio.metadata.AudioMetadata;
import audio.metadata.IMetadataListener;
import audio.squelch.ISquelchStateListener;
import audio.squelch.SquelchState;
import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener;
import dsp.filter.Filters;
import dsp.filter.dc.AveragingDCRemovalFilter_RB;
import dsp.filter.dc.DCRemovalFilter_RB;
import dsp.filter.fir.real.RealFIRFilter_R_R;
/**
* Provides packaging of demodulated audio sample buffers into audio packets for
* broadcast to registered audio packet listeners. Includes audio packet
* metadata in constructed audio packets.
*
* Incorporates audio squelch state listener to control if audio packets are
* broadcast or ignored.
*/
public class AudioModule extends Module implements IAudioPacketProvider,
IChannelEventListener,
IMetadataListener,
IRealBufferListener,
ISquelchStateListener,
Listener<RealBuffer>
{
protected static final Logger mLog = LoggerFactory.getLogger( AudioModule.class );
/* Determines responsiveness of DC filter to frequency changes */
private static final float DC_REMOVAL_RATIO = 0.000003f;
/* Provides a unique identifier for this audio module instance to use as a
* source identifier for all audio packets */
private static int UNIQUE_ID = 0;
private int mSourceID;
private AudioMetadata mAudioMetadata;
private ChannelEventListener mChannelEventListener = new ChannelEventListener();
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
private RealFIRFilter_R_R mAudioFilter = new RealFIRFilter_R_R(
Filters.FIR_BANDPASS_AUDIO_48KHZ.getCoefficients(), 2.0f );
private DCRemovalFilter_RB mDCRemovalFilter;
private Listener<AudioPacket> mAudioPacketListener;
private boolean mRemoveDC = false;
public AudioModule()
{
this( false );
}
public AudioModule( boolean removeDC )
{
mSourceID = ++UNIQUE_ID;
mAudioMetadata = new AudioMetadata( mSourceID );
mRemoveDC = removeDC;
if( mRemoveDC )
{
mDCRemovalFilter = new AveragingDCRemovalFilter_RB( DC_REMOVAL_RATIO );
mDCRemovalFilter.setListener( this );
}
}
@Override
public void dispose()
{
mSquelchStateListener = null;
mAudioPacketListener = null;
}
@Override
public void init()
{
mAudioMetadata.reset();
}
/**
* Processes demodulated audio samples into audio packets with current audio
* metadata and sends to the registered listener
*/
@Override
public void receive( RealBuffer buffer )
{
if( mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH )
{
float[] audio = buffer.getSamples();
mAudioFilter.filter( audio );
AudioPacket packet = new AudioPacket( audio, mAudioMetadata.copyOf() );
mAudioPacketListener.receive( packet );
}
}
@Override
public Listener<RealBuffer> getRealBufferListener()
{
if( mRemoveDC )
{
return mDCRemovalFilter;
}
return this;
}
@Override
public Listener<Metadata> getMetadataListener()
{
return mAudioMetadata;
}
@Override
public void setAudioPacketListener( Listener<AudioPacket> listener )
{
mAudioPacketListener = listener;
}
@Override
public void removeAudioPacketListener()
{
mAudioPacketListener = null;
}
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mChannelEventListener;
}
@Override
public Listener<SquelchState> getSquelchStateListener()
{
return mSquelchStateListener;
}
/**
* Wrapper for channel event listener. Responds to channel state reset
* events to remove/cleanup current audio metadata
*/
public class ChannelEventListener implements Listener<ChannelEvent>
{
@Override
public void receive( ChannelEvent event )
{
switch( event.getEvent() )
{
case CHANNEL_STATE_RESET:
mAudioMetadata.reset();
break;
case CHANGE_SELECTED:
mAudioMetadata.setSelected( event.getChannel().isSelected() );
break;
default:
break;
}
}
}
/**
* Wrapper for squelch state listener
*/
public class SquelchStateListener implements Listener<SquelchState>
{
@Override
public void receive( SquelchState state )
{
mSquelchState = state;
}
}
}

View File

@@ -1,124 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package audio;
import java.nio.ByteBuffer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.real.RealSampleListener;
import audio.inverted.AudioAdapter;
import audio.inverted.AudioType;
import audio.inverted.IAudioTypeListener;
import buffer.FloatSampleBufferAssembler;
public class AudioOutputImpl implements RealSampleListener,
SquelchListener, IAudioTypeListener, IAudioOutput
{
private final static Logger mLog =
LoggerFactory.getLogger( AudioOutputImpl.class );
private static final int sAUDIO_BLOCK_SIZE = 24000;
private static final AudioFormat sAUDIO_FORMAT =
new AudioFormat( 48000, //SampleRate
16, //Sample Size
1, //Channels
true, //Signed
false ); //Little Endian
private AudioAdapter mAdapter = new AudioAdapter( 48000 );
private FloatSampleBufferAssembler mBuffer =
new FloatSampleBufferAssembler( sAUDIO_BLOCK_SIZE );
private SourceDataLine mOutput;
private boolean mEnabled = false;
private SquelchState mSquelchState = SquelchState.SQUELCH;
public AudioOutputImpl( String channelName )
{
try
{
mOutput = AudioSystem.getSourceDataLine( sAUDIO_FORMAT );
mOutput.open( sAUDIO_FORMAT, (int)( sAUDIO_FORMAT.getSampleRate() ) );
mOutput.start();
}
catch ( LineUnavailableException e )
{
mLog.error( "AudioOutput - couldn't open audio speakers "
+ "for playback", e );
}
}
@Override
public void setSquelch( SquelchState state )
{
mSquelchState = state;
}
@Override
public void setAudioPlaybackEnabled( boolean enabled )
{
mEnabled = enabled;
}
@Override
public void dispose()
{
mOutput.stop();
mOutput.close();
mOutput = null;
}
@Override
public void receive( float sample )
{
float outputSample = mAdapter.get( sample );
if( mEnabled && mSquelchState == SquelchState.UNSQUELCH )
{
mBuffer.put( outputSample );
}
else
{
/* Use 0-valued samples when we're disabled or squelched */
mBuffer.put( 0.0f );
}
if( !mBuffer.hasRemaining() )
{
ByteBuffer buffer = mBuffer.get();
mOutput.write( buffer.array(), 0, buffer.array().length );
mBuffer.reset();
}
}
@Override
public void setAudioType( AudioType type )
{
mAdapter.setAudioType( type );
}
}

View File

@@ -1,25 +0,0 @@
package audio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AudioOutputManager
{
private final static Logger mLog =
LoggerFactory.getLogger( AudioOutputManager.class );
/**
* Handles all audio produced by the decoding channels and routes audio
* packets to any combination of outputs based on any alias audio routing
* options specified by the user.
*/
public AudioOutputManager()
{
}
public static void main( String[] args )
{
}
}

View File

@@ -1,29 +1,34 @@
package audio;
import audio.metadata.AudioMetadata;
public class AudioPacket
{
private String mSource;
private Type mType;
private byte[] mAudioData;
private int mPriority;
private float[] mAudioData;
private AudioMetadata mAudioMetadata;
public AudioPacket( String source, Type type )
public AudioPacket( Type type )
{
mSource = source;
mType = type;
}
public AudioPacket( String source, byte[] audio, int priority )
public AudioPacket( float[] audio, AudioMetadata metadata )
{
this( source, Type.AUDIO );
this( Type.AUDIO );
mAudioData = audio;
mPriority = priority;
mAudioMetadata = metadata;
}
public String getSource()
public AudioMetadata getMetadata()
{
return mSource;
return mAudioMetadata;
}
public void setMetadata( AudioMetadata metadata )
{
mAudioMetadata = metadata;
}
public Type getType()
@@ -31,16 +36,11 @@ public class AudioPacket
return mType;
}
public byte[] getAudioData()
public float[] getAudioData()
{
return mAudioData;
}
public int getPriority()
{
return mPriority;
}
public enum Type
{
AUDIO,

View File

@@ -0,0 +1,56 @@
package audio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
public class AudioPacketRateCounter implements Listener<AudioPacket>
{
private final static Logger mLog =
LoggerFactory.getLogger( AudioPacketRateCounter.class );
private Listener<AudioPacket> mListener;
private long mCount;
private long mStartTime = -1;
private String mName;
public AudioPacketRateCounter()
{
mName = "UNNAMED";
}
public AudioPacketRateCounter( String name )
{
mName = name;
}
@Override
public void receive( AudioPacket packet )
{
if( mStartTime == -1 )
{
mStartTime = System.currentTimeMillis();
}
mCount += packet.getAudioData().length;
long elapsed = System.currentTimeMillis() - mStartTime;
if( elapsed != 0 )
{
mLog.debug( "[" + mName + "] Audio Rate: " + ( mCount / elapsed ) + " kHz" );
}
if( mListener != null )
{
mListener.receive( packet );
}
}
public void setListener( Listener<AudioPacket> listener )
{
mListener = listener;
}
}

120
src/audio/AudioPanel.java Normal file
View File

@@ -0,0 +1,120 @@
package audio;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.sound.sampled.BooleanControl;
import javax.swing.JButton;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import settings.SettingsManager;
public class AudioPanel extends JPanel implements Listener<AudioEvent>
{
private static final long serialVersionUID = 1L;
private static final Logger mLog = LoggerFactory.getLogger( AudioPanel.class );
private SettingsManager mSettingsManager;
private IAudioController mController;
private MuteButton mMuteButton;
public AudioPanel( SettingsManager settingsManager, IAudioController controller )
{
mSettingsManager = settingsManager;
mController = controller;
mMuteButton = new MuteButton( mController );
init();
}
private void init()
{
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]") );
setBackground( Color.BLACK );
add( mMuteButton, "span" );
for( String channel: mController.getAudioChannels() )
{
add( new AudioChannelPanel( mSettingsManager, mController, channel ), "span" );
}
}
public int getPreferredHeight()
{
return mMuteButton.getHeight() +
( mController.getAudioChannels().size() * 20 );
}
@Override
public void receive( AudioEvent event )
{
if( event.getType() == AudioEvent.Type.AUDIO_CONFIGURATION_CHANGED )
{
mLog.debug( "Audio configuration change detected" );
}
}
public class MuteButton extends JButton
{
private IAudioController mController;
private boolean mMuted = false;
public MuteButton( IAudioController controller )
{
super( "Mute Audio" );
setBackground( Color.GREEN );
mController = controller;
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
mMuted = !mMuted;
for( String channel: mController.getAudioChannels() )
{
try
{
BooleanControl control = mController.getMuteControl( channel );
if( control != null )
{
control.setValue( mMuted );
}
}
catch ( AudioException e1 )
{
mLog.error( "Couldn't obtain mute control for "
+ "channel [" + channel + "] from audio manager", e1 );
}
}
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
MuteButton.this.setText( ( mMuted ? "Unmute Audio" : "Mute Audio" ) );
MuteButton.this.setBackground( ( mMuted ? Color.RED : Color.GREEN ) );
}
} );
}
} );
}
}
}

View File

@@ -0,0 +1,36 @@
package audio;
import java.util.List;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.FloatControl;
import sample.Listener;
import audio.metadata.AudioMetadata;
/**
* Interface for controlling one or more audio channels
*/
public interface IAudioController
{
public List<String> getAudioChannels();
public BooleanControl getMuteControl( String channel ) throws AudioException;
public boolean hasMuteControl( String channel );
public FloatControl getGainControl( String channel ) throws AudioException;
public boolean hasGainControl( String channel );
public void addAudioEventListener( String channel, Listener<AudioEvent> listener )
throws AudioException;
public void removeAudioEventListener( String channel, Listener<AudioEvent> listener );
public void setAudioMetadataListener( String channel, Listener<AudioMetadata> listener );
public void removeAudioMetadataListener( String channel );
public void setConfigurationChangeListener( Listener<AudioEvent> listener );
}

View File

@@ -1,29 +0,0 @@
package audio;
import audio.SquelchListener.SquelchState;
import audio.inverted.AudioType;
public interface IAudioOutput
{
/**
* Squelch / Unsquelch state for this channel
*/
public abstract void setSquelch( SquelchState state );
/**
* Audio playback defines if this channel is currently playing on the
* system speakers.
*/
public abstract void setAudioPlaybackEnabled( boolean enabled );
/**
* Dispose of any system resources for this audio output
*/
public abstract void dispose();
/**
* Sets the audio to normal or inverted output
*/
public abstract void setAudioType( AudioType type );
}

View File

@@ -0,0 +1,9 @@
package audio;
import sample.Listener;
public interface IAudioPacketProvider
{
public void setAudioPacketListener( Listener<AudioPacket> listener );
public void removeAudioPacketListener();
}

View File

@@ -88,7 +88,9 @@ public class AudioAdapter
default:
retVal = (float)( mPostInversionFilter.get(
mAudioHighPassFilter.get( sample ) *
mSineWaveGenerator.nextFloat() ) );
mSineWaveGenerator.getFloat() ) );
mSineWaveGenerator.rotate();
break;
}

View File

@@ -38,7 +38,7 @@ public class AudioInverter implements RealSampleListener
mSineWaveGenerator = new Oscillator( inversionFrequency, sampleRate );
mPostInversionLowPassFilter = new FloatFIRFilter(
Filters.FIRLP_55TAP_48000FS_3000FC.getCoefficients(), 1.04 );
Filters.FIRLP_55TAP_48000FS_3000FC.getCoefficients(), 1.04f );
mPostInversionLowPassFilter.setListener( new FilteredSampleProcessor() );
}
@@ -61,7 +61,9 @@ public class AudioInverter implements RealSampleListener
if( mPostInversionLowPassFilter != null )
{
mPostInversionLowPassFilter.receive(
sample * mSineWaveGenerator.nextFloat() );
sample * mSineWaveGenerator.getFloat() );
mSineWaveGenerator.rotate();
}
}

View File

@@ -0,0 +1,180 @@
package audio.metadata;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.Metadata;
import alias.MetadataType;
public class AudioMetadata implements Listener<Metadata>
{
protected static final Logger mLog = LoggerFactory.getLogger( AudioMetadata.class );
private int mSource;
private int mPriority = 99;
private boolean mSelected = false;
private List<Metadata> mMetadata = new ArrayList<>();
private boolean mUpdated = false;
public AudioMetadata( int source )
{
mSource = source;
}
public AudioMetadata copyOf()
{
AudioMetadata copy = new AudioMetadata( mSource );
copy.mPriority = mPriority;
copy.mSelected = mSelected;
copy.mMetadata.addAll( mMetadata );
if( mUpdated )
{
copy.mUpdated = true;
mUpdated = false;
}
return copy;
}
/**
* Removes any accumulated temporal metadata and returns to a default set.
*/
public void reset()
{
Iterator<Metadata> it = mMetadata.iterator();
while( it.hasNext() )
{
if( it.next().isTemporal() )
{
it.remove();
}
}
mUpdated = true;
}
/**
* Indicates if this audio metadata contains updated information. The flag
* will only be set when new data is added and will be reset by a copyOf()
* method invocation.
*/
public boolean isUpdated()
{
return mUpdated;
}
/**
* Adds the metadata. Any temporal metadata will be removed when the
* dispose() method is invoked.
*/
@Override
public void receive( Metadata metadata )
{
if( metadata.isReset() )
{
reset();
}
else
{
mMetadata.add( metadata );
if( metadata.hasAlias() )
{
//TODO: assign priority from alias here
}
}
mUpdated = true;
}
/**
* Returns the complete set of metadata
*/
public List<Metadata> getMetadata()
{
return mMetadata;
}
/**
* Returns the first metadata object matching the type, or null if the
* metadata type is not found.
*/
public Metadata getMetadata( MetadataType type )
{
for( Metadata metadata: mMetadata )
{
if( metadata.getMetadataType() == type )
{
return metadata;
}
}
return null;
}
/**
* Source channel ID
*/
public void setSource( int source )
{
mSource = source;
mUpdated = true;
}
public int getSource()
{
return mSource;
}
/**
* Sets the priority of this audio packet in the range of 1 - Max Integer value
*
* @param priority (1 - Max Int)
*/
public void setPriority( int priority )
{
assert( 1 <= priority && priority <= Integer.MAX_VALUE );
mPriority = priority;
mUpdated = true;
}
/**
* Returns the priority of this audio packet or a priority of -1 if the
* audio source is currently selected, so that the priority of this packet
* is higher (lower?) than any other audio source
*
* @return - priority of audio packet where lower numbers are higher priority
*/
public int getPriority()
{
return mSelected ? -1 : mPriority;
}
/**
* Selected audio channel. A selected channel will override the priority
* level so that the audio can be listened to immediately.
*/
public void setSelected( boolean selected )
{
mSelected = selected;
mUpdated = true;
}
public boolean isSelected()
{
return mSelected;
}
}

View File

@@ -0,0 +1,9 @@
package audio.metadata;
import sample.Listener;
import alias.Metadata;
public interface IMetadataListener
{
public Listener<Metadata> getMetadataListener();
}

View File

@@ -0,0 +1,10 @@
package audio.metadata;
import sample.Listener;
import alias.Metadata;
public interface IMetadataProvider
{
public void setMetadataListener( Listener<Metadata> listener );
public void removeMetadataListener();
}

View File

@@ -0,0 +1,84 @@
package audio.output;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.FloatControl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import audio.AudioEvent;
import audio.AudioPacket;
import audio.metadata.AudioMetadata;
public abstract class AudioOutput implements Listener<AudioPacket>
{
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
private AtomicBoolean mSquelched = new AtomicBoolean( false );
private Listener<AudioMetadata> mAudioMetadataListener;
protected Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
public void dispose()
{
mAudioEventBroadcaster.dispose();
mAudioEventBroadcaster = null;
mAudioMetadataListener = null;
}
public abstract String getChannelName();
public abstract BooleanControl getMuteControl();
public abstract boolean hasMuteControl();
public abstract FloatControl getGainControl();
public abstract boolean hasGainControl();
protected void broadcast( AudioMetadata metadata )
{
if( mAudioMetadataListener != null )
{
mAudioMetadataListener.receive( metadata );
}
}
public void setSquelched( boolean squelched )
{
mSquelched.set( squelched );
}
public boolean isSquelched()
{
return mSquelched.get();
}
/**
* Registers a single listener to receive audio start and audio stop events
*/
public void addAudioEventListener( Listener<AudioEvent> listener )
{
mAudioEventBroadcaster.addListener( listener );
}
public void removeAudioEventListener( Listener<AudioEvent> listener )
{
mAudioEventBroadcaster.removeListener( listener );
}
/**
* Registers a single listener to receive the audio metadata from each
* audio packet
*/
public void setAudioMetadataListener( Listener<AudioMetadata> listener )
{
mAudioMetadataListener = listener;
}
public void removeAudioMetadataListener()
{
mAudioMetadataListener = null;
}
}

View File

@@ -1,5 +1,8 @@
package audio;
package audio.output;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
@@ -9,20 +12,25 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import audio.AudioEvent;
import audio.AudioFormats;
import audio.AudioPacket;
import audio.AudioPacket.Type;
import controller.NamingThreadFactory;
/**
* Audio output with automatic flow control based on the availability of
* audio data packets.
*/
public class MonoAudioOutput implements Listener<AudioPacket>
public class MonoAudioOutput extends AudioOutput
{
private final static Logger mLog = LoggerFactory.getLogger( MonoAudioOutput.class );
@@ -43,15 +51,24 @@ public class MonoAudioOutput implements Listener<AudioPacket>
private ScheduledExecutorService mExecutorService;
private SourceDataLine mOutput;
private FloatControl mGainControl;
private BooleanControl mMuteControl;
private boolean mCanProcessAudio;
private AtomicBoolean mMuted = new AtomicBoolean( true );
private AtomicBoolean mSquelched = new AtomicBoolean( false );
private long mLastBufferReceived;
private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent;
public MonoAudioOutput()
{
try
super();
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED, getChannelName() );
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, getChannelName() );
try
{
mOutput = AudioSystem.getSourceDataLine(
AudioFormats.PCM_SIGNED_48KHZ_16BITS );
@@ -62,7 +79,14 @@ public class MonoAudioOutput implements Listener<AudioPacket>
mCanProcessAudio = true;
mExecutorService = Executors.newSingleThreadScheduledExecutor();
mGainControl = (FloatControl)mOutput.getControl(
FloatControl.Type.MASTER_GAIN );
mMuteControl = (BooleanControl)mOutput.getControl(
BooleanControl.Type.MUTE );
mExecutorService = Executors.newSingleThreadScheduledExecutor(
new NamingThreadFactory( "audio (mono) output" ) );
/* Run the queue processor task every 40 milliseconds */
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
@@ -76,19 +100,24 @@ public class MonoAudioOutput implements Listener<AudioPacket>
}
}
public void setMuted( boolean muted )
@Override
public String getChannelName()
{
mMuted.set( muted );
return "Mono";
}
public void setSquelched( boolean squelched )
/**
* Timestamp when most recent buffer was received
*/
public long getLastBufferReceived()
{
mSquelched.set( squelched );
return mLastBufferReceived;
}
public void dispose()
{
super.dispose();
mCanProcessAudio = false;
mBuffer.clear();
@@ -109,10 +138,13 @@ public class MonoAudioOutput implements Listener<AudioPacket>
{
if( mCanProcessAudio )
{
mLastBufferReceived = System.currentTimeMillis();
mBuffer.add( packet );
}
}
public class QueueProcessor implements Runnable
{
private AtomicBoolean mProcessing = new AtomicBoolean();
@@ -131,23 +163,39 @@ public class MonoAudioOutput implements Listener<AudioPacket>
List<AudioPacket> packets = new ArrayList<AudioPacket>();
mBuffer.drainTo( packets );
if( packets.size() > 0 )
{
for( AudioPacket packet: packets )
{
if( mCanProcessAudio )
{
if( mMuted.get() || mSquelched.get() )
if( isSquelched() )
{
checkStop();
}
else if( packet.getType() == Type.AUDIO )
{
mOutput.write( packet.getAudioData(), 0,
packet.getAudioData().length );
float[] samples = packet.getAudioData();
/* Little-endian byte buffer */
ByteBuffer buffer =
ByteBuffer.allocate( samples.length * 2 )
.order( ByteOrder.LITTLE_ENDIAN );
ShortBuffer shortBuffer = buffer.asShortBuffer();
for( float sample: samples )
{
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
}
mOutput.write( buffer.array(), 0,
buffer.array().length );
checkStart();
broadcast( packet.getMetadata() );
}
}
}
@@ -163,21 +211,50 @@ public class MonoAudioOutput implements Listener<AudioPacket>
private void checkStart()
{
if( mCanProcessAudio && !mMuted.get() && !mSquelched.get() &&
if( mCanProcessAudio && !isSquelched() &&
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
{
mOutput.start();
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
}
}
private void checkStop()
{
if( mCanProcessAudio && mOutput.isRunning() &&
( mOutput.available() >= STOP_THRESHOLD || mMuted.get() || mSquelched.get() ) )
( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
{
mOutput.drain();
mOutput.stop();
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
}
}
}
@Override
public BooleanControl getMuteControl()
{
return mMuteControl;
}
@Override
public boolean hasMuteControl()
{
return mMuteControl != null;
}
@Override
public FloatControl getGainControl()
{
return mGainControl;
}
@Override
public boolean hasGainControl()
{
return mGainControl != null;
}
}

View File

@@ -0,0 +1,8 @@
package audio.squelch;
import sample.Listener;
public interface ISquelchStateListener
{
public Listener<SquelchState> getSquelchStateListener();
}

View File

@@ -0,0 +1,9 @@
package audio.squelch;
import sample.Listener;
public interface ISquelchStateProvider
{
public void setSquelchStateListener( Listener<SquelchState> listener );
public void removeSquelchStateListener();
}

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package audio.squelch;
/**
* Provides squelch control based on presense of decoded audio samples.
*
* NOTE: this class is not yet fully implemented with audio sample processing
* and currently only provides user driven squelch control
*/
public class ManualSquelchController extends SquelchController
{
public ManualSquelchController()
{
mSquelchMode = SquelchMode.NONE;
}
@Override
public void setSquelchMode( SquelchMode mode )
{
//TODO: add functionality
}
}

View File

@@ -0,0 +1,122 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package audio.squelch;
import sample.Broadcaster;
import sample.Listener;
import sample.real.RealBuffer;
import sample.real.RealSampleListener;
import sample.real.RealToRealBufferAssembler;
/**
* Squelch control - receives single audio samples or real audio buffers and
* adjusts squelch state either based on the audio samples or based on external
* control inputs.
*
* Assembles received single audio samples into real sample buffers for
* consumption by an external audio processor, like the AudioPacketProcessor
* class.
*/
public abstract class SquelchController
implements Listener<RealBuffer>, RealSampleListener
{
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
private Listener<RealBuffer> mAudioListener;
protected SquelchState mSquelchState = SquelchState.UNSQUELCH;
protected SquelchMode mSquelchMode = SquelchMode.MANUAL;
private RealToRealBufferAssembler mSampleAssembler = new RealToRealBufferAssembler( 512 );
public SquelchController()
{
mSampleAssembler.setListener( this );
}
@Override
public void receive( float sample )
{
mSampleAssembler.receive( sample );
}
@Override
public void receive( RealBuffer buffer )
{
if( mAudioListener != null )
{
mAudioListener.receive( buffer );
}
}
public abstract void setSquelchMode( SquelchMode mode );
public SquelchMode getSquelchMode()
{
return mSquelchMode;
}
public void setSquelchState( SquelchState state )
{
mSquelchState = state;
}
public SquelchState getSquelchState()
{
return mSquelchState;
}
public boolean isSquelched()
{
switch( mSquelchMode )
{
case AUTOMATIC:
case MANUAL:
return mSquelchState == SquelchState.SQUELCH;
case NONE:
return false;
default:
return false;
}
}
public void addSquelchStateListener( Listener<SquelchState> listener )
{
if( listener != null )
{
mSquelchStateBroadcaster.addListener( listener );
/* Send current squelch state to the listener */
listener.receive( mSquelchState );
}
}
public void removeSquelchStateListener( Listener<SquelchState> listener )
{
mSquelchStateBroadcaster.removeListener( listener );
}
public void setAudioListener( Listener<RealBuffer> listener )
{
mAudioListener = listener;
}
public void removeAudioListener( Listener<RealBuffer> listener )
{
mAudioListener = null;
}
}

View File

@@ -0,0 +1,25 @@
package audio.squelch;
/**
* Squelch state for a decoding channel. The state determines the handling of
* any audio packets processed by the channel state.
*/
public enum SquelchMode
{
/**
* Automatic: decoded message activity dictates squelch state and determines
* if audio is passed
*/
AUTOMATIC,
/**
* Manually applied squelch noise level threshold determines if audio is
* passed.
*/
MANUAL,
/**
* No Squelch - no squelch is applied and all audio is passed
*/
NONE;
}

View File

@@ -0,0 +1,7 @@
package audio.squelch;
public enum SquelchState
{
SQUELCH,
UNSQUELCH;
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,49 +15,41 @@
* 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 decode.tait;
import message.Message;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
package audio.squelch;
public class Tait1200ChannelState extends AuxChannelState
public class TrunkingSquelchController extends SquelchController
{
private Tait1200ActivitySummary mActivitySummary;
public Tait1200ChannelState( ChannelState parentState )
private boolean mAllowSquelchOverride = true;
public TrunkingSquelchController()
{
super( parentState );
}
/**
* Provides control over audio stream as directed by an external trunking
* channel state.
*
* Override automatic squelch control by setting the squelch mode to none.
*
* Disable automatic control override by setting allowManualOverride to false;
*
* Note: this control does not respond to squelch mode manual.
*/
public TrunkingSquelchController( boolean allowSquelchOverride )
{
mAllowSquelchOverride = allowSquelchOverride;
mActivitySummary =
new Tait1200ActivitySummary( parentState.getAliasList() );
mSquelchMode = SquelchMode.AUTOMATIC;
}
@Override
public void reset()
public void setSquelchMode( SquelchMode mode )
{
}
@Override
public void fade()
{
}
@Override
public void receive( Message message )
{
if( message instanceof Tait1200GPSMessage )
if( mode == SquelchMode.AUTOMATIC ||
( mode == SquelchMode.NONE && mAllowSquelchOverride ) )
{
mActivitySummary.receive( message );
// mParentChannelState.receiveCallEvent(
// Tait1200CallEvent.getLJ1200Event( (Tait1200Message)message ) );
mSquelchMode = mode;
}
}
@Override
public String getActivitySummary()
{
return mActivitySummary.getSummary();
}
}
}

View File

@@ -20,14 +20,14 @@ package buffer;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import sample.complex.ComplexSample;
import sample.complex.Complex;
/**
* Provides a complex sample enumeration wrapper around a byte array containing
* interleaved byte-length I & Q sample values in the range of 0 - 255, converting
* those samples into complex samples with value range of -128 to 0 to 127.
*/
public class ComplexByteBuffer implements Enumeration<ComplexSample>
public class ComplexByteBuffer implements Enumeration<Complex>
{
private static float[] mValues;
@@ -81,11 +81,11 @@ public class ComplexByteBuffer implements Enumeration<ComplexSample>
* method to ensure you do not read past the end of the buffer.
*/
@Override
public ComplexSample nextElement()
public Complex nextElement()
{
// return new ComplexSample( mValues[ ( mBytes[ mPointer++ ] & 0xFF ) ],
// mValues[ ( mBytes[ mPointer++ ] & 0xFF ) ] );
return new ComplexSample( mValues[ ( mBytes.get() & 0xFF ) ],
return new Complex( mValues[ ( mBytes.get() & 0xFF ) ],
mValues[ ( mBytes.get() & 0xFF ) ] );
}
}

View File

@@ -19,7 +19,7 @@ package buffer;
import java.util.Arrays;
import sample.complex.ComplexSample;
import sample.complex.Complex;
/**
* Circular sample buffer - allocates a buffer and stores samples in a circular
@@ -30,14 +30,14 @@ import sample.complex.ComplexSample;
*/
public class ComplexCircularBuffer
{
ComplexSample[] mBuffer;
Complex[] mBuffer;
int mBufferPointer = 0;
public ComplexCircularBuffer( int size )
{
mBuffer = new ComplexSample[ size ];
mBuffer = new Complex[ size ];
Arrays.fill( mBuffer, new ComplexSample( 1.0f, 1.0f ) );
Arrays.fill( mBuffer, new Complex( 1.0f, 1.0f ) );
}
/**
@@ -46,16 +46,16 @@ public class ComplexCircularBuffer
* @param sample
* @return
*/
public ComplexSample get( ComplexSample sample )
public Complex get( Complex sample )
{
ComplexSample retVal = mBuffer[ mBufferPointer ];
Complex retVal = mBuffer[ mBufferPointer ];
put( sample );
return retVal;
}
public void put( ComplexSample sample )
public void put( Complex sample )
{
mBuffer[ mBufferPointer ] = sample;
@@ -67,9 +67,9 @@ public class ComplexCircularBuffer
}
}
public ComplexSample[] get( int count )
public Complex[] get( int count )
{
ComplexSample[] samples = new ComplexSample[ count ];
Complex[] samples = new Complex[ count ];
int tempCounter = mBufferPointer;

View File

@@ -21,7 +21,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import sample.complex.ComplexSample;
import sample.complex.Complex;
public class ComplexSampleBufferAssembler
{
@@ -41,7 +41,7 @@ public class ComplexSampleBufferAssembler
* Load the sample's bytes, left then right, little endian, into the
* backing byte array
*/
public void put( ComplexSample sample )
public void put( Complex sample )
{
mByteBuffer.putShort( (short)( sample.left() * Short.MAX_VALUE ) );
mByteBuffer.putShort( (short)( sample.right() * Short.MAX_VALUE ) );

View File

@@ -17,7 +17,7 @@
******************************************************************************/
package buffer;
import sample.complex.ComplexSample;
import sample.complex.Complex;
/**
* Implements a FIFO circular buffer for complex samples that provides access
@@ -26,17 +26,17 @@ import sample.complex.ComplexSample;
*/
public class ComplexTappedCircularBuffer
{
private ComplexSample[] mBuffer;
private Complex[] mBuffer;
private int mBufferPointer = 0;
public ComplexTappedCircularBuffer( int length )
{
mBuffer = new ComplexSample[ length ];
mBuffer = new Complex[ length ];
//Pre-load the buffer with 0-valued samples
for( int x = 0; x < length; x++ )
{
mBuffer[ x ] = new ComplexSample( 0, 0 );
mBuffer[ x ] = new Complex( 0, 0 );
}
}
@@ -44,7 +44,7 @@ public class ComplexTappedCircularBuffer
* Adds the sample to the buffer and removes the oldest sample
* @param sample
*/
public void add( ComplexSample sample )
public void add( Complex sample )
{
mBuffer[ mBufferPointer++ ] = sample;
@@ -68,7 +68,7 @@ public class ComplexTappedCircularBuffer
* @throws ArrayIndexOutOfBoundsException if you attempt to access a sample
* outside of the buffer size
*/
public ComplexSample get( int index )
public Complex get( int index )
{
/* Since the samples are stored in a forward fashion, we have to access
* the samples in reverse to comply with the access contract, where

View File

@@ -21,9 +21,9 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Enumeration;
import sample.complex.ComplexSample;
import sample.complex.Complex;
public class ShortComplexByteBuffer implements Enumeration<ComplexSample>
public class ShortComplexByteBuffer implements Enumeration<Complex>
{
private ByteBuffer mByteBuffer;
@@ -45,9 +45,9 @@ public class ShortComplexByteBuffer implements Enumeration<ComplexSample>
}
@Override
public ComplexSample nextElement()
public Complex nextElement()
{
return new ComplexSample( (float)mByteBuffer.getShort(),
return new Complex( (float)mByteBuffer.getShort(),
(float)mByteBuffer.getShort() );
}
}

View File

@@ -21,10 +21,10 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Enumeration;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import source.mixer.MixerChannel;
public class StereoByteBuffer implements Enumeration<ComplexSample>
public class StereoByteBuffer implements Enumeration<Complex>
{
private ByteBuffer mByteBuffer;
@@ -51,9 +51,9 @@ public class StereoByteBuffer implements Enumeration<ComplexSample>
}
@Override
public ComplexSample nextElement()
public Complex nextElement()
{
return new ComplexSample( (float)mByteBuffer.getShort(),
return new Complex( (float)mByteBuffer.getShort(),
(float)mByteBuffer.getShort() );
}
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,6 +29,8 @@ import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;
import map.IconManager;
import module.decode.DecoderType;
import module.decode.config.DecodeConfigFactory;
import playlist.PlaylistNode;
import source.config.SourceConfigTuner;
import source.recording.RecordingGroupNode;
@@ -37,14 +39,11 @@ import source.tuner.TunerGroupNode;
import source.tuner.TunerNode;
import source.tuner.TunerSelectionListener;
import controller.channel.Channel;
import controller.channel.ChannelEventListener;
import controller.channel.ChannelNode;
import controller.site.Site;
import controller.system.System;
import controller.system.SystemListNode;
import controller.system.SystemNode;
import decode.DecoderType;
import decode.config.DecodeConfigFactory;
public class ConfigurationControllerModel extends DefaultTreeModel
{
@@ -54,12 +53,8 @@ public class ConfigurationControllerModel extends DefaultTreeModel
private ResourceManager mResourceManager;
private ArrayList<ChannelEventListener> mChannelConfigChangeListeners =
new ArrayList<ChannelEventListener>();
private ArrayList<TunerSelectionListener> mTunerSelectionListeners =
new ArrayList<TunerSelectionListener>();
private PlaylistNode mPlaylistNode;
private TunerGroupNode mTunerGroupNode;

View File

@@ -21,7 +21,6 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
@@ -30,23 +29,24 @@ import javax.swing.event.ChangeListener;
import map.MapPanel;
import message.Message;
import module.decode.event.CallEventPanel;
import module.decode.event.MessageActivityPanel;
import module.decode.state.ChannelList;
import net.miginfocom.swing.MigLayout;
import sample.Listener;
import spectrum.ChannelSpectrumPanel;
import audio.AudioPanel;
import com.jidesoft.swing.JideSplitPane;
import com.jidesoft.swing.JideTabbedPane;
import controller.activity.CallEventPanel;
import controller.activity.MessageActivityPanel;
import controller.channel.ChannelManager;
import controller.state.ChannelStateList;
public class ControllerPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private ChannelStateList mChannelStateList;
private ChannelList mChannelStateList;
private CallEventPanel mCallEventPanel = new CallEventPanel();
@@ -135,7 +135,8 @@ public class ControllerPanel extends JPanel
/* Channel state list */
mChannelStateList = new ChannelStateList( mResourceManager.getSettingsManager() );
mChannelStateList = new ChannelList( mResourceManager.getPlaylistManager(),
mResourceManager.getSettingsManager() );
/* Register each of the components to receive channel events when the
* channels are selected or change */
@@ -147,12 +148,20 @@ public class ControllerPanel extends JPanel
JScrollPane channelStateListScroll = new JScrollPane();
channelStateListScroll.getViewport().setView( mChannelStateList );
channelStateListScroll.setPreferredSize( new Dimension( 150,
(int)channelStateListScroll.getPreferredSize().getHeight() ) );
channelStateListScroll.setPreferredSize( new Dimension( 200, 300 ) );
AudioPanel audioPanel = new AudioPanel( mResourceManager.getSettingsManager(),
mResourceManager.getAudioManager() );
audioPanel.setPreferredSize( new Dimension( 200, audioPanel.getPreferredHeight() ) );
JideSplitPane audioChannelListSplit = new JideSplitPane( JideSplitPane.VERTICAL_SPLIT );
audioChannelListSplit.setDividerSize( 5 );
audioChannelListSplit.add( audioPanel );
audioChannelListSplit.add( channelStateListScroll );
JideSplitPane channelSplit = new JideSplitPane( JideSplitPane.HORIZONTAL_SPLIT );
channelSplit.setDividerSize( 5 );
channelSplit.add( channelStateListScroll );
channelSplit.add( audioChannelListSplit );
channelSplit.add( mTabbedPane );
add( channelSplit );

View File

@@ -0,0 +1,48 @@
package controller;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Thread factory that applies custom names to threads
*/
public class NamingThreadFactory implements ThreadFactory
{
static final AtomicInteger poolNumber = new AtomicInteger(1);
final ThreadGroup mThreadGroup;
final AtomicInteger mThreadNumber = new AtomicInteger(1);
final String mNamePrefix;
public NamingThreadFactory( String prefix )
{
SecurityManager s = System.getSecurityManager();
mThreadGroup = (s != null)? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
mNamePrefix = prefix + " pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread( Runnable runnable )
{
Thread thread = new Thread(mThreadGroup, runnable,
mNamePrefix + mThreadNumber.getAndIncrement(), 0 );
if( thread.isDaemon() )
{
thread.setDaemon( false );
}
if( thread.getPriority() != Thread.NORM_PRIORITY )
{
thread.setPriority( Thread.NORM_PRIORITY );
}
return thread;
}
}

View File

@@ -17,7 +17,6 @@
******************************************************************************/
package controller;
import alias.action.AliasActionManager;
import map.MapService;
import playlist.PlaylistManager;
import record.RecorderManager;
@@ -25,6 +24,7 @@ import settings.SettingsManager;
import source.SourceManager;
import source.recording.RecordingSourceManager;
import source.tuner.TunerManager;
import audio.AudioManager;
import controller.channel.ChannelManager;
import eventlog.EventLogManager;
@@ -33,6 +33,7 @@ import eventlog.EventLogManager;
*/
public class ResourceManager
{
private AudioManager mAudioManager;
private ChannelManager mChannelManager;
private EventLogManager mEventLogManager;
private PlaylistManager mPlaylistManager;
@@ -67,13 +68,24 @@ public class ResourceManager
* ChannelManager requires a reference to the ResourceManager so that
* channel processing chains can access system wide resources
*/
mThreadPoolManager = new ThreadPoolManager();
mAudioManager = new AudioManager( mThreadPoolManager );
mChannelManager = new ChannelManager( this );
mSourceManager = new SourceManager( this );
mRecordingSourceManager = new RecordingSourceManager( this );
mTunerManager = new TunerManager( this );
mController = new ConfigurationControllerModel( this );
mMapService = new MapService( this );
mThreadPoolManager = new ThreadPoolManager();
}
public AudioManager getAudioManager()
{
return mAudioManager;
}
public void setAudioManager( AudioManager audioManager )
{
mAudioManager = audioManager;
}
public ConfigurationControllerModel getController()

View File

@@ -19,13 +19,22 @@ package controller;
import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThreadPoolManager
{
private final static Logger mLog = LoggerFactory.getLogger( ThreadPoolManager.class );
private int THREAD_POOL_SIZE = 2;
private int MAX_TASK_COUNT = 24;
@@ -46,7 +55,8 @@ public class ThreadPoolManager
{
if( mExecutor == null )
{
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE );
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
new NamingThreadFactory( "sdrtrunk" ) );
}
if( mTasks.size() <= MAX_TASK_COUNT )
@@ -71,7 +81,8 @@ public class ThreadPoolManager
{
if( mExecutor == null )
{
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE );
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
new NamingThreadFactory( "sdrtrunk" ) );
}
if( mTasks.size() <= MAX_TASK_COUNT )
@@ -109,6 +120,7 @@ public class ThreadPoolManager
public enum ThreadType
{
AUDIO_PROCESSING,
SOURCE_SAMPLE_PROCESSING,
DECIMATION,
DECODER;

View File

@@ -17,17 +17,13 @@
******************************************************************************/
package controller.channel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JSeparator;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@@ -35,12 +31,23 @@ import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import message.Message;
import module.Module;
import module.ProcessingChain;
import module.decode.DecoderFactory;
import module.decode.config.AuxDecodeConfiguration;
import module.decode.config.DecodeConfigFactory;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEventModel;
import module.decode.event.MessageActivityModel;
import module.decode.state.ChannelState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.config.RecordConfiguration;
import sample.Listener;
import source.Source;
import source.SourceException;
import source.SourceType;
import source.config.SourceConfigFactory;
import source.config.SourceConfigRecording;
@@ -48,19 +55,15 @@ import source.config.SourceConfigTuner;
import source.config.SourceConfiguration;
import source.tuner.TunerChannel;
import source.tuner.TunerChannel.Type;
import source.tuner.frequency.AutomaticFrequencyControl;
import alias.AliasList;
import controller.ResourceManager;
import controller.activity.ActivitySummaryFrame;
import controller.channel.ChannelEvent.Event;
import controller.config.Configuration;
import controller.site.Site;
import controller.state.ChannelState;
import controller.state.ChannelState.State;
import controller.system.System;
import decode.config.AuxDecodeConfiguration;
import decode.config.DecodeConfigFactory;
import decode.config.DecodeConfiguration;
import eventlog.MessageEventLogger;
import eventlog.config.EventLogConfiguration;
import filter.FilterSet;
@XmlSeeAlso( { Configuration.class } )
@XmlRootElement( name = "channel" )
@@ -68,15 +71,16 @@ public class Channel extends Configuration
{
private final static Logger mLog = LoggerFactory.getLogger( Channel.class );
private static final boolean ENABLED = true;
private static final boolean DISABLED = false;
private static final boolean BROADCAST_CHANGE = true;
public enum ChannelType
{
STANDARD,
TRAFFIC
};
private static final boolean ENABLED = true;
private static final boolean DISABLED = false;
private static final boolean BROADCAST_CHANGE = true;
private CopyOnWriteArrayList<ChannelEventListener> mChannelListeners =
new CopyOnWriteArrayList<ChannelEventListener>();
@@ -84,8 +88,7 @@ public class Channel extends Configuration
private CopyOnWriteArrayList<Listener<Message>> mMessageListeners =
new CopyOnWriteArrayList<Listener<Message>>();
private HashMap<Integer,Channel> mTrafficChannels =
new HashMap<Integer,Channel>();
private Map<String,Channel> mTrafficChannels = new HashMap<>();
private DecodeConfiguration mDecodeConfiguration =
DecodeConfigFactory.getDefaultDecodeConfiguration();
@@ -105,7 +108,15 @@ public class Channel extends Configuration
private boolean mEnabled;
private boolean mSelected;
private ChannelType mChannelType;
private CallEventModel mCallEventModel;
private MessageActivityModel mMessageActivityModel;
private MessageEventLogger mEventLogger;
private ProcessingChain mProcessingChain;
private ChannelState mChannelState;
private long mTrafficChannelTimeout =
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS;
/**
* Constructs a new standard channel with a default name of "New Channel"
@@ -135,34 +146,46 @@ public class Channel extends Configuration
mChannelType = channelType;
}
public ChannelType getChannelType()
{
return mChannelType;
}
/**
* Indicates if the channel has Automatic or Direct Frequency Control
*/
public boolean hasFrequencyControl()
{
return mProcessingChain != null &&
mProcessingChain.getDecoder().hasFrequencyCorrectionControl();
mProcessingChain.hasFrequencyCorrectionControl();
}
public long getFrequencyCorrection()
{
if( mProcessingChain != null )
if( hasFrequencyControl() )
{
return mProcessingChain.getDecoder().getFrequencyCorrection();
return mProcessingChain.getFrequencyCorrectionControl().getErrorCorrection();
}
return 0;
}
/**
* Sets the channel state traffic channel call end timer to the specified
* value.
*/
public void setTrafficChannelTimeout( long milliseconds )
{
mTrafficChannelTimeout = milliseconds;
if( mChannelState != null )
{
mChannelState.setTrafficChannelTimeout( milliseconds );
}
}
/**
* Sets the resource manager responsible for this channel and registers
* the channel manager as a listener to this channel. Fires channel add
* channel event to the newly added channel manager channel listener.
*
* Note: resource manager instance is applied after object construction
* since we construct the channel objects from the xml playlist
*/
public void setResourceManager( ResourceManager resourceManager )
{
@@ -170,7 +193,7 @@ public class Channel extends Configuration
/* Add system-wide channel listeners onto this channel */
addListeners( mResourceManager.getChannelManager().getChannelListeners() );
/* If we're enabled, fire enable changed event to get processing started */
if( mEnabled )
{
@@ -200,7 +223,7 @@ public class Channel extends Configuration
*/
public boolean isProcessing()
{
return mProcessingChain != null && mProcessingChain.isProcessing();
return mProcessingChain != null && mProcessingChain.processing();
}
/**
@@ -216,100 +239,51 @@ public class Channel extends Configuration
{
mSelected = selected;
if( mProcessingChain != null &&
mProcessingChain.isProcessing() &&
mProcessingChain.getAudioOutput() != null )
{
mProcessingChain.getAudioOutput().setAudioPlaybackEnabled( selected );
}
fireChannelEvent( Event.CHANGE_SELECTED );
}
public JMenu getContextMenu()
@XmlTransient
public CallEventModel getCallEventModel()
{
JMenu menu = new JMenu( mSite + "-" + mName );
if( mEnabled )
{
JMenuItem disable = new JMenuItem( "Disable" );
disable.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setEnabled( DISABLED, BROADCAST_CHANGE );
getResourceManager().getPlaylistManager().save();
}
} );
menu.add( disable );
menu.add( new JSeparator() );
JMenuItem actySummaryItem =
new JMenuItem( "Activity Summary" );
actySummaryItem.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
if( mResourceManager != null )
{
ProcessingChain chain = getProcessingChain();
if( chain != null )
{
ChannelState state = chain.getChannelState();
if( state != null )
{
String summary = state.getActivitySummary();
new ActivitySummaryFrame( summary,
mResourceManager.getController().getTree() );
}
}
}
}
} );
menu.add( actySummaryItem );
}
else
{
JMenuItem enable = new JMenuItem( "Enable" );
enable.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setEnabled( ENABLED, BROADCAST_CHANGE );
getResourceManager().getPlaylistManager().save(); }
} );
menu.add( enable );
}
menu.add( new JSeparator() );
JMenuItem deleteItem = new JMenuItem( "Delete" );
deleteItem.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
dispose();
getResourceManager().getPlaylistManager().save(); }
} );
menu.add( deleteItem );
return menu;
return mCallEventModel;
}
/**
* Returns the MessageEventLogger for this processing chain
*/
@XmlTransient
public MessageEventLogger getEventLogger()
{
return mEventLogger;
}
/**
* Sets the MessageEventLogger for this processing chain
*/
public void setEventLogger( MessageEventLogger eventLogger )
{
mEventLogger = eventLogger;
}
@XmlTransient
public MessageActivityModel getMessageActivityModel()
{
return mMessageActivityModel;
}
@XmlTransient
public ChannelState getChannelState()
{
return mChannelState;
}
@XmlTransient
public ChannelType getChannelType()
{
return mChannelType;
}
/**
* Orderly shutdown method when this channel is going to be deleted.
*/
@@ -393,6 +367,11 @@ public class Channel extends Configuration
return mSystem;
}
public boolean hasSystem()
{
return mSystem != null;
}
/**
* Sets the owning system for this channel and optionally broadcasts a
* system change channel event.
@@ -415,6 +394,11 @@ public class Channel extends Configuration
return mSite;
}
public boolean hasSite()
{
return mSite != null;
}
/**
* Sets the owning site for this channel and optionally broadcasts a site
* change channel event.
@@ -468,7 +452,14 @@ public class Channel extends Configuration
if( fireChannelEvent )
{
fireChannelEvent( Event.CHANGE_ENABLED );
if( enabled )
{
fireChannelEvent( Event.CHANNEL_ENABLED );
}
else
{
fireChannelEvent( Event.CHANNEL_DISABLED );
}
}
}
@@ -522,11 +513,6 @@ public class Channel extends Configuration
public void setDecodeConfiguration( DecodeConfiguration config )
{
mDecodeConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateDecoder();
}
}
/**
@@ -544,11 +530,6 @@ public class Channel extends Configuration
public void setAuxDecodeConfiguration( AuxDecodeConfiguration config )
{
mAuxDecodeConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateDecoder();
}
}
/**
@@ -581,11 +562,6 @@ public class Channel extends Configuration
public void setSourceConfiguration( SourceConfiguration config )
{
mSourceConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateSource();
}
}
/**
@@ -618,11 +594,6 @@ public class Channel extends Configuration
public void setEventLogConfiguration( EventLogConfiguration config )
{
mEventLogConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateEventLogging();
}
}
/**
@@ -655,11 +626,6 @@ public class Channel extends Configuration
public void setRecordConfiguration( RecordConfiguration config )
{
mRecordConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateRecording();
}
}
/**
@@ -730,38 +696,124 @@ public class Channel extends Configuration
*/
private void start()
{
if( mEnabled && mProcessingChain == null && mResourceManager != null )
if( mEnabled && mResourceManager != null )
{
mProcessingChain = new ProcessingChain( this, mResourceManager );
setupChannelState();
try
{
setup();
mProcessingChain.start();
fireChannelEvent( Event.CHANNEL_PROCESSING_STARTED );
}
catch( SourceException se )
{
mLog.error( "Error obtaining source for channel" );
}
}
}
private void setupChannelState()
{
if( mChannelState == null )
{
mChannelState = new ChannelState( mResourceManager
.getThreadPoolManager(), mChannelType );
mChannelState.setTrafficChannelTimeout( mTrafficChannelTimeout );
}
}
/**
* Sets up the channel and prepares to commense processing. Sets up the
* processing chain and requests a source.
*
* @throws SourceException - if no source is available or if there is an
* error obtaining a source
*/
public void setup() throws SourceException
{
setupProcessingChain();
setupSource();
//TODO: Add logging
//TODO: Add recorder(s)
}
private void setupProcessingChain()
{
if( mProcessingChain == null )
{
mProcessingChain = new ProcessingChain(
mResourceManager.getThreadPoolManager() );
/* Get the optional alias list for the decode modules to use */
AliasList aliasList = mResourceManager.getPlaylistManager()
.getPlayist().getAliasDirectory().getAliasList( mAliasListName );
/* Add the channel state as a module */
mProcessingChain.addModule( mChannelState );
/* Processing Modules */
List<Module> modules = DecoderFactory.getModules( mChannelType,
mResourceManager, mDecodeConfiguration, mAuxDecodeConfiguration,
aliasList );
mProcessingChain.addModules( modules );
/* Get message filters for the set of processing modules */
FilterSet messageFilter = DecoderFactory.getMessageFilters( modules );
/* Setup the message activity model and add message listeners */
mMessageActivityModel = new MessageActivityModel( messageFilter );
mProcessingChain.addMessageListener( mMessageActivityModel );
/* Add audio manager as listener for audio packets */
mProcessingChain.addAudioPacketListener(
mResourceManager.getAudioManager() );
/* Add system-wide message listeners */
mProcessingChain.addListeners(
mProcessingChain.addMessageListeners(
mResourceManager.getChannelManager().getMessageListeners() );
/* Add individual message listeners */
mProcessingChain.addListeners( mMessageListeners );
mProcessingChain.start();
fireChannelEvent( Event.PROCESSING_STARTED );
}
/* Add individual message listeners */
mProcessingChain.addMessageListeners( mMessageListeners );
/* If this is a traffic channel, override the call fade timeout before
* we set the state to call, so that it will auto-expire after the
* fade timeout and be auto deleted */
if( mChannelType == ChannelType.TRAFFIC )
/* Create the call event model to receive call events */
mCallEventModel = new CallEventModel();
mProcessingChain.addCallEventListener( mCallEventModel );
}
}
/**
* Attempts to obtain a source and apply it to the processing chain.
*
* @throws SourceException - if the processing chain is not setup or if
* there is an error produced while obtaining the source
*/
private void setupSource() throws SourceException
{
if( mProcessingChain != null )
{
if( mProcessingChain.isProcessing() )
Source source = mResourceManager.getSourceManager()
.getSource( getSourceConfiguration(), getDecodeConfiguration()
.getDecoderType().getChannelBandwidth() );
if( source != null )
{
mProcessingChain.getChannelState().setCallFadeTimeout( 40000 );
mProcessingChain.getChannelState().setState( State.CALL );
mProcessingChain.setSource( source );
}
else
{
mProcessingChain.getChannelState().setCallFadeTimeout( 5000 );
/* Channel state should already be no_tuner */
throw new SourceException( "Couldn't obtain tuner channel source" );
}
}
else
{
throw new SourceException( "Cannot obtain source for a null "
+ "processing chain - setup the processing chain first" );
}
}
@@ -770,27 +822,14 @@ public class Channel extends Configuration
*/
private void stop()
{
/* Stop and remove any traffic channels */
for( Channel traffic: mTrafficChannels.values() )
{
traffic.stop();
traffic.dispose();
}
mTrafficChannels.clear();
if( !mEnabled && mProcessingChain != null )
{
mProcessingChain.stop();
mProcessingChain.dispose();
mProcessingChain = null;
mSelected = false;
}
fireChannelEvent( Event.PROCESSING_STOPPED );
fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED );
}
/**
@@ -798,9 +837,18 @@ public class Channel extends Configuration
*/
public void fireChannelEvent( Event event )
{
ChannelEvent channelEvent = new ChannelEvent( this, event );
for( ChannelEventListener listener: mChannelListeners )
{
listener.channelChanged( new ChannelEvent( this, event ) );
listener.channelChanged( channelEvent );
}
/* Send event to processing chain so modules can respond to channel
* selection events */
if( mProcessingChain != null )
{
mProcessingChain.getChannelEventListener().receive( channelEvent );
}
}
@@ -813,7 +861,7 @@ public class Channel extends Configuration
if( !mChannelListeners.contains( listener ) )
{
mChannelListeners.add( listener );
listener.channelChanged(
new ChannelEvent( this, Event.CHANNEL_ADDED ) );
}
@@ -850,7 +898,7 @@ public class Channel extends Configuration
if( isProcessing() )
{
mProcessingChain.addListener( listener );
mProcessingChain.addMessageListener( listener );
}
}
@@ -858,9 +906,9 @@ public class Channel extends Configuration
{
mMessageListeners.remove( listener );
if( isProcessing() )
if( mProcessingChain != null )
{
mProcessingChain.removeListener( listener );
mProcessingChain.removeMessageListener( listener );
}
}
@@ -870,13 +918,15 @@ public class Channel extends Configuration
*/
public boolean isWithin( long minimum, long maximum )
{
return getTunerChannel() != null &&
getTunerChannel().isWithin( minimum, maximum );
TunerChannel tunerChannel = getTunerChannel();
return tunerChannel != null &&
tunerChannel.isWithin( minimum, maximum );
}
public void addTrafficChannel( int channelNumber, Channel channel )
public void addTrafficChannel( String channelID, Channel channel )
{
mTrafficChannels.put( channelNumber, channel );
mTrafficChannels.put( channelID, channel );
}
public boolean hasTrafficChannel( Channel channel )
@@ -886,22 +936,11 @@ public class Channel extends Configuration
public void removeTrafficChannel( Channel channel )
{
int keyToRemove = -1;
String name = channel.getChannelDisplayName();
if( mTrafficChannels.values().contains( channel ) )
if( mTrafficChannels.containsKey( name ) )
{
for( Integer channelNumber: mTrafficChannels.keySet() )
{
if( mTrafficChannels.get( channelNumber ).equals( channel ) )
{
keyToRemove = channelNumber;
}
}
}
if( keyToRemove != -1 )
{
mTrafficChannels.remove( keyToRemove );
mTrafficChannels.remove( name );
}
}
@@ -910,8 +949,9 @@ public class Channel extends Configuration
return Collections.unmodifiableCollection( mTrafficChannels.values() );
}
public boolean hasTrafficChannel( int channelNumber )
public boolean hasTrafficChannel( String name )
{
return mTrafficChannels.containsKey( channelNumber );
return mTrafficChannels.containsKey( name );
}
}

View File

@@ -35,6 +35,8 @@ import javax.swing.JTextField;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import module.decode.AuxDecodeComponentEditor;
import module.decode.DecodeComponentEditor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
@@ -47,8 +49,6 @@ import alias.AliasList;
import com.jidesoft.swing.JideTabbedPane;
import controller.Editor;
import decode.AuxDecodeComponentEditor;
import decode.DecodeComponentEditor;
import eventlog.EventLogComponentEditor;
public class ChannelEditor extends Editor implements ActionListener
@@ -99,7 +99,7 @@ public class ChannelEditor extends Editor implements ActionListener
/**
* ComboBox: Alias Lists
*/
add( new JLabel( "Aliases:" ) );
add( new JLabel( "Alias List:" ) );
mComboAliasLists = new JComboBox<AliasList>();

View File

@@ -34,19 +34,26 @@ public class ChannelEvent
{
CHANGE_ALIAS_LIST,
CHANGE_DECODER,
CHANGE_ENABLED,
CHANGE_EVENT_LOGGER,
CHANGE_NAME,
CHANGE_SELECTED,
CHANGE_SITE,
CHANGE_RECORDER,
CHANGE_SELECTED,
CHANGE_SOURCE,
CHANGE_SYSTEM,
CHANNEL_ADDED,
CHANNEL_ADDED,
CHANNEL_DELETED,
CHANNEL_ENABLED,
CHANNEL_DISABLED,
PROCESSING_STARTED,
PROCESSING_STOPPED,
CHANNEL_PROCESSING_STARTED,
CHANNEL_PROCESSING_STOPPED,
CHANNEL_STATE_RESET,
TRAFFIC_CHANNEL_ADDED,
TRAFFIC_CHANNEL_DELETED;
}
}

View File

@@ -17,8 +17,13 @@
******************************************************************************/
package controller.channel;
import gui.SDRTrunk;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.action.AliasActionManager;
import message.Message;
import sample.Listener;
@@ -26,6 +31,8 @@ import controller.ResourceManager;
public class ChannelManager implements ChannelEventListener
{
private final static Logger mLog = LoggerFactory.getLogger( ChannelManager.class );
private ArrayList<Channel> mChannels = new ArrayList<Channel>();
private ArrayList<ChannelEventListener> mChannelListeners =
@@ -56,13 +63,13 @@ public class ChannelManager implements ChannelEventListener
{
switch( event.getEvent() )
{
case CHANNEL_ADDED:
case CHANNEL_ENABLED:
if( !mChannels.contains( event.getChannel() ) )
{
mChannels.add( event.getChannel() );
}
break;
case CHANNEL_DELETED:
case CHANNEL_DISABLED:
mChannels.remove( event.getChannel() );
break;
}

View File

@@ -78,17 +78,17 @@ public class ChannelNode extends ConfigurableNode implements ChannelEventListene
{
/* Refresh the node for each of these events */
case CHANGE_DECODER:
case CHANGE_ENABLED:
case CHANGE_NAME:
case CHANGE_SELECTED:
case CHANGE_SITE:
case CHANGE_SYSTEM:
case PROCESSING_STARTED:
case PROCESSING_STOPPED:
case CHANNEL_ENABLED:
case CHANNEL_PROCESSING_STARTED:
case CHANNEL_PROCESSING_STOPPED:
this.refresh();
break;
/* We're being deleted, so cleanup */
case CHANNEL_DELETED:
case CHANNEL_DISABLED:
delete();
break;
default:
@@ -154,7 +154,7 @@ public class ChannelNode extends ConfigurableNode implements ChannelEventListene
public JPopupMenu getContextMenu()
{
JPopupMenu popupMenu = new JPopupMenu( "Channel Menu" );
popupMenu.add( getChannel().getContextMenu() );
// popupMenu.add( getChannel().getContextMenu() );
return popupMenu;
}

View File

@@ -17,7 +17,9 @@
******************************************************************************/
package controller.channel;
import module.ProcessingChainOld;
public interface ChannelSelectionListener
{
public void channelSelected( ProcessingChain channel );
public void channelSelected( ProcessingChainOld channel );
}

View File

@@ -0,0 +1,8 @@
package controller.channel;
import sample.Listener;
public interface IChannelEventListener
{
public Listener<ChannelEvent> getChannelEventListener();
}

View File

@@ -0,0 +1,9 @@
package controller.channel;
import sample.Listener;
public interface IChannelEventProvider
{
public void setChannelEventListener( Listener<ChannelEvent> listener );
public void removeChannelEventListener();
}

View File

@@ -15,7 +15,7 @@
* 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.map;
import java.util.ArrayList;

View File

@@ -15,7 +15,7 @@
* 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.map;
import java.awt.Component;
import java.awt.Dimension;
@@ -80,7 +80,6 @@ public class ChannelMapEditor extends JPanel implements ActionListener
@Override
public void focusLost( FocusEvent arg0 )
{
mLog.info( "text field action event fired, new value:" + mNameText.getText() );
mChannelMapCopy.setName( mNameText.getText() );
}
} );
@@ -208,7 +207,6 @@ public class ChannelMapEditor extends JPanel implements ActionListener
@Override
public void actionPerformed( ActionEvent e )
{
mLog.info( "Action Performed:" + e.getActionCommand() );
}
@Override

View File

@@ -15,7 +15,7 @@
* 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.map;
import java.util.ArrayList;

View File

@@ -15,7 +15,7 @@
* 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.map;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

View File

@@ -15,7 +15,7 @@
* 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.map;
import javax.swing.table.AbstractTableModel;

View File

@@ -15,7 +15,7 @@
* 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.map;
import java.awt.event.ActionEvent;

View File

@@ -15,7 +15,7 @@
* 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.map;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

View File

@@ -20,10 +20,10 @@ package controller.config;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import module.decode.config.AuxDecodeConfiguration;
import module.decode.config.DecodeConfiguration;
import record.config.RecordConfiguration;
import source.config.SourceConfiguration;
import decode.config.AuxDecodeConfiguration;
import decode.config.DecodeConfiguration;
import eventlog.config.EventLogConfiguration;
@XmlSeeAlso( { AuxDecodeConfiguration.class,

View File

@@ -1,80 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.state;
import message.Message;
import sample.Broadcaster;
import sample.Listener;
import controller.state.ChannelState.ChangedAttribute;
/**
* ChannelState for auxiliary decoders.
*/
public abstract class AuxChannelState implements Listener<Message>
{
protected ChannelState mParentChannelState;
protected Broadcaster<ChangedAttribute> mChangeBroadcaster =
new Broadcaster<ChangedAttribute>();
public AuxChannelState( ChannelState parentState )
{
mParentChannelState = parentState;
}
/**
* Prepare for garbage collect
*/
public void dispose()
{
mChangeBroadcaster.dispose();
mParentChannelState = null;
}
/**
* Call fade - change display and await a call to reset()
*/
public abstract void fade();
/**
* Activity summary for this decoder
*/
public abstract String getActivitySummary();
/**
* Call end - cleanup the display
*/
public abstract void reset();
public void addListener( Listener<ChangedAttribute> listener )
{
mChangeBroadcaster.addListener( listener );
}
public void removeListener( Listener<ChangedAttribute> listener )
{
mChangeBroadcaster.removeListener( listener );
}
public void broadcastChange( ChangedAttribute attribute )
{
if( mChangeBroadcaster != null )
{
mChangeBroadcaster.broadcast( attribute );
}
}
}

View File

@@ -1,697 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.state;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.JTable;
import message.Message;
import message.MessageFilterFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import audio.SquelchListener;
import audio.SquelchListener.SquelchState;
import audio.inverted.AudioType;
import audio.inverted.IAudioTypeListener;
import controller.activity.CallEvent;
import controller.activity.CallEvent.CallEventType;
import controller.activity.CallEventAliasCellRenderer;
import controller.activity.CallEventModel;
import controller.activity.MessageActivityModel;
import controller.channel.Channel.ChannelType;
import controller.channel.ProcessingChain;
import filter.FilterSet;
/**
* ChannelState provides a state machine for tracking a voice or data call
* through it's life cycle of CALL/DATA, FADE, and then END, and then back to
* an IDLE state. This class also provides squelch control for unsquelching
* sound to the speakers, and/or sound to an audio recorder
*
* State Descriptions:
* IDLE No voice or data call activity
* CALL/DATA A voice or data call has started, or is continuing
* CONTROL Control channel
* FADE The phase after a voice or data call when either an explicit
* call end has been received, or when no new signalling updates
* have been received, and the fade timer has expired. This phase
* allows for gui updates to signal to the user that the call is
* ended, while continuing to display the call details for the user
* END The phase after the voice or data call FADE, when the call is
* now over, and the gui should be reset to IDLE.
*
* When a call is started, invoke mTimerService.startFadeTimer() to start the
* call fade monitoring timer process that will automatically signal a call end
* once no more call signalling has been detected after the fade timeout has
* occurred, by invoking the fade() method.
*
* When a call ends, invoke the mTimerService.stopFadeTimer() and then invoke
* the mTimerService.startResetTimer(). Once the reset timeout occurs,
* the reset() method will be called, allowing the channel state to perform
* any reset actions, like clearing status and id labels on any view panels.
*
* AuxChannelState class allows for auxiliary decoders like Fleetsync to both
* have a state and leverage the state machine of the parent ChannelState.
* This allows a non-squelching channel state like for conventional fm to have
* an aux decoder like fleetsync, and allow the fleetsync auxchannelstate to
* leverage the call/end states of the parent conventional channelstate.
*
*/
public abstract class ChannelState implements Listener<Message>
{
private final static Logger mLog =
LoggerFactory.getLogger( ChannelState.class );
private State mState = State.IDLE;
private Object mCallFadeLock = new Object();
private Object mCallResetLock = new Object();
private long mFadeTimeout = -1;
private long mResetTimeout = -1;
protected long mCallFadeTimeout;
protected long mCallResetTimeout;
protected TimerService mTimerService = new TimerService();
protected ArrayList<SquelchListener> mSquelchListeners =
new ArrayList<SquelchListener>();
protected ArrayList<AuxChannelState> mAuxChannelStates =
new ArrayList<AuxChannelState>();
protected Broadcaster<ChangedAttribute> mChangeBroadcaster =
new Broadcaster<ChangedAttribute>();
protected IAudioTypeListener mAudioTypeListener;
protected AudioType mAudioType = AudioType.NORMAL;
protected CallEventModel mCallEventModel = new CallEventModel();
protected JTable mCallEventTable;
protected MessageActivityModel mMessageActivityModel;
protected JTable mMessageActivityTable;
protected ProcessingChain mProcessingChain;
protected AliasList mAliasList;
protected CallEvent mCurrentCallEvent;
public ChannelState( ProcessingChain processingChain, AliasList aliasList )
{
mProcessingChain = processingChain;
mAliasList = aliasList;
/* Register to receive decoded messages from the processing chain */
mProcessingChain.addListener( this );
SystemProperties props = SystemProperties.getInstance();
mCallFadeTimeout = props.get( "call.fade.timeout", 2000 );
mCallResetTimeout = props.get( "call.reset.timeout", 4000 );
/* Get a message filter for the primary decoder and aux decoders */
FilterSet messageFilter = MessageFilterFactory
.getMessageFilter( mProcessingChain.getChannel() );
mMessageActivityModel = new MessageActivityModel( messageFilter );
}
public void setCurrentCallEvent( CallEvent callEvent )
{
mCurrentCallEvent = callEvent;
}
public CallEvent getCurrentCallEvent()
{
return mCurrentCallEvent;
}
/**
* Allows setting the call event model to use for traffic channels, so that
* all traffic channels can share the parent control channel call event
* model.
* @param model
*/
public void setCallEventModel( CallEventModel model )
{
mCallEventModel = model;
}
public CallEventModel getCallEventModel()
{
return mCallEventModel;
}
public void receiveCallEvent( CallEvent event )
{
mCallEventModel.add( event );
}
public void dispose()
{
mTimerService.dispose();
mAuxChannelStates.clear();
mSquelchListeners.clear();
mChangeBroadcaster.dispose();
mChangeBroadcaster = null;
if( mProcessingChain.getChannel().getChannelType() == ChannelType.STANDARD )
{
mCallEventModel.dispose();
}
/* If we're a traffic channel, set the call event model to null so that
* we don't dispose the parent control channel's call event model */
else
{
mCallEventModel = null;
}
mMessageActivityModel.dispose();
mMessageActivityModel = null;
mAliasList = null;
mAudioTypeListener = null;
mCallEventModel = null;
mProcessingChain = null;
}
/**
* Channel Activity Summary - implemented by the sub-class.
*/
public abstract String getActivitySummary();
public AliasList getAliasList()
{
return mAliasList;
}
public boolean hasAliasList()
{
return mAliasList != null;
}
public void broadcastChange( final ChangedAttribute attribute )
{
mChangeBroadcaster.broadcast( attribute );
}
public void setCallFadeTimeout( long milliseconds )
{
mCallFadeTimeout = milliseconds;
}
public JTable getCallEventTable()
{
if( mCallEventTable == null )
{
mCallEventTable = new JTable( mCallEventModel );
mCallEventTable.setAutoCreateRowSorter( true );
mCallEventTable.setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
CallEventAliasCellRenderer renderer =
new CallEventAliasCellRenderer( mProcessingChain
.getResourceManager().getSettingsManager() );
mCallEventTable.getColumnModel().getColumn( 3 )
.setCellRenderer( renderer );
mCallEventTable.getColumnModel().getColumn( 5 )
.setCellRenderer( renderer );
}
return mCallEventTable;
}
/**
* Returns the message activity model.
*/
public MessageActivityModel getMessageActivityModel()
{
return mMessageActivityModel;
}
public void addAuxChannelState( AuxChannelState state )
{
mAuxChannelStates.add( state );
}
public ArrayList<AuxChannelState> getAuxChannelStates()
{
return mAuxChannelStates;
}
public void receive( Message message )
{
if( mMessageActivityModel != null )
{
mMessageActivityModel.receive( message );
}
for( AuxChannelState state: mAuxChannelStates )
{
if( state != null )
{
state.receive( message );
}
}
}
public ProcessingChain getProcessingChain()
{
return mProcessingChain;
}
public State getState()
{
return mState;
}
public void setState( State state )
{
/* Let all CALL state changes go through in order to update the fade
* timer, otherwise, don't update the state if it hasn't changed */
if( mState != state || state == State.CALL || state == State.DATA || state == State.ENCRYPTED )
{
if( mState.canChangeTo( state ) )
{
mState = state;
switch( state )
{
case CONTROL:
setSquelchState( SquelchState.SQUELCH );
break;
case CALL:
setSquelchState( SquelchState.UNSQUELCH );
mTimerService.stopResetTimer();
mTimerService.startFadeTimer();
break;
case DATA:
mTimerService.stopResetTimer();
mTimerService.startFadeTimer();
break;
case ENCRYPTED:
setSquelchState( SquelchState.SQUELCH );
mTimerService.stopResetTimer();
mTimerService.startFadeTimer();
case NO_TUNER:
mTimerService.stopResetTimer();
if( getProcessingChain().getChannel()
.getChannelType() == ChannelType.TRAFFIC )
{
mTimerService.startFadeTimer();
}
break;
case FADE:
mTimerService.stopFadeTimer();
mTimerService.startResetTimer();
setSquelchState( SquelchState.SQUELCH );
if( mAudioTypeListener != null )
{
mAudioTypeListener.setAudioType( mAudioType );
}
break;
case END:
mTimerService.stopResetTimer();
mTimerService.stopFadeTimer();
setSquelchState( SquelchState.SQUELCH );
if( mAudioTypeListener != null )
{
mAudioTypeListener.setAudioType( mAudioType );
}
/* If this is a traffic channel, stop it */
if( mProcessingChain.getChannel()
.getChannelType() == ChannelType.TRAFFIC )
{
mProcessingChain.getChannel().setEnabled( false );
}
break;
case IDLE:
break;
default:
break;
}
broadcastChange( ChangedAttribute.CHANNEL_STATE );
}
else
{
mLog.error( "Channel State - can't change from [" +
mState.toString() + "] to [" + state.toString() + "]" );
}
}
}
/**
* End method to reset the channel state once a call has completely
* ended and now you want to return the channel state to idle
*/
protected void reset()
{
setState( State.END );
for( AuxChannelState state: mAuxChannelStates )
{
state.reset();
}
//Reset the state to idle
setState( State.IDLE );
}
/**
* Method to perform any call fade actions. On fade, you should change the
* channel state to fading, and construct a call-reset timer to execute a few
* seconds later. This allows the view to signal to the user that the call
* has faded/ended, and display faded information to the user, until the
* cleanup() task is run.
*/
protected void fade( CallEventType type )
{
for( AuxChannelState state: mAuxChannelStates )
{
state.fade();
}
setState( State.FADE );
}
public void setListener( IAudioTypeListener listener )
{
mAudioTypeListener = listener;
}
public void addListener( SquelchListener listener )
{
mSquelchListeners.add( listener );
}
public void removeListener( SquelchListener listener )
{
mSquelchListeners.remove( listener );
}
protected void setSquelchState( SquelchState state )
{
Iterator<SquelchListener> it = mSquelchListeners.iterator();
while( it.hasNext() )
{
it.next().setSquelch( state );
}
}
/**
* Timer service
*/
public class TimerService extends ScheduledThreadPoolExecutor
{
private long mTimerMonitorRevisitRate = 100; //100 milliseconds
private ScheduledFuture<?> mTimerMonitor;
public TimerService()
{
super( 1 );
}
public void dispose()
{
if( mTimerMonitor != null )
{
mTimerMonitor.cancel( true );
}
this.shutdownNow();
}
private void checkMonitor()
{
boolean createTimer = false;
synchronized( mCallFadeLock )
{
if( mFadeTimeout != -1 )
{
createTimer = true;
}
}
synchronized( mCallResetLock )
{
if( mResetTimeout != -1 )
{
createTimer = true;
}
}
if( createTimer )
{
//Check to see if the timer is already running, if not, start it
if( mTimerMonitor == null || mTimerMonitor.isDone() )
{
scheduleAtFixedRate( new StateTimeoutMonitor(),
mTimerMonitorRevisitRate,
mTimerMonitorRevisitRate,
TimeUnit.MILLISECONDS );
}
}
else
{
if( mTimerMonitor != null )
{
mTimerMonitor.cancel( true );
}
}
}
public void startFadeTimer()
{
synchronized( mCallFadeLock )
{
mFadeTimeout = System.currentTimeMillis() + mCallFadeTimeout;
}
checkMonitor();
}
public void resetFadeTimer()
{
startFadeTimer();
}
public void stopFadeTimer()
{
synchronized( mCallFadeLock )
{
mFadeTimeout = -1;
}
checkMonitor();
}
public void startResetTimer()
{
synchronized( mCallResetLock )
{
mResetTimeout = System.currentTimeMillis() + mCallResetTimeout;
}
checkMonitor();
}
public void stopResetTimer()
{
synchronized( mCallResetLock )
{
mResetTimeout = -1;
}
checkMonitor();
}
}
public class StateTimeoutMonitor implements Runnable
{
@Override
public void run()
{
long now = System.currentTimeMillis();
synchronized( mCallFadeLock )
{
if( mFadeTimeout != -1 && now >= mFadeTimeout )
{
mFadeTimeout = -1;
fade( CallEventType.CALL_TIMEOUT );
}
}
synchronized( mCallResetLock )
{
if( mResetTimeout != -1 && now >= mResetTimeout )
{
mResetTimeout = -1;
reset();
}
}
}
}
public enum State
{
IDLE( "IDLE" ) {
@Override
public boolean canChangeTo( State state )
{
return true;
}
},
CALL( "CALL" ) {
@Override
public boolean canChangeTo( State state )
{
return state == CALL ||
state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE;
}
},
DATA( "DATA" ) {
@Override
public boolean canChangeTo( State state )
{
return state == CALL ||
state == CONTROL ||
state == DATA ||
state == State.ENCRYPTED ||
state == FADE;
}
},
ENCRYPTED( "ENCRYPTED" )
{
@Override
public boolean canChangeTo( State state )
{
return state == FADE;
}
},
CONTROL( "CONTROL" )
{
@Override
public boolean canChangeTo( State state )
{
return true;
}
},
FADE( "FADE" ) {
@Override
public boolean canChangeTo( State state )
{
return state != FADE; //All states except fade allowed
}
},
END( "END" ) {
@Override
public boolean canChangeTo( State state )
{
return state != FADE; //Fade is only disallowed state
}
},
NO_TUNER( "NO TUNER" ) {
@Override
public boolean canChangeTo( State state )
{
return state == FADE || state == END;
}
};
private String mDisplayValue;
private State( String displayValue )
{
mDisplayValue = displayValue;
}
public abstract boolean canChangeTo( State state );
public String getDisplayValue()
{
return mDisplayValue;
}
}
/**
* Registers a listener to receive channel state attribute changes
*/
public void addListener( Listener<ChangedAttribute> listener )
{
mChangeBroadcaster.addListener( listener );
}
/**
* Removes a listener from receiving channel state attribute changes
*/
public void removeListener( Listener<ChangedAttribute> listener )
{
mChangeBroadcaster.removeListener( listener );
}
public enum ChangedAttribute
{
AUDIO_TYPE,
CHANNEL_NUMBER,
CHANNEL_NAME,
CHANNEL_SITE_NUMBER,
CHANNEL_SITE_NUMBER_ALIAS,
CHANNEL_STATE,
DESCRIPTION,
FROM_TALKGROUP,
FROM_TALKGROUP_ALIAS,
FROM_TALKGROUP_TYPE,
MESSAGE,
MESSAGE_TYPE,
NAC,
SITE,
SITE_ALIAS,
SITE_NAME,
SOURCE,
SYSTEM,
SYSTEM_NAME,
SQUELCH,
TO_TALKGROUP,
TO_TALKGROUP_ALIAS,
TO_TALKGROUP_TYPE,
WACN;
}
}

View File

@@ -1,388 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.state;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.util.HashMap;
import javax.swing.JMenu;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import sample.Listener;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelState.State;
public abstract class ChannelStatePanel extends JPanel
implements Listener<ChangedAttribute>,
SettingChangeListener
{
private static final long serialVersionUID = 1L;
protected Font mFontDetails = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
protected Font mFontDecoder = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
protected Font mFontAuxDecoder = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
protected Color mColorChannelBackground;
protected Color mColorChannelSelected;
protected Color mColorTopCall;
protected Color mColorMiddleCall;
protected Color mColorTopControl;
protected Color mColorMiddleControl;
protected Color mColorTopData;
protected Color mColorMiddleData;
protected Color mColorTopFade;
protected Color mColorMiddleFade;
protected Color mColorTopIdle;
protected Color mColorMiddleIdle;
protected Color mColorTopNoTuner;
protected Color mColorMiddleNoTuner;
protected Color mColorLabelDetails;
protected Color mColorLabelDecoder;
protected Color mColorLabelAuxDecoder;
private HashMap<Channel,ChannelStatePanel> mTrafficPanels =
new HashMap<Channel,ChannelStatePanel>();
protected Channel mChannel;
public ChannelStatePanel( Channel channel )
{
mChannel = channel;
/* Register to receive settings updates */
getSettingsManager().addListener( this );
/* Register to receive channel updates */
mChannel.getProcessingChain().getChannelState().addListener( this );
getColors();
setLayout( new MigLayout( "insets 3 2 2 2", "[grow,fill]", "[]0[]0[]") );
}
public SettingsManager getSettingsManager()
{
return mChannel.getResourceManager().getSettingsManager();
}
public void dispose()
{
mTrafficPanels.clear();
mChannel = null;
}
private void getColors()
{
mColorChannelBackground = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_BACKGROUND ).getColor();
mColorChannelSelected = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_SELECTED_CHANNEL ).getColor();
mColorTopCall = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CALL ).getColor();
mColorMiddleCall = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CALL ).getColor();
mColorTopControl = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CONTROL ).getColor();
mColorMiddleControl = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL ).getColor();
mColorTopData = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_DATA ).getColor();
mColorMiddleData = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_DATA ).getColor();
mColorTopFade = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_FADE ).getColor();
mColorMiddleFade = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_FADE ).getColor();
mColorTopIdle = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ).getColor();
mColorMiddleIdle = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_IDLE ).getColor();
mColorTopNoTuner = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ).getColor();
mColorMiddleNoTuner = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ).getColor();
mColorLabelDetails = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ).getColor();
mColorLabelDecoder = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ).getColor();
mColorLabelAuxDecoder = getSettingsManager().getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ).getColor();
}
public HashMap<Channel,ChannelStatePanel> getTrafficPanels()
{
return mTrafficPanels;
}
public JMenu getContextMenu()
{
return mChannel.getContextMenu();
}
public Channel getChannel()
{
return mChannel;
}
public ChannelState getChannelState()
{
if( mChannel.getProcessingChain() != null )
{
return mChannel.getProcessingChain().getChannelState();
}
return null;
}
public boolean isSelected()
{
return mChannel.isSelected();
}
public void addPanel( final JPanel panel )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
add( panel, "span,wrap" );
validate();
}
} );
}
public void removePanel( final JPanel panel )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
remove( panel );
validate();
if( getParent() != null )
{
getParent().validate();
}
if( panel instanceof ChannelStatePanel )
{
((ChannelStatePanel)panel).dispose();
}
else if( panel instanceof AuxStatePanel )
{
((AuxStatePanel)panel).dispose();
}
}
} );
}
public void trafficChannelAdded( Channel traffic )
{
//Disregard -- sub class can override and implement
}
public void trafficChannelDeleted( Channel traffic )
{
ChannelStatePanel panelToRemove =
getTrafficPanels().remove( traffic );
removePanel( panelToRemove );
}
public void setSelected( boolean selected )
{
for( ChannelStatePanel traffic: getTrafficPanels().values() )
{
traffic.setSelected( false );
}
mChannel.setSelected( selected );
repaint();
}
private GradientPaint getGradient( Color top, Color middle )
{
return new GradientPaint( 0f, -50.0f, top,
0f, (float)getHeight() / 2.2f, middle, true );
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent( g );
setBackground( mColorChannelBackground );
Graphics2D g2 = (Graphics2D)g.create();
Paint p = null;
ChannelState channelState = getChannelState();
if( channelState != null )
{
State state = channelState.getState();
if( state != null )
{
switch( state )
{
case CALL:
p = getGradient( mColorTopCall, mColorMiddleCall );
break;
case CONTROL:
p = getGradient( mColorTopControl, mColorMiddleControl );
break;
case DATA:
p = getGradient( mColorTopData, mColorMiddleData );
break;
case FADE:
p = getGradient( mColorTopFade, mColorMiddleFade );
break;
case NO_TUNER:
p = getGradient( mColorTopNoTuner, mColorMiddleNoTuner );
break;
case END:
case IDLE:
default:
p = getGradient( mColorTopIdle, mColorMiddleIdle );
break;
}
}
}
g2.setPaint( p );
g2.fillRect( 0, 0, getWidth(), getHeight() );
/* Draw bottom separator line */
g2.setColor( Color.LIGHT_GRAY );
g2.drawLine( 0, getHeight() - 1, getWidth(), getHeight() - 1 );
/* Draw channel selected box */
if( mChannel.isSelected() )
{
g2.setColor( mColorChannelSelected );
g2.drawRect( 1, 1, getWidth() - 2, getHeight() - 2 );
}
g2.dispose();
}
@Override
public void settingChanged( Setting setting )
{
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_BACKGROUND:
mColorChannelBackground = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_CALL:
mColorMiddleCall = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_CALL:
mColorTopCall = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL:
mColorMiddleControl = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_CONTROL:
mColorTopControl = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_DATA:
mColorMiddleData = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_DATA:
mColorTopData = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_FADE:
mColorMiddleFade = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_FADE:
mColorTopFade = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_IDLE:
mColorMiddleIdle = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_IDLE:
mColorTopIdle = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER:
mColorMiddleNoTuner = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_NO_TUNER:
mColorTopNoTuner = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_AUX_DECODER:
mColorLabelAuxDecoder = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_DECODER:
mColorLabelDecoder = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_DETAILS:
mColorLabelDetails = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_SELECTED_CHANNEL:
mColorChannelSelected = colorSetting.getColor();
repaint();
break;
}
}
}
@Override
public void settingDeleted( Setting setting ) {}
}

View File

@@ -1,63 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode;
import controller.channel.ChannelNode;
import decode.config.DecodeConfiguration;
import decode.ltrnet.LTRNetEditor;
import decode.ltrstandard.LTRStandardEditor;
import decode.mpt1327.MPT1327Editor;
import decode.nbfm.NBFMEditor;
import decode.p25.P25Editor;
import decode.passport.PassportEditor;
public class DecodeEditorFactory
{
public static DecodeEditor getPanel( DecodeConfiguration config,
ChannelNode channelNode )
{
DecodeEditor configuredPanel;
switch( config.getDecoderType() )
{
case NBFM:
configuredPanel = new NBFMEditor( config );
break;
case LTR_STANDARD:
configuredPanel = new LTRStandardEditor( config );
break;
case LTR_NET:
configuredPanel = new LTRNetEditor( config );
break;
case MPT1327:
configuredPanel = new MPT1327Editor( config, channelNode );
break;
case PASSPORT:
configuredPanel = new PassportEditor( config );
break;
case P25_PHASE1:
configuredPanel = new P25Editor( config );
break;
default:
configuredPanel = new DecodeEditor( config );
break;
}
return configuredPanel;
}
}

View File

@@ -1,276 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode;
import java.util.ArrayList;
import java.util.List;
import message.Message;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.real.RealSampleBroadcaster;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import source.tuner.frequency.AutomaticFrequencyControl;
import source.tuner.frequency.FrequencyCorrectionControl;
import audio.IAudioOutput;
import decode.config.DecodeConfiguration;
import eventlog.MessageEventLogger;
public abstract class Decoder implements Listener<Message>
{
private MessageEventLogger mEventLogger;
private Broadcaster<Message> mMessageBroadcaster = new Broadcaster<Message>();
private RealSampleBroadcaster mRealBroadcaster = new RealSampleBroadcaster();
private Broadcaster<ComplexSample> mComplexBroadcaster =
new Broadcaster<ComplexSample>();
protected SampleType mSourceSampleType;
protected ArrayList<Decoder> mAuxiliaryDecoders = new ArrayList<Decoder>();
protected FrequencyCorrectionControl mFrequencyCorrection;
/**
* Abstract decoder class.
*
* @param sampleType - complex (I/Q) or real (demodulated) sample type.
* Each decoder instance will be configured to receive either complex or
* real samples. When configured to receive complex samples, the
* decoder will implement a demodulator component and feed the output from
* that demodulator back onto itself via the real sample interface, so
* that any added auxiliary decoders will then receive the demodulated
* output stream.
*/
public Decoder( SampleType sampleType )
{
mSourceSampleType = sampleType;
}
public Decoder( DecodeConfiguration config, SampleType sampleType )
{
this( sampleType );
if( config.supportsAFC() && config.isAFCEnabled() )
{
mFrequencyCorrection =
new AutomaticFrequencyControl( config.getAFCMaximumCorrection() );
/* Register AFC to receive non-filtered demodulated audio samples */
addUnfilteredRealSampleListener(
(RealSampleListener)mFrequencyCorrection );
}
}
/**
* Optional frequency correction control to provide frequency adjustments
* directed by the decoder.
*/
public FrequencyCorrectionControl getFrequencyCorrectionControl()
{
return mFrequencyCorrection;
}
/**
* Indicates if a frequency correction controller exists for this decoder
*/
public boolean hasFrequencyCorrectionControl()
{
return mFrequencyCorrection != null;
}
/**
* Current frequency correction setting or zero if no controller exists
*/
public int getFrequencyCorrection()
{
if( hasFrequencyCorrectionControl() )
{
return mFrequencyCorrection.getErrorCorrection();
}
return 0;
}
/**
* Returns a real (ie demodulated) sample listener interface for
* connecting this decoder to a real sample stream provider
*/
public RealSampleListener getRealReceiver()
{
return mRealBroadcaster;
}
public abstract IAudioOutput getAudioOutput();
/**
* Returns a complex listener interface for connecting this decoder to a
* float stream provider
*/
public Listener<ComplexSample> getComplexReceiver()
{
return (Listener<ComplexSample>)mComplexBroadcaster;
}
/**
* Returns the primary decoder type for this decoder
*/
public abstract DecoderType getType();
/**
* Cleanup method. Invoke this method after stop and before delete.
*/
public void dispose()
{
for( Decoder auxiliaryDecoder: mAuxiliaryDecoders )
{
auxiliaryDecoder.dispose();
}
mAuxiliaryDecoders.clear();
mMessageBroadcaster.clear();
mComplexBroadcaster.clear();
mRealBroadcaster.clear();
if( mFrequencyCorrection != null )
{
mFrequencyCorrection.dispose();
}
}
/**
* Adds the auxiliary decoder (piggyback) to this decoder.
*
* Note: we assume that the auxiliary decoder is designed to receive
* demodulated samples, thus we automatically register the aux decoder to
* receive the demodulated sample stream.
*
* Registers this decoder to receive the message output stream from the
* auxiliary decoder, so that those messages can be echoed and included in
* the consolidated message stream to all message listeners registered on
* the primary decoder.
*/
public void addAuxiliaryDecoder( Decoder decoder )
{
mAuxiliaryDecoders.add( decoder );
mRealBroadcaster.addListener( decoder.getRealReceiver() );
decoder.addMessageListener( this );
}
/**
* Returns a list of all auxiliary decoders that have been added to this
* processing chain
*/
public List<Decoder> getAuxiliaryDecoders()
{
return mAuxiliaryDecoders;
}
/**
* Sets the MessageEventLogger for this processing chain
*/
public void setEventLogger( MessageEventLogger eventLogger )
{
mEventLogger = eventLogger;
}
/**
* Returns the MessageEventLogger for this processing chain
*/
public MessageEventLogger getEventLogger()
{
return mEventLogger;
}
/**
* Main receiver method for all demodulators to send their decoded messages
* so that they will be broadcast to all registered listeners
*/
@Override
public void receive( Message message )
{
send( message );
}
/**
* Broadcasts the message to all registered listeners
*/
public void send( Message message )
{
if( mMessageBroadcaster != null )
{
mMessageBroadcaster.receive( message );
}
}
/**
* Adds a listener to receiving decoded messages from all attached decoders
*/
public void addMessageListener( Listener<Message> listener )
{
mMessageBroadcaster.addListener( listener );
}
/**
* Removes the listener from receiving decoded messages from all attached
* decoders
*/
public void removeMessageListener( Listener<Message> listener )
{
mMessageBroadcaster.removeListener( listener );
}
/**
* Adds a real sample listener to receive unfiltered demodulated samples
*/
public abstract void addUnfilteredRealSampleListener( RealSampleListener listener );
/**
* Adds a real sample listener to receive demodulated samples
*/
public void addRealSampleListener( RealSampleListener listener )
{
mRealBroadcaster.addListener( listener );
}
/**
* Remove the real sample listener from receiving demodulated samples
*/
public void removeRealListener( RealSampleListener listener )
{
mRealBroadcaster.removeListener( listener );
}
/**
* Adds a complex (I/Q) sample listener to receive copy of the inbound
* complex sample stream
*/
public void addComplexListener( Listener<ComplexSample> listener )
{
mComplexBroadcaster.addListener( listener );
}
/**
* Removes the complex (I/Q) sample listener from receiving samples
*/
public void removeComplexListener( Listener<ComplexSample> listener )
{
mComplexBroadcaster.removeListener( listener );
}
}

View File

@@ -1,219 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode;
import source.Source.SampleType;
import alias.AliasList;
import controller.channel.ChannelMap;
import controller.channel.ProcessingChain;
import controller.state.ChannelState;
import decode.am.AMChannelState;
import decode.am.AMDecoder;
import decode.config.AuxDecodeConfiguration;
import decode.config.DecodeConfigLTRNet;
import decode.config.DecodeConfigLTRStandard;
import decode.config.DecodeConfigMPT1327;
import decode.config.DecodeConfigP25Phase1;
import decode.config.DecodeConfiguration;
import decode.fleetsync2.Fleetsync2Decoder;
import decode.fleetsync2.FleetsyncChannelState;
import decode.lj1200.LJ1200ChannelState;
import decode.lj1200.LJ1200Decoder;
import decode.ltrnet.LTRNetChannelState;
import decode.ltrnet.LTRNetDecoder;
import decode.ltrstandard.LTRChannelState;
import decode.ltrstandard.LTRStandardDecoder;
import decode.mdc1200.MDCChannelState;
import decode.mdc1200.MDCDecoder;
import decode.mpt1327.MPT1327ChannelState;
import decode.mpt1327.MPT1327Decoder;
import decode.nbfm.NBFMChannelState;
import decode.nbfm.NBFMDecoder;
import decode.p25.P25ChannelState;
import decode.p25.P25Decoder;
import decode.passport.PassportChannelState;
import decode.passport.PassportDecoder;
import decode.tait.Tait1200ChannelState;
import decode.tait.Tait1200Decoder;
public class DecoderFactory
{
/**
* Returns a fully configured decoder with embedded auxiliary decoders
* @param channelConfig
* @return
*/
public static Decoder getDecoder( ProcessingChain chain,
SampleType sampleType,
AliasList aliasList )
{
Decoder retVal = null;
DecodeConfiguration config =
chain.getChannel().getDecodeConfiguration();
switch( config.getDecoderType() )
{
case AM:
retVal = new AMDecoder( sampleType );
break;
case NBFM:
retVal = new NBFMDecoder( config, sampleType );
break;
case LTR_STANDARD:
DecodeConfigLTRStandard ltrStandardConfig =
(DecodeConfigLTRStandard)config;
retVal = new LTRStandardDecoder( config, sampleType, aliasList,
ltrStandardConfig.getMessageDirection() );
break;
case LTR_NET:
DecodeConfigLTRNet ltrNetConfig = (DecodeConfigLTRNet)config;
retVal = new LTRNetDecoder( config, sampleType, aliasList,
ltrNetConfig.getMessageDirection() );
break;
case MPT1327:
DecodeConfigMPT1327 mptConfig = (DecodeConfigMPT1327)config;
retVal = new MPT1327Decoder( config, sampleType, aliasList,
mptConfig.getSync() );
break;
case PASSPORT:
retVal = new PassportDecoder( config, sampleType, aliasList );
break;
case P25_PHASE1:
DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1)config;
retVal = new P25Decoder( chain.getResourceManager(),
sampleType, p25Config.getModulation(), aliasList );
break;
default:
throw new IllegalArgumentException(
"Unknown decoder type [" +
config.getDecoderType().toString() + "]" );
}
if( retVal != null )
{
AuxDecodeConfiguration auxConfig =
chain.getChannel().getAuxDecodeConfiguration();
for( DecoderType auxDecoder: auxConfig.getAuxDecoders() )
{
switch( auxDecoder )
{
case FLEETSYNC2:
retVal.addAuxiliaryDecoder(
new Fleetsync2Decoder( aliasList ) );
break;
case MDC1200:
retVal.addAuxiliaryDecoder(
new MDCDecoder( aliasList ) );
break;
case LJ_1200:
retVal.addAuxiliaryDecoder(
new LJ1200Decoder( aliasList ) );
break;
case TAIT_1200:
retVal.addAuxiliaryDecoder(
new Tait1200Decoder( aliasList ) );
break;
}
}
}
return retVal;
}
public static ChannelState getChannelStateNew( ProcessingChain chain,
AliasList aliasList )
{
ChannelState retVal = null;
DecodeConfiguration decodeConfig =
chain.getChannel().getDecodeConfiguration();
switch( decodeConfig.getDecoderType() )
{
case AM:
retVal = new AMChannelState( chain, aliasList );
break;
case NBFM:
retVal = new NBFMChannelState( chain, aliasList );
break;
case LTR_STANDARD:
retVal = new LTRChannelState( chain, aliasList );
break;
case LTR_NET:
retVal = new LTRNetChannelState( chain, aliasList );
break;
case MPT1327:
DecodeConfigMPT1327 mpt = (DecodeConfigMPT1327)decodeConfig;
String channelMapName = mpt.getChannelMapName();
ChannelMap map = chain.getResourceManager().getPlaylistManager()
.getPlayist().getChannelMapList()
.getChannelMap( channelMapName );
retVal = new MPT1327ChannelState( chain, aliasList, map );
break;
case PASSPORT:
retVal = new PassportChannelState( chain, aliasList );
break;
case P25_PHASE1:
retVal = new P25ChannelState( chain, aliasList );
break;
default:
break;
}
if( retVal != null )
{
AuxDecodeConfiguration auxConfig =
chain.getChannel().getAuxDecodeConfiguration();
for( DecoderType auxDecoder: auxConfig.getAuxDecoders() )
{
switch( auxDecoder )
{
case FLEETSYNC2:
retVal.addAuxChannelState(
new FleetsyncChannelState( retVal ) );
break;
case LJ_1200:
retVal.addAuxChannelState(
new LJ1200ChannelState( retVal ) );
break;
case MDC1200:
retVal.addAuxChannelState(
new MDCChannelState( retVal ) );
break;
case TAIT_1200:
retVal.addAuxChannelState(
new Tait1200ChannelState( retVal ) );
}
}
}
return retVal;
}
}

View File

@@ -1,69 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.am;
import alias.AliasList;
import audio.SquelchListener;
import audio.SquelchListener.SquelchState;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
/**
* Conventional channel state - this class doesn't do anything at the moment,
* it is a place holder for future expansion for the conventional decoder
*/
public class AMChannelState extends ChannelState
{
public AMChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
}
/**
* Make the ConventionalChannelState always unsquelched
*/
public void addListener( SquelchListener listener )
{
super.addListener( listener );
super.setSquelchState( SquelchState.UNSQUELCH );
}
@Override
public void setSquelchState( SquelchState state )
{
//do nothing ... we want the squelch state always unsquelched (for now)
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "\tDecoder:\tAM\n\n" );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
}

View File

@@ -1,105 +0,0 @@
package decode.am;
import audio.AudioOutputImpl;
import audio.IAudioOutput;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import decode.Decoder;
import decode.DecoderType;
import dsp.am.AMDemodulator;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.DCRemovalFilter2;
import dsp.filter.FilterFactory;
import dsp.filter.FloatFIRFilter;
import dsp.filter.Window.WindowType;
import dsp.gain.ComplexAutomaticGainControl;
import dsp.gain.RealAutomaticGainControl;
public class AMDecoder extends Decoder
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double DC_REMOVAL_RATIO = 0.003;
private ComplexFIRFilter mIQFilter;
private ComplexAutomaticGainControl mBasebandAGC =
new ComplexAutomaticGainControl();
private AMDemodulator mDemodulator = new AMDemodulator();
private DCRemovalFilter2 mDCRemovalFilter =
new DCRemovalFilter2( DC_REMOVAL_RATIO );
private RealAutomaticGainControl mAudioAGC = new RealAutomaticGainControl();
private FloatFIRFilter mLowPassFilter;
private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "AM Decoder Audio Output" );
public AMDecoder( SampleType sampleType )
{
super( sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
mIQFilter = new ComplexFIRFilter(
FilterFactory.getLowPass( 48000, 6500, 73, WindowType.HAMMING ),
1.0 );
/**
* The Decoder super class is both a Complex listener and a float
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
this.addComplexListener( mIQFilter );
mIQFilter.setListener( mBasebandAGC );
mBasebandAGC.setListener( mDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDemodulator.setListener( mDCRemovalFilter );
/**
* Route the demodulated, filtered samples back to this class to send
* to all registered listeners
*/
mLowPassFilter = new FloatFIRFilter(
FilterFactory.getLowPass( 48000, 3000, 31, WindowType.COSINE ), 1.0 );
mDCRemovalFilter.setListener( mLowPassFilter );
mLowPassFilter.setListener( mAudioAGC );
mAudioAGC.setListener( this.getRealReceiver() );
addRealSampleListener( mAudioOutput );
}
}
@Override
public IAudioOutput getAudioOutput()
{
return mAudioOutput;
}
@Override
public DecoderType getType()
{
return DecoderType.AM;
}
@Override
public void addUnfilteredRealSampleListener( RealSampleListener listener )
{
throw new IllegalArgumentException( "cannot add real sample "
+ "listener to AM demodulator - not implemented" );
}
}

View File

@@ -1,136 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.am;
import java.awt.EventQueue;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class AMPanel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mStateLabel;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private JLabel mProtocol = new JLabel( "AM" );
public AMPanel( Channel channel )
{
super( channel );
init();
}
public void init()
{
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
add( mStateLabel );
add( mSourceLabel );
add( mChannelLabel, "wrap" );
add( mProtocol, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
break;
case SOURCE:
mSourceLabel.setText( mChannel.getSourceConfiguration()
.getDescription() );
break;
case CHANNEL_NAME:
case SITE_NAME:
case SYSTEM_NAME:
mChannelLabel.setText( mChannel.getChannelDisplayName() );
break;
}
repaint();
}
});
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mStateLabel != null )
{
mStateLabel.setForeground( mColorLabelDecoder );
}
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mSourceLabel != null )
{
mSourceLabel.setForeground( mColorLabelDetails );
}
if( mChannelLabel != null )
{
mChannelLabel.setForeground( mColorLabelDetails );
}
break;
}
}
}
}

View File

@@ -1,171 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.fleetsync2;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class FleetsyncActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private AliasList mAliasList;
private TreeSet<String> mIdents = new TreeSet<String>();
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public FleetsyncActivitySummary( AliasList list )
{
mAliasList = list;
}
public void dispose()
{
mIdents.clear();
mEmergencyIdents.clear();
}
@Override
public void receive( Message message )
{
if( message instanceof FleetsyncMessage )
{
FleetsyncMessage fs = ((FleetsyncMessage)message);
if( fs.isValid() )
{
String from = fs.getFromID();
mIdents.add( from );
String to = fs.getToID();
if( !to.startsWith( "xxx" ))
{
mIdents.add( to );
}
}
FleetsyncMessageType type = fs.getMessageType();
switch( type )
{
case EMERGENCY:
case LONE_WORKER_EMERGENCY:
mEmergencyIdents.add( fs.getFromID() );
break;
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "=============================\n" );
sb.append( "Decoder:\tFleetsync II\n\n" );
sb.append( "Fleetsync Idents\n" );
if( mIdents.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mIdents.iterator();
while( it.hasNext() )
{
String ident = it.next();
sb.append( " " );
sb.append( formatIdent( ident ) );
sb.append( " " );
Alias alias = mAliasList.getFleetsyncAlias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
sb.append( "\nFleetsync Emergency Activations\n" );
if( mEmergencyIdents.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mEmergencyIdents.iterator();
while( it.hasNext() )
{
String ident = it.next();
sb.append( " " );
sb.append( formatIdent( ident ) );
sb.append( " " );
Alias alias = mAliasList.getFleetsyncAlias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
return sb.toString();
}
public static String formatIdent( String ident )
{
StringBuilder sb = new StringBuilder();
if( ident.length() == 7 )
{
sb.append( ident.substring( 0, 3 ) );
sb.append( "-" );
sb.append( ident.substring( 3, 7 ) );
return sb.toString();
}
else
{
return ident;
}
}
}

View File

@@ -1,193 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.fleetsync2;
import message.Message;
import alias.Alias;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelState.State;
public class FleetsyncChannelState extends AuxChannelState
{
private String mFleetFrom;
private Alias mFleetFromAlias;
private String mFleetTo;
private Alias mFleetToAlias;
private String mMessage;
private String mMessageType;
private FleetsyncActivitySummary mActivitySummary;
public FleetsyncChannelState( ChannelState parentState )
{
super( parentState );
mActivitySummary =
new FleetsyncActivitySummary( parentState.getAliasList() );
}
@Override
public void reset()
{
setFleetIDFrom( null );
setFleetIDFromAlias( null );
setFleetIDTo( null );
setFleetIDToAlias( null );
setMessage( null );
setMessageType( null );
}
@Override
public void fade()
{
}
@Override
public void receive( Message message )
{
if( message instanceof FleetsyncMessage )
{
mActivitySummary.receive( message );
FleetsyncMessage fleetsync = (FleetsyncMessage)message;
setFleetIDFrom( fleetsync.getFromID() );
setFleetIDFromAlias( fleetsync.getFromIDAlias() );
FleetsyncMessageType type = fleetsync.getMessageType();
if( type != FleetsyncMessageType.ANI )
{
setFleetIDTo( fleetsync.getToID() );
setFleetIDToAlias( fleetsync.getToIDAlias() );
}
setMessageType( type.getLabel() );
switch( type )
{
case GPS:
setMessage( fleetsync.getGPSLocation() );
break;
case STATUS:
StringBuilder sb = new StringBuilder();
sb.append( fleetsync.getStatus() );
Alias status = fleetsync.getStatusAlias();
if( status != null )
{
sb.append( "/" );
sb.append( status.getName() );
}
setMessage( sb.toString() );
break;
default:
break;
}
/* Set the state to CALL on the parent channel state, so that the
* parent state timer can track turning off any updates we apply */
mParentChannelState.setState( State.CALL );
FleetsyncCallEvent fsCallEvent =
FleetsyncCallEvent.getFleetsync2Event( fleetsync );
fsCallEvent.setAliasList( mParentChannelState.getAliasList() );
mParentChannelState.receiveCallEvent( fsCallEvent );
}
}
public String getFleetIDFrom()
{
return mFleetFrom;
}
public void setFleetIDFrom( String from )
{
mFleetFrom = from;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getFleetIDFromAlias()
{
return mFleetFromAlias;
}
public void setFleetIDFromAlias( Alias alias )
{
mFleetFromAlias = alias;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
public String getFleetIDTo()
{
return mFleetTo;
}
public void setFleetIDTo( String to )
{
mFleetTo = to;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
}
public Alias getFleetIDToAlias()
{
return mFleetToAlias;
}
public void setFleetIDToAlias( Alias alias )
{
mFleetToAlias = alias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getMessage()
{
return mMessage;
}
public void setMessage( String message )
{
mMessage = message;
broadcastChange( ChangedAttribute.MESSAGE );
}
public String getMessageType()
{
return mMessageType;
}
public void setMessageType( String type )
{
mMessageType = type;
broadcastChange( ChangedAttribute.MESSAGE_TYPE );
}
@Override
public String getActivitySummary()
{
return mActivitySummary.getSummary();
}
}

View File

@@ -1,67 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.lj1200;
import message.Message;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
public class LJ1200ChannelState extends AuxChannelState
{
private LJ1200ActivitySummary mActivitySummary;
public LJ1200ChannelState( ChannelState parentState )
{
super( parentState );
mActivitySummary =
new LJ1200ActivitySummary( parentState.getAliasList() );
}
@Override
public void reset()
{
}
@Override
public void fade()
{
}
@Override
public void receive( Message message )
{
if( message instanceof LJ1200Message )
{
mActivitySummary.receive( message );
mParentChannelState.receiveCallEvent(
LJ1200CallEvent.getLJ1200Event( (LJ1200Message)message ) );
}
else if( message instanceof LJ1200TransponderMessage )
{
mActivitySummary.receive( message );
}
}
@Override
public String getActivitySummary()
{
return mActivitySummary.getSummary();
}
}

View File

@@ -1,36 +0,0 @@
package decode.lj1200;
import java.util.Collections;
import java.util.List;
import message.Message;
import filter.Filter;
import filter.FilterElement;
public class LJ1200MessageFilter extends Filter<Message>
{
public LJ1200MessageFilter()
{
super( "LJ-1200 Message Filter" );
}
@Override
public boolean passes( Message message )
{
return mEnabled && canProcess( message );
}
@Override
public boolean canProcess( Message message )
{
return message instanceof LJ1200Message ||
message instanceof LJ1200TransponderMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -1,404 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.ltrnet;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class LTRNetActivitySummary implements ActivitySummaryProvider
{
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
private AliasList mAliasList;
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<String> mESNs = new TreeSet<String>();
private TreeSet<Integer> mUniqueIDs = new TreeSet<Integer>();
private TreeSet<String> mNeighborIDs = new TreeSet<String>();
private TreeSet<String> mSiteIDs = new TreeSet<String>();
private HashMap<Integer,Double> mReceiveFrequencies =
new HashMap<Integer,Double>();
private HashMap<Integer,Double> mTransmitFrequencies =
new HashMap<Integer,Double>();
private int mMonitoredLCN;
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public LTRNetActivitySummary( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( Message message )
{
if( message instanceof LTRNetOSWMessage )
{
LTRNetOSWMessage ltr = ((LTRNetOSWMessage)message);
if( ltr.isValid() )
{
switch( ltr.getMessageType() )
{
case CA_STRT:
case CA_ENDD:
String talkgroup = ltr.getTalkgroupID();
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
break;
case SY_IDLE:
mMonitoredLCN = ltr.getChannel();
break;
case MA_CHNH:
break;
case MA_CHNL:
break;
case FQ_RXHI:
mReceiveFrequencies.put( ltr.getHomeRepeater(),
ltr.getFrequency() );
break;
case FQ_RXLO:
break;
case FQ_TXHI:
mTransmitFrequencies.put( ltr.getHomeRepeater(),
ltr.getFrequency() );
break;
case FQ_TXLO:
break;
case ID_ESNH:
break;
case ID_ESNL:
break;
case ID_NBOR:
String neighborID = ltr.getNeighborID();
if( neighborID != null )
{
mNeighborIDs.add( neighborID );
}
break;
case ID_UNIQ:
int uniqueid = ltr.getRadioUniqueID();
if( uniqueid != sINT_NULL_VALUE )
{
mUniqueIDs.add( uniqueid );
}
break;
case ID_SITE:
String siteID = ltr.getSiteID();
if( siteID != null )
{
mSiteIDs.add( siteID );
}
break;
case UN_KNWN:
break;
default:
break;
}
}
}
else if( message instanceof LTRNetISWMessage )
{
LTRNetISWMessage ltr = ((LTRNetISWMessage)message);
if( ltr.isValid() )
{
switch( ltr.getMessageType() )
{
case CA_STRT:
case CA_ENDD:
String talkgroup = ltr.getTalkgroupID();
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
break;
case ID_ESNH:
case ID_ESNL:
String esn = ltr.getESN();
if( !esn.contains( "xxxx" ) )
{
mESNs.add( ltr.getESN() );
}
break;
case ID_UNIQ:
int uniqueid = ltr.getRadioUniqueID();
if( uniqueid != sINT_NULL_VALUE )
{
mUniqueIDs.add( uniqueid );
}
break;
default:
break;
}
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "Decoder:\tLTR-Net\n\n" );
if( mSiteIDs.isEmpty() )
{
sb.append( "Site:\tUnknown\n" );
}
else
{
Iterator<String> it = mSiteIDs.iterator();
while( it.hasNext() )
{
sb.append( "Site:\t" );
String siteID = it.next();
sb.append( siteID );
Alias siteAlias = mAliasList.getSiteID( String.valueOf( siteID ) );
if( siteAlias != null )
{
sb.append( " " );
sb.append( siteAlias.getName() );
}
sb.append( "\n" );
}
}
sb.append( "\nLCNs (transmit | receive)\n" );
if( mReceiveFrequencies.isEmpty() && mTransmitFrequencies.isEmpty() )
{
sb.append( " None\n" );
}
else
{
for( int x = 1; x < 21; x++ )
{
double rcv = 0.0d;
if( mReceiveFrequencies.containsKey( x ) )
{
rcv = mReceiveFrequencies.get( x );
}
double xmt = 0.0d;
if( mTransmitFrequencies.containsKey( x ) )
{
xmt = mTransmitFrequencies.get( x );
}
if( rcv > 0.0d || xmt > 0.0d )
{
if( x < 10 )
{
sb.append( " " );
}
sb.append( x );
sb.append( ": " );
if( xmt == -1.0d )
{
sb.append( "---.-----" );
}
else
{
sb.append( mDecimalFormatter.format( xmt ) );
}
sb.append( " | " );
if( rcv == -1.0d )
{
sb.append( "---.-----" );
}
else
{
sb.append( mDecimalFormatter.format( rcv ) );
}
if( x == mMonitoredLCN )
{
sb.append( " **" );
}
sb.append( "\n" );
}
}
}
sb.append( "\nTalkgroups\n" );
if( mTalkgroups.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mTalkgroups.iterator();
while( it.hasNext() )
{
String tgid = it.next();
sb.append( " " );
sb.append( tgid );
sb.append( " " );
Alias alias = mAliasList.getTalkgroupAlias( tgid );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
sb.append( "\nRadio Unique IDs\n" );
if( mUniqueIDs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<Integer> it = mUniqueIDs.iterator();
while( it.hasNext() )
{
int uid = it.next();
sb.append( " " );
sb.append( uid );
sb.append( " " );
Alias alias = mAliasList.getUniqueID( uid );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
sb.append( "\nESNs\n" );
if( mESNs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mESNs.iterator();
while( it.hasNext() )
{
String esn = it.next();
sb.append( " " );
sb.append( esn );
sb.append( " " );
Alias alias = mAliasList.getESNAlias( esn );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
sb.append( "\nNeighbor Sites\n" );
if( mNeighborIDs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mNeighborIDs.iterator();
while( it.hasNext() )
{
String neighbor = it.next();
sb.append( " " );
sb.append( neighbor );
sb.append( " " );
Alias alias = mAliasList.getSiteID( String.valueOf( neighbor ) );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
return sb.toString();
}
}

View File

@@ -1,433 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.ltrnet;
import java.awt.EventQueue;
import java.util.HashMap;
import message.Message;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent;
import controller.activity.CallEvent.CallEventType;
import controller.activity.CallEventModel;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import controller.state.ChannelState.State;
public class LTRNetChannelState extends ChannelState
{
private String mTalkgroup;
private Alias mTalkgroupAlias;
private String mDescription;
private int mChannelNumber;
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
private LTRNetActivitySummary mActivitySummary;
public LTRNetChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
mActivitySummary = new LTRNetActivitySummary( aliasList );
}
/**
* Intercept the fade event so that we can generate a call end event
*/
@Override
public void fade( final CallEventType type )
{
/*
* We can receive multiple call tear-down messages -- only respond to
* the message that can change the state to fade
*/
if( getState().canChangeTo( State.FADE ) )
{
LTRCallEvent current = getCurrentLTRCallEvent();
if( current != null )
{
/* Close out the call event. If we only received 1 call
* message, then flag it as noise and remove the call event */
if( current.isValid() )
{
mCallEventModel.setEnd( current );
}
else
{
mCallEventModel.remove( current );
}
}
}
setCurrentCallEvent( null );
mActiveCalls.clear();
super.fade( type );
}
public LTRCallEvent getCurrentLTRCallEvent()
{
CallEvent current = getCurrentCallEvent();
if( current != null )
{
return (LTRCallEvent)current;
}
return null;
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( mActivitySummary.getSummary() );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
@Override
public void receive( Message message )
{
super.receive( message );
if( message instanceof LTRNetOSWMessage )
{
mActivitySummary.receive( message );
LTRNetOSWMessage ltr = (LTRNetOSWMessage)message;
Alias talkgroupAlias = ltr.getTalkgroupIDAlias();
switch( ltr.getMessageType() )
{
case CA_STRT:
if( mChannelNumber == 0 )
{
mChannelNumber = ltr.getChannel();
}
if( ltr.getChannel() == mChannelNumber )
{
processCallMessage( ltr );
if( ltr.getGroup() == 253 )
{
mDescription = "REGISTER";
broadcastChange( ChangedAttribute.DESCRIPTION );
}
else
{
mTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
if( talkgroupAlias != null )
{
mTalkgroupAlias = talkgroupAlias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
}
}
else
{
//Call Detect
if( !mActiveCalls.containsKey( ltr.getChannel() ) ||
!mActiveCalls.get( ltr.getChannel() )
.contentEquals( ltr.getTalkgroupID() ) )
{
mActiveCalls.put( ltr.getChannel(),
ltr.getTalkgroupID() );
mCallEventModel.add(
new LTRCallEvent.Builder( CallEventType.CALL_DETECT )
.aliasList( mAliasList )
.channel( String.valueOf( ltr.getChannel() ) )
.to( ltr.getTalkgroupID() )
.build() );
}
}
break;
case CA_ENDD:
if( ltr.getGroup() != 253 )
{
mTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
if( talkgroupAlias != null )
{
mTalkgroupAlias = talkgroupAlias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
}
fade( CallEventType.CALL_END );
break;
case ID_UNIQ:
mDescription = "REGISTER UID";
broadcastChange( ChangedAttribute.DESCRIPTION );
mTalkgroup = String.valueOf( ltr.getRadioUniqueID() );
broadcastChange( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
final LTRCallEvent currentEvent = getCurrentLTRCallEvent();
if( currentEvent != null &&
currentEvent.getCallEventType() == CallEventType.REGISTER )
{
final String uid = String.valueOf( ltr.getRadioUniqueID() );
if( uid != null )
{
mCallEventModel.setFromID( currentEvent, uid );
}
}
else
{
mCallEventModel.add(
new LTRCallEvent.Builder( CallEventType.REGISTER )
.aliasList( mAliasList )
.details( "Radio Unique ID" )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.from( String.valueOf( ltr.getRadioUniqueID() ) )
.build() );
}
setState( State.CALL );
break;
case SY_IDLE:
mChannelNumber = ltr.getChannel();
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
break;
default:
break;
}
}
else if( message instanceof LTRNetISWMessage )
{
mActivitySummary.receive( message );
LTRNetISWMessage ltr = (LTRNetISWMessage)message;
Alias talkgroupAlias = ltr.getTalkgroupIDAlias();
switch( ltr.getMessageType() )
{
case CA_STRT:
processCallMessage( ltr );
if( ltr.getGroup() == 253 )
{
mDescription = "REGISTER";
broadcastChange( ChangedAttribute.DESCRIPTION );
}
else
{
mTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
if( talkgroupAlias != null )
{
mTalkgroupAlias = talkgroupAlias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
}
break;
case CA_ENDD:
mTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
if( talkgroupAlias != null )
{
mTalkgroupAlias = talkgroupAlias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
fade( CallEventType.CALL_END );
break;
case ID_UNIQ:
mDescription = "REGISTER UID";
broadcastChange( ChangedAttribute.DESCRIPTION );
mTalkgroup = String.valueOf( ltr.getRadioUniqueID() );
broadcastChange( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
if( getState() != State.DATA )
{
LTRCallEvent event =
new LTRCallEvent.Builder( CallEventType.REGISTER )
.aliasList( mAliasList )
.channel( String.valueOf( ltr.getChannel() ) )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.to( ltr.getTalkgroupID() )
.build();
mCallEventModel.add( event );
setCurrentCallEvent( event );
setState( State.DATA );
}
if( mCurrentCallEvent != null )
{
mCurrentCallEvent.setFromID(
String.valueOf( ltr.getRadioUniqueID() ) );
mCurrentCallEvent.setDetails( "Unique ID" );
}
break;
case ID_ESNH:
case ID_ESNL:
mDescription = "ESN";
broadcastChange( ChangedAttribute.DESCRIPTION );
mTalkgroup = ltr.getESN();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = ltr.getESNAlias();
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
setState( State.DATA );
mCallEventModel.add(
new LTRCallEvent.Builder( CallEventType.REGISTER_ESN )
.aliasList( mAliasList )
.details( "ESN:" + ltr.getESN() )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.from( ltr.getESN() )
.build() );
break;
default:
break;
}
}
}
private void processCallMessage( LTRNetMessage message )
{
LTRCallEvent current = getCurrentLTRCallEvent();
if( current == null || !current.addMessage( message ) )
{
if( current != null )
{
mCallEventModel.remove( current );
}
LTRCallEvent event;
if( message.getGroup() == 253 )
{
event = new LTRCallEvent.Builder( CallEventType.REGISTER )
.aliasList( mAliasList )
.channel( String.valueOf( message.getChannel() ) )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.to( message.getTalkgroupID() )
.build();
setState( State.DATA );
}
else
{
event = new LTRCallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( String.valueOf( message.getChannel() ) )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.to( message.getTalkgroupID() )
.build();
setState( State.CALL );
}
mCallEventModel.add( event );
setCurrentCallEvent( event );
}
else
{
if( getState() == State.CALL )
{
setState( State.CALL );
}
else if( getState() == State.DATA )
{
setState( State.DATA );
}
}
}
public void reset()
{
mTalkgroup = null;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = null;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
mDescription = null;
broadcastChange( ChangedAttribute.DESCRIPTION );
super.reset();
}
public String getToTalkgroup()
{
return mTalkgroup;
}
public Alias getToTalkgroupAlias()
{
return mTalkgroupAlias;
}
public String getDescription()
{
return mDescription;
}
public boolean hasChannelNumber()
{
return mChannelNumber != 0;
}
public int getChannelNumber()
{
return mChannelNumber;
}
}

View File

@@ -1,175 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.ltrnet;
import instrument.Instrumentable;
import instrument.tap.Tap;
import java.util.ArrayList;
import java.util.List;
import message.MessageDirection;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
import audio.AudioOutputImpl;
import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import decode.config.DecodeConfiguration;
import dsp.filter.DCRemovalFilter2;
import dsp.fsk.LTRFSKDecoder;
import dsp.nbfm.FilteringNBFMDemodulator;
public class LTRNetDecoder extends Decoder implements Instrumentable
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double sDC_REMOVAL_RATIO = 0.000003;
public static final int sLTR_STANDARD_MESSAGE_LENGTH = 40;
private FilteringNBFMDemodulator mDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
private LTRFSKDecoder mLTRFSKDecoder;
private MessageFramer mLTRMessageFramer;
private LTRNetMessageProcessor mLTRMessageProcessor;
private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "LTRNet Decoder Audio Output" );
private List<Tap> mAvailableTaps;
public LTRNetDecoder( DecodeConfiguration config,
SampleType sampleType,
AliasList aliasList,
MessageDirection direction )
{
super( config, sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
/**
* The Decoder super class is both a Complex listener and a Float
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
mDemodulator = new FilteringNBFMDemodulator();
this.addComplexListener( mDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO );
mDemodulator.addListener( mDCRemovalFilter );
mDCRemovalFilter.setListener( this.getRealReceiver() );
}
addRealSampleListener( mAudioOutput );
mLTRFSKDecoder = new LTRFSKDecoder();
/**
* The DC removal filter will impact performance of the LTRFSKDemod,
* so we get the samples straight from the NBFM demod
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
mDemodulator.addListener( mLTRFSKDecoder );
}
else
{
this.addRealSampleListener( mLTRFSKDecoder );
}
if( direction == MessageDirection.OSW )
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
sLTR_STANDARD_MESSAGE_LENGTH );
}
else
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
sLTR_STANDARD_MESSAGE_LENGTH );
}
mLTRFSKDecoder.addListener( mLTRMessageFramer );
mLTRMessageProcessor = new LTRNetMessageProcessor( direction, aliasList );
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
mLTRMessageProcessor.addMessageListener( this );
}
@Override
public DecoderType getType()
{
return DecoderType.LTR_NET;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.addAll( mLTRFSKDecoder.getTaps() );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
mLTRFSKDecoder.addTap( tap );
}
@Override
public void removeTap( Tap tap )
{
mLTRFSKDecoder.removeTap( tap );
}
@Override
public void addUnfilteredRealSampleListener( RealSampleListener listener )
{
if( mDemodulator != null )
{
mDemodulator.addListener( listener );
}
// TODO Auto-generated method stub
}
@Override
public IAudioOutput getAudioOutput()
{
return mAudioOutput;
}
}

View File

@@ -1,165 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.ltrnet;
import java.util.HashMap;
import message.Message;
import message.MessageDirection;
import message.MessageType;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
public class LTRNetMessageProcessor implements Listener<BinaryMessage>
{
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
private HashMap<Integer,LTRNetOSWMessage> mReceiveHighAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private HashMap<Integer,LTRNetOSWMessage> mReceiveLowAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private HashMap<Integer,LTRNetOSWMessage> mTransmitHighAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private HashMap<Integer,LTRNetOSWMessage> mTransmitLowAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private LTRNetISWMessage mESNHighMessage;
private LTRNetISWMessage mESNLowMessage;
private MessageDirection mDirection;
private AliasList mAliasList;
public LTRNetMessageProcessor( MessageDirection direction,
AliasList list )
{
mDirection = direction;
mAliasList = list;
}
@Override
public void receive( BinaryMessage buffer )
{
LTRNetMessage message;
if( mDirection == MessageDirection.OSW )
{
message = new LTRNetOSWMessage( buffer, mAliasList );
if( message.isValid() )
{
if( message.getMessageType() == MessageType.FQ_TXHI )
{
mTransmitHighAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
message.setAuxiliaryMessage( mTransmitLowAuxMessages.get(
message.getHomeRepeater() ) );
}
else if( message.getMessageType() == MessageType.FQ_TXLO )
{
mTransmitLowAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
message.setAuxiliaryMessage( mTransmitHighAuxMessages.get(
message.getHomeRepeater() ) );
}
else if( message.getMessageType() == MessageType.FQ_RXHI )
{
mReceiveHighAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
message.setAuxiliaryMessage( mReceiveLowAuxMessages.get(
message.getHomeRepeater() ) );
}
else if( message.getMessageType() == MessageType.FQ_RXLO )
{
mReceiveLowAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
message.setAuxiliaryMessage( mReceiveHighAuxMessages.get(
message.getHomeRepeater() ) );
}
}
}
else
{
buffer.flip( 0, 40 );
message = new LTRNetISWMessage( buffer, mAliasList );
if( message.isValid() )
{
/**
* Catch ESN High messages to marry up with ESN Low messages. Purge
* the ESN high message after 2 messages, so that it doesn't get
* paired with the wrong esn low message
*/
if( message.getMessageType() == MessageType.ID_ESNH )
{
mESNHighMessage = (LTRNetISWMessage)message;
if( mESNLowMessage != null )
{
/* Check that the stored message is not older than
* 5 seconds */
if( System.currentTimeMillis() -
mESNLowMessage.getTimeReceived() < 5000 )
{
((LTRNetISWMessage)message).setAuxiliaryMessage( mESNLowMessage );
}
else
{
mESNLowMessage = null;
}
}
}
else if( message.getMessageType() == MessageType.ID_ESNL )
{
mESNLowMessage = (LTRNetISWMessage)message;
if( mESNHighMessage != null )
{
/* Check that the stored message is not older than 5 seconds */
if( System.currentTimeMillis() - mESNHighMessage.getTimeReceived() < 5000 )
{
((LTRNetISWMessage)message).setAuxiliaryMessage( mESNHighMessage );
}
else
{
mESNHighMessage = null;
}
}
}
}
}
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
}

View File

@@ -1,240 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.ltrstandard;
import java.util.HashMap;
import message.Message;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent;
import controller.activity.CallEvent.CallEventType;
import controller.activity.CallEventModel;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import decode.ltrnet.LTRCallEvent;
public class LTRChannelState extends ChannelState
{
private String mTalkgroup;
private Alias mTalkgroupAlias;
private int mChannelNumber;
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
private LTRStandardActivitySummary mActivitySummary;
public LTRChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
mActivitySummary = new LTRStandardActivitySummary( aliasList );
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( mActivitySummary.getSummary() );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
@Override
public void receive( Message message )
{
super.receive( message );
if( message instanceof LTRStandardOSWMessage )
{
mActivitySummary.receive( message );
LTRStandardOSWMessage ltr = (LTRStandardOSWMessage)message;
switch( ltr.getMessageType() )
{
/**
* Process Call Message
*
* States:
* 1) Call message with invalid LCN
* 2) State=IDLE/FADE, new CA_STRT
* 3) State=CALL, new CA_STRT, same talkgroup
* 4) State=CALL, new CA_STRT, different talkgroup
*/
case CA_STRT:
if( 0 < ltr.getChannel() && ltr.getChannel() <=20 )
{
/* If we haven't captured the LCN yet ... */
if( mChannelNumber == 0 )
{
mChannelNumber = ltr.getChannel();
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
}
/* If this call event is for this channel, then update
* talkgroup and alias */
if( ltr.getChannel() == mChannelNumber )
{
mTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
LTRCallEvent current = getCurrentLTRCallEvent();
/* If we have an ongoing call, then add this message
* to the current event - if addMessage returns
* false, then we have a different talkgroup and a
* new call event, so close out the old one. */
if( current != null && !current.addMessage( ltr ) )
{
setState( State.FADE );
}
if( current == null )
{
LTRCallEvent event =
new LTRCallEvent.Builder( CallEventType.CALL )
.to( ltr.getTalkgroupID() )
.aliasList( getAliasList() )
.channel( String.valueOf( ltr.getChannel() ) )
.frequency( ( ltr.getChannel() == mChannelNumber ) ?
mProcessingChain.getChannel()
.getTunerChannel().getFrequency() : 0 )
.build();
mCallEventModel.add( event );
setCurrentCallEvent( event );
}
setState( State.CALL );
}
}
break;
case CA_ENDD:
mTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
fade( CallEventType.CALL_END );
break;
case SY_IDLE:
int channel = ltr.getChannel();
if( 0 < channel && channel <= 20 &&
channel == ltr.getHomeRepeater() &&
channel == ltr.getFree() )
{
mChannelNumber = channel;
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
}
break;
default:
break;
}
}
}
public LTRCallEvent getCurrentLTRCallEvent()
{
CallEvent current = getCurrentCallEvent();
if( current != null )
{
return (LTRCallEvent)current;
}
return null;
}
/**
* Intercept the fade event so that we can generate a call end event
*/
@Override
public void fade( final CallEventType type )
{
/* We can receive multiple call tear-down messages -- only respond to
* the message that can change the state to fade */
if( getState().canChangeTo( State.FADE ) )
{
mActiveCalls.clear();
LTRCallEvent current = getCurrentLTRCallEvent();
if( current != null )
{
/* Close out the call event. If we only received 1 call
* message, then flag it as noise and remove the call event */
if( current.isValid() )
{
mCallEventModel.setEnd( current );
}
else
{
mCallEventModel.remove( current );
}
}
setCurrentCallEvent( null );
}
super.fade( type );
}
public void reset()
{
mTalkgroup = null;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mTalkgroupAlias = null;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
super.reset();
}
public String getTalkgroup()
{
return mTalkgroup;
}
public Alias getTalkgroupAlias()
{
return mTalkgroupAlias;
}
public boolean hasChannelNumber()
{
return mChannelNumber != 0;
}
public int getChannelNumber()
{
return mChannelNumber;
}
}

View File

@@ -1,209 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.ltrstandard;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class LTRStandardActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
private AliasList mAliasList;
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<Integer> mActiveLCNs = new TreeSet<Integer>();
private int mMonitoredLCN;
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public LTRStandardActivitySummary( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( Message message )
{
if( message instanceof LTRStandardOSWMessage )
{
LTRStandardOSWMessage ltr = ((LTRStandardOSWMessage)message);
if( ltr.isValid() )
{
switch( ltr.getMessageType() )
{
case CA_STRT:
case CA_ENDD:
int home = ltr.getHomeRepeater();
if( 0 < home && home <= 20 )
{
String talkgroup = ltr.getTalkgroupID( false );
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
}
int channel = ltr.getChannel();
if( 0 < channel && channel <= 20 )
{
if( channel == home )
{
mMonitoredLCN = channel;
mActiveLCNs.add( channel );
}
int free = ltr.getFree();
if( 0 < free && free <= 20 )
{
mActiveLCNs.add( free );
}
}
break;
case SY_IDLE:
int idleChannel = ltr.getChannel();
if( idleChannel == ltr.getHomeRepeater() &&
idleChannel == ltr.getFree() )
{
mMonitoredLCN = idleChannel;
mActiveLCNs.add( idleChannel );
}
break;
case UN_KNWN:
default:
break;
}
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n\n" );
sb.append( "Decoder:\tLTR-Standard\n" );
sb.append( "Monitored LCN: " );
if( mMonitoredLCN > 0 )
{
sb.append( mMonitoredLCN );
}
else
{
sb.append( "unknown" );
}
sb.append( "\n" );
Integer[] lcns = mActiveLCNs.toArray( new Integer[0] );
sb.append( "Active LCNs:\t" );
if( lcns.length > 0 )
{
sb.append( Arrays.toString( lcns ) );
}
else
{
sb.append( "none" );
}
sb.append( "\n\n" );
sb.append( "Talkgroups\n" );
if( mTalkgroups.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mTalkgroups.iterator();
while( it.hasNext() )
{
String tgid = it.next();
sb.append( " " );
sb.append( formatTalkgroup( tgid ) );
sb.append( " " );
Alias alias = mAliasList.getTalkgroupAlias( tgid );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
return sb.toString();
}
public static String formatTalkgroup( String talkgroup )
{
StringBuilder sb = new StringBuilder();
if( talkgroup.length() == 6 )
{
sb.append( talkgroup.substring( 0, 1 ) );
sb.append( "-" );
sb.append( talkgroup.substring( 1, 3 ) );
sb.append( "-" );
sb.append( talkgroup.substring( 3, 6 ) );
return sb.toString();
}
else
{
return talkgroup;
}
}
}

View File

@@ -1,141 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.ltrstandard;
import message.MessageDirection;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
import audio.AudioOutputImpl;
import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import decode.config.DecodeConfiguration;
import dsp.filter.DCRemovalFilter2;
import dsp.fsk.LTRFSKDecoder;
import dsp.nbfm.FilteringNBFMDemodulator;
public class LTRStandardDecoder extends Decoder
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double sDC_REMOVAL_RATIO = 0.000003;
public static final int sLTR_STANDARD_MESSAGE_LENGTH = 40;
private FilteringNBFMDemodulator mDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
private LTRFSKDecoder mLTRFSKDecoder;
private MessageFramer mLTRMessageFramer;
private LTRStandardMessageProcessor mLTRMessageProcessor;
private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "LTR Standard Decoder Audio Output" );
public LTRStandardDecoder( DecodeConfiguration config,
SampleType sampleType,
AliasList aliasList,
MessageDirection direction )
{
super( config, sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
/**
* The Decoder super class is both a Complex listener and a Short
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
mDemodulator = new FilteringNBFMDemodulator();
this.addComplexListener( mDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO );
mDemodulator.addListener( mDCRemovalFilter );
mDCRemovalFilter.setListener( this.getRealReceiver() );
}
addRealSampleListener( mAudioOutput );
mLTRFSKDecoder = new LTRFSKDecoder();
/**
* The DC removal filter will impact performance of the LTRFSKDemod,
* so we get the samples straight from the NBFM demod
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
mDemodulator.addListener( mLTRFSKDecoder );
}
else
{
this.addRealSampleListener( mLTRFSKDecoder );
}
if( direction == MessageDirection.OSW )
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
sLTR_STANDARD_MESSAGE_LENGTH );
}
else
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
sLTR_STANDARD_MESSAGE_LENGTH );
}
mLTRFSKDecoder.addListener( mLTRMessageFramer );
mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
mLTRMessageProcessor.addMessageListener( this );
}
@Override
public DecoderType getType()
{
return DecoderType.LTR_STANDARD;
}
@Override
public void addUnfilteredRealSampleListener( RealSampleListener listener )
{
if( mDemodulator != null )
{
mDemodulator.addListener( listener );
}
}
@Override
public IAudioOutput getAudioOutput()
{
return mAudioOutput;
}
}

View File

@@ -1,131 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.mdc1200;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class MDCActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private AliasList mAliasList;
private TreeSet<String> mIdents = new TreeSet<String>();
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public MDCActivitySummary( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( Message message )
{
if( message instanceof MDCMessage )
{
MDCMessage mdc = ((MDCMessage)message);
mIdents.add( mdc.getUnitID() );
if( mdc.isEmergency() )
{
mEmergencyIdents.add( mdc.getUnitID() );
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "=============================\n" );
sb.append( "Decoder:\tMDC-1200\n\n" );
sb.append( "MDC-1200 Idents\n" );
if( mIdents.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mIdents.iterator();
while( it.hasNext() )
{
String ident = it.next();
sb.append( " " );
sb.append( ident );
sb.append( " " );
Alias alias = mAliasList.getMDC1200Alias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
sb.append( "MDC-1200 Emergency Idents\n" );
if( mEmergencyIdents.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mEmergencyIdents.iterator();
while( it.hasNext() )
{
String ident = it.next();
sb.append( " " );
sb.append( ident );
sb.append( " " );
Alias alias = mAliasList.getMDC1200Alias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
return sb.toString();
}
}

View File

@@ -1,183 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.mdc1200;
import message.Message;
import alias.Alias;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelState.State;
public class MDCChannelState extends AuxChannelState
{
private String mFrom;
private Alias mFromAlias;
private String mTo;
private Alias mToAlias;
private String mMessage;
private String mMessageType;
private MDCActivitySummary mActivitySummary;
public MDCChannelState( ChannelState parentState )
{
super( parentState );
mActivitySummary = new MDCActivitySummary( parentState.getAliasList() );
}
@Override
public void reset()
{
setFrom( null );
setFromAlias( null );
setTo( null);
setToAlias( null );
setMessage( null );
setMessageType( null );
}
@Override
public void fade() {}
@Override
public void receive( Message message )
{
if( message instanceof MDCMessage )
{
mActivitySummary.receive( message );
MDCMessage mdc = (MDCMessage)message;
setFrom( mdc.getFromID() );
setFromAlias( mdc.getFromIDAlias() );
MDCMessageType type = mdc.getMessageType();
setMessageType( type.getLabel() );
StringBuilder sb = new StringBuilder();
switch( type )
{
case ACKNOWLEDGE:
case ANI:
case EMERGENCY:
case PAGING:
case STATUS:
default:
sb.append( "OPCODE " );
sb.append( String.valueOf( mdc.getOpcode() ) );
if( mdc.isBOT() )
{
sb.append( " TYPE:BOT" );
}
if( mdc.isEOT() )
{
sb.append( " TYPE:EOT" );
}
break;
}
setMessage( sb.toString() );
//Set the state to CALL on the parent channel state, so that the
//timer can track turning off any updates we apply
mParentChannelState.setState( State.CALL );
MDCCallEvent event = MDCCallEvent.getMDCCallEvent( mdc );
event.setAliasList( mParentChannelState.getAliasList() );
mParentChannelState.receiveCallEvent( event );
}
}
public String getFrom()
{
return mFrom;
}
public void setFrom( String from )
{
mFrom = from;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getFromAlias()
{
return mFromAlias;
}
public void setFromAlias( Alias alias )
{
mFromAlias = alias;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
public String getTo()
{
return mTo;
}
public void setTo( String to )
{
mTo = to;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
}
public Alias getToAlias()
{
return mToAlias;
}
public void setToAlias( Alias alias )
{
mToAlias = alias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getMessage()
{
return mMessage;
}
public void setMessage( String message )
{
mMessage = message;
broadcastChange( ChangedAttribute.MESSAGE );
}
public String getMessageType()
{
return mMessageType;
}
public void setMessageType( String type )
{
mMessageType = type;
broadcastChange( ChangedAttribute.MESSAGE_TYPE );
}
@Override
public String getActivitySummary()
{
return mActivitySummary.getSummary();
}
}

View File

@@ -1,159 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.mpt1327;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class MPT1327ActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private static final int sINT_NULL_VALUE = -1;
private AliasList mAliasList;
private TreeSet<String> mIdents = new TreeSet<String>();
private String mSite;
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public MPT1327ActivitySummary( AliasList list )
{
mAliasList = list;
}
/**
* Cleanup method
*/
public void dispose()
{
mIdents.clear();
}
@Override
public void receive( Message message )
{
if( message instanceof MPT1327Message )
{
MPT1327Message mpt = ((MPT1327Message)message);
if( mpt.isValid() )
{
switch( mpt.getMessageType() )
{
case ACK:
mIdents.add( mpt.getFromID() );
break;
case ACKI:
mIdents.add( mpt.getFromID() );
mIdents.add( mpt.getToID() );
break;
case AHYC:
mIdents.add( mpt.getToID() );
break;
case ALH:
mSite = mpt.getSiteID();
break;
case GTC:
if( mpt.hasFromID() )
{
mIdents.add( mpt.getFromID() );
}
if( mpt.hasToID() )
{
mIdents.add( mpt.getToID() );
}
break;
}
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "Decoder:\tMPT-1327\n" );
sb.append( "Site:\t" );
if( mSite != null )
{
sb.append( mSite );
if( mAliasList != null )
{
Alias siteAlias = mAliasList.getSiteID( mSite );
if( siteAlias != null )
{
sb.append( " " );
sb.append( siteAlias.getName() );
sb.append( "\n" );
}
}
}
else
{
sb.append( "Unknown\n" );
}
sb.append( "\n" );
sb.append( "Talkgroups\n" );
if( mIdents.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mIdents.iterator();
while( it.hasNext() )
{
String ident = it.next();
sb.append( " " );
sb.append( ident );
sb.append( " " );
Alias alias = mAliasList.getMPT1327Alias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
return sb.toString();
}
}

View File

@@ -1,422 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.mpt1327;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import message.Message;
import sample.Listener;
import source.config.SourceConfigTuner;
import alias.AliasList;
import audio.SquelchListener;
import audio.SquelchListener.SquelchState;
import controller.activity.CallEvent;
import controller.activity.CallEvent.CallEventType;
import controller.channel.Channel;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelMap;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import decode.config.DecodeConfigMPT1327;
import decode.mpt1327.MPT1327Message.IdentType;
public class MPT1327ChannelState extends ChannelState
{
private final static Logger mLog = LoggerFactory.getLogger( MPT1327ChannelState.class );
private String mSite;
private String mFromTalkgroup;
private String mToTalkgroup;
private int mChannelNumber;
private ChannelType mChannelType;
private ChannelMap mChannelMap;
private MPT1327ActivitySummary mActivitySummary;
public MPT1327ChannelState( ProcessingChain processingChain,
AliasList aliasList,
ChannelMap channelMap )
{
super( processingChain, aliasList );
mActivitySummary = new MPT1327ActivitySummary( aliasList );
mChannelMap = channelMap;
mChannelType = processingChain.getChannel().getChannelType();
}
public void dispose()
{
super.dispose();
mActivitySummary.dispose();
mActivitySummary = null;
mCurrentCallEvent = null;
}
public void setCurrentCall( MPT1327CallEvent event )
{
mCurrentCallEvent = event;
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( mActivitySummary.getSummary() );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
@Override
public void receive( Message message )
{
super.receive( message );
if( message instanceof MPT1327Message )
{
mActivitySummary.receive( message );
MPT1327Message mpt = (MPT1327Message)message;
if( mpt.isValid() )
{
switch( mpt.getMessageType() )
{
case ACK:
IdentType identType = ( (MPT1327Message) message ).getIdent1Type();
if( identType == IdentType.REGI )
{
mCallEventModel.add(
new MPT1327CallEvent.Builder( CallEventType.REGISTER )
.aliasList( getProcessingChain().getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( "REGISTERED ON NETWORK" )
.frequency( mChannelMap.getFrequency( mChannelNumber ) )
.from( mpt.getToID() )
.to( mpt.getFromID() )
.build() );
}
else
{
mCallEventModel.add(
new MPT1327CallEvent.Builder( CallEventType.RESPONSE )
.aliasList( getProcessingChain().getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( "ACK " + identType.getLabel() )
.frequency( mChannelMap.getFrequency( mChannelNumber ) )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
}
break;
case AHYC:
mCallEventModel.add(
new MPT1327CallEvent.Builder( CallEventType.COMMAND )
.aliasList( getProcessingChain().getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( ( (MPT1327Message) message ).getRequestString() )
.frequency( mChannelMap.getFrequency( mChannelNumber ) )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
break;
case AHYQ:
mCallEventModel.add(
new MPT1327CallEvent.Builder( CallEventType.STATUS )
.aliasList( getProcessingChain().getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( mpt.getStatusMessage() )
.frequency( mChannelMap.getFrequency( mChannelNumber ) )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
break;
case ALH:
String site = mpt.getSiteID();
if( mSite == null )
{
mSite = site;
broadcastChange( ChangedAttribute.CHANNEL_SITE_NUMBER );
}
else if( site != null && !site.contentEquals( mSite ) )
{
mSite = site;
broadcastChange( ChangedAttribute.CHANNEL_SITE_NUMBER );
}
setState( State.CONTROL );
break;
case GTC:
if( mpt.isValidCall() )
{
int channelNumber = mpt.getChannel();
if( !getProcessingChain().getChannel()
.hasTrafficChannel( channelNumber ) )
{
String aliasListName = mProcessingChain
.getChannel().getAliasListName();
Channel traffic = getTrafficChannel( channelNumber,
aliasListName );
/* Set the system and site to same as control channel */
traffic.setSystem( getProcessingChain().getChannel()
.getSystem(), false );
traffic.setSite( getProcessingChain().getChannel()
.getSite(), false );
/* Add the traffic channel to the parent control channel */
getProcessingChain().getChannel()
.addTrafficChannel( channelNumber, traffic );
/* Start the traffic channel */
traffic.setEnabled( true );
/* Set traffic channel state info */
MPT1327ChannelState trafficState =
(MPT1327ChannelState)traffic
.getProcessingChain().getChannelState();
MPT1327ChannelState controlState =
(MPT1327ChannelState)getProcessingChain()
.getChannelState();
trafficState.setChannelMap( controlState.getChannelMap() );
trafficState.setChannelNumber( channelNumber );
trafficState.setFromTalkgroup( mpt.getFromID() );
trafficState.setToTalkgroup( mpt.getToID() );
/* Add this control channel as message listener on the
* traffic channel so we can receive call tear-down
* messages */
traffic.addListener( (Listener<Message>)this );
CallEventType type = traffic.isProcessing() ?
CallEventType.CALL :
CallEventType.CALL_NO_TUNER;
/*
* Set the traffic channel's call event model to
* share the control channel's call event model
*/
traffic.getProcessingChain().getChannelState()
.setCallEventModel( mCallEventModel );
MPT1327CallEvent callStartEvent =
new MPT1327CallEvent.Builder( type )
.aliasList( traffic.getProcessingChain().getAliasList() )
.channel( String.valueOf( channelNumber ) )
.frequency( mChannelMap.getFrequency( channelNumber ) )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build();
traffic.getProcessingChain().getChannelState()
.setCurrentCallEvent( callStartEvent );
mCallEventModel.add( callStartEvent );
}
}
break;
case CLEAR:
case MAINT:
if( mChannelType == ChannelType.TRAFFIC )
{
fade( CallEventType.CALL_END );
}
break;
case HEAD_PLUS1:
case HEAD_PLUS2:
case HEAD_PLUS3:
case HEAD_PLUS4:
mCallEventModel.add(
new MPT1327CallEvent.Builder( CallEventType.SDM )
.aliasList( mAliasList )
.details( mpt.getMessage() )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
break;
}
}
}
}
/**
* Intercept the fade event so that we can generate a call end event
*/
@Override
public void fade( final CallEventType type )
{
/*
* We can receive multiple call tear-down messages -- only post a call
* end event for the one that can change the state to fade
*/
if( getState().canChangeTo( State.FADE ) )
{
CallEvent current = getCurrentCallEvent();
if( current != null )
{
mCallEventModel.setEnd( current );
}
else
{
mCallEventModel.add(
new MPT1327CallEvent.Builder( type )
.aliasList( getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.frequency( mChannelMap.getFrequency( mChannelNumber ) )
.from( mFromTalkgroup )
.to( mToTalkgroup )
.build() );
}
setCurrentCall( null );
}
super.fade( type );
}
/**
* Make the ConventionalChannelState always unsquelched
*/
public void addListener( SquelchListener listener )
{
super.addListener( listener );
super.setSquelchState( SquelchState.UNSQUELCH );
}
public void reset()
{
mFromTalkgroup = null;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mToTalkgroup = null;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
super.reset();
}
public String getSite()
{
return mSite;
}
public ChannelMap getChannelMap()
{
return mChannelMap;
}
public void setChannelMap( ChannelMap channelMap )
{
mChannelMap = channelMap;
}
public String getFromTalkgroup()
{
return mFromTalkgroup;
}
/**
* Set the talkgroup. This is used primarily for traffic channels since
* the talkgroup will already have been identified prior to the traffic
* channel being created.
*/
public void setFromTalkgroup( String fromTalkgroup )
{
mFromTalkgroup = fromTalkgroup;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
}
public String getToTalkgroup()
{
return mToTalkgroup;
}
/**
* Set the talkgroup. This is used primarily for traffic channels since
* the talkgroup will already have been identified prior to the traffic
* channel being created.
*/
public void setToTalkgroup( String toTalkgroup )
{
mToTalkgroup = toTalkgroup;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
}
public int getChannelNumber()
{
return mChannelNumber;
}
/**
* Set the channel number. This is used primarily for traffic channels since
* the channel will already have been identified prior to the traffic
* channel being created.
*/
public void setChannelNumber( int channel )
{
mChannelNumber = channel;
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
}
public String getAliasListName()
{
return mProcessingChain.getChannel().getAliasListName();
}
private Channel getTrafficChannel( int channelNumber, String aliasListName )
{
Channel traffic = new Channel( "Traffic Channel " + channelNumber,
ChannelType.TRAFFIC );
traffic.setResourceManager( getProcessingChain().getResourceManager() );
SourceConfigTuner source = new SourceConfigTuner();
long frequency = mChannelMap.getFrequency( channelNumber );
source.setFrequency( frequency );
traffic.setSourceConfiguration( source );
DecodeConfigMPT1327 decode = new DecodeConfigMPT1327();
traffic.setDecodeConfiguration( decode );
traffic.setAliasListName( aliasListName );
return traffic;
}
}

View File

@@ -1,282 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.mpt1327;
import java.awt.EventQueue;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import alias.Alias;
import alias.AliasList;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class MPT1327Panel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private JLabel mStateLabel;
private JLabel mTalkgroup;
private JLabel mTalkgroupAlias;
private JLabel mSiteLabel;
private JLabel mSite;
private JLabel mSiteAliasLabel;
private MPT1327ChannelState mState;
private AliasList mAliasList;
public MPT1327Panel( Channel channel )
{
super( channel );
mState = (MPT1327ChannelState)channel.getProcessingChain().getChannelState();
mState.addListener( this );
mAliasList = mState.getAliasList();
init();
}
public void dispose()
{
super.dispose();
mState = null;
mAliasList = null;
}
public void init()
{
mProtocol = new JLabel( "MPT1327" );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mTalkgroup = new JLabel( mState.getFromTalkgroup() );
mTalkgroup.setFont( mFontDecoder );
mTalkgroup.setForeground( mColorLabelDecoder );
mTalkgroupAlias = new JLabel();
mTalkgroupAlias.setFont( mFontDecoder );
mTalkgroupAlias.setForeground( mColorLabelDecoder );
Alias alias = getTalkgroupAlias( mState.getFromTalkgroup() );
if( alias != null )
{
mTalkgroupAlias.setText( alias.getName() );
mTalkgroupAlias.setIcon( getIcon( alias ) );
}
mSiteLabel = new JLabel( "Site:" );
mSiteLabel.setFont( mFontDetails );
mSiteLabel.setForeground( mColorLabelDetails );
mSite = new JLabel( String.valueOf( mState.getSite() ) );
mSite.setFont( mFontDecoder );
mSite.setForeground( mColorLabelDecoder );
mSiteAliasLabel = new JLabel();
mSiteAliasLabel.setFont( mFontDecoder );
mSiteAliasLabel.setForeground( mColorLabelDecoder );
Alias siteAlias = getSiteAlias( mState.getSite() );
if( siteAlias != null )
{
mSiteAliasLabel.setText( siteAlias.getName() );
}
add( mProtocol );
add( mSourceLabel );
add( mChannelLabel, "wrap" );
add( mStateLabel );
add( mTalkgroup );
add( mTalkgroupAlias, "wrap" );
add( mSiteLabel );
add( mSite );
add( mSiteAliasLabel, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( mState.getState().getDisplayValue() );
break;
case CHANNEL_SITE_NUMBER:
mSite.setText( String.valueOf( mState.getSite() ) );
Alias siteAlias = getSiteAlias( mState.getSite() );
if( siteAlias != null )
{
mSiteAliasLabel.setText( siteAlias.getName() );
}
repaint();
break;
case FROM_TALKGROUP:
mTalkgroup.setText( mState.getFromTalkgroup() );
Alias alias = getTalkgroupAlias( mState.getFromTalkgroup() );
if( alias != null )
{
mTalkgroupAlias.setText( alias.getName() );
mTalkgroupAlias.setIcon( getIcon( alias ) );
}
repaint();
break;
}
}
} );
}
private ImageIcon getIcon( Alias alias )
{
if( alias != null )
{
String iconName = alias.getIconName();
if( iconName != null )
{
return getSettingsManager().getImageIcon( iconName, 12 );
}
}
return null;
}
private Alias getSiteAlias( String site )
{
if( site != null && mAliasList != null )
{
return mAliasList.getSiteID( site );
}
return null;
}
private Alias getTalkgroupAlias( String talkgroup )
{
if( talkgroup != null && mAliasList != null )
{
return mAliasList.getTalkgroupAlias( talkgroup );
}
return null;
}
@Override
public void trafficChannelAdded( Channel traffic )
{
if( !getTrafficPanels().keySet().contains( traffic ) )
{
MPT1327TrafficPanel panel =
new MPT1327TrafficPanel( traffic );
getTrafficPanels().put( traffic, panel );
addPanel( panel );
}
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
if( mStateLabel != null )
{
mStateLabel.setForeground( mColorLabelDecoder );
}
if( mTalkgroup != null )
{
mTalkgroup.setForeground( mColorLabelDecoder );
}
if( mTalkgroupAlias != null )
{
mTalkgroupAlias.setForeground( mColorLabelDecoder );
}
if( mSite != null )
{
mSite.setForeground( mColorLabelDecoder );
}
if( mSiteAliasLabel != null )
{
mSiteAliasLabel.setForeground( mColorLabelDecoder );
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mSourceLabel != null )
{
mSourceLabel.setForeground( mColorLabelDetails );
}
if( mChannelLabel != null )
{
mChannelLabel.setForeground( mColorLabelDetails );
}
if( mSiteLabel != null )
{
mSiteLabel.setForeground( mColorLabelDetails );
}
break;
}
}
}
}

View File

@@ -1,258 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.mpt1327;
import java.awt.EventQueue;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import alias.Alias;
import alias.AliasList;
import controller.channel.Channel;
import controller.channel.ProcessingChain;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class MPT1327TrafficPanel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocolLabel;
private JLabel mFrequencyLabel;
private JLabel mChannelLabel;
private JLabel mStateLabel;
private JLabel mFromTalkgroupLabel;
private JLabel mFromTalkgroupAliasLabel;
private JLabel mToLabel;
private JLabel mToTalkgroupLabel;
private JLabel mToTalkgroupAliasLabel;
private AliasList mAliasList;
public MPT1327TrafficPanel( Channel channel )
{
super( channel );
mAliasList = channel.getProcessingChain().getChannelState().getAliasList();
init();
}
public void dispose()
{
super.dispose();
mAliasList = null;
}
public void init()
{
mProtocolLabel = new JLabel( "MPT1327" );
mProtocolLabel.setFont( mFontDecoder );
mProtocolLabel.setForeground( mColorLabelDecoder );
mFrequencyLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mFrequencyLabel.setFont( mFontDetails );
mFrequencyLabel.setForeground( mColorLabelDetails );
MPT1327ChannelState state =
(MPT1327ChannelState)mChannel.getProcessingChain().getChannelState();
int channelNumber = state.getChannelNumber();
mChannelLabel = new JLabel( "Channel " + channelNumber );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupLabel = new JLabel( state.getFromTalkgroup() );
mFromTalkgroupLabel.setFont( mFontDecoder );
mFromTalkgroupLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupAliasLabel = new JLabel();
mFromTalkgroupAliasLabel.setFont( mFontDecoder );
mFromTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
if( mAliasList != null )
{
Alias alias = mAliasList.getMPT1327Alias( state.getFromTalkgroup() );
if( alias != null )
{
mFromTalkgroupAliasLabel.setText( alias.getName() );
mFromTalkgroupAliasLabel.setIcon( getChannel()
.getResourceManager().getSettingsManager()
.getImageIcon( alias.getIconName(), 12 ) );
}
}
mToLabel = new JLabel( "TO:" );
mToLabel.setFont( mFontDetails );
mToLabel.setForeground( mColorLabelDetails );
mToTalkgroupLabel = new JLabel( state.getFromTalkgroup() );
mToTalkgroupLabel.setFont( mFontDecoder );
mToTalkgroupLabel.setForeground( mColorLabelDecoder );
mToTalkgroupAliasLabel = new JLabel();
mToTalkgroupAliasLabel.setFont( mFontDecoder );
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
if( mAliasList != null )
{
Alias alias = mAliasList.getMPT1327Alias( state.getFromTalkgroup() );
if( alias != null )
{
mToTalkgroupAliasLabel.setText( alias.getName() );
mToTalkgroupAliasLabel.setIcon( getChannel()
.getResourceManager().getSettingsManager()
.getImageIcon( alias.getIconName(), 12 ) );
}
}
add( mProtocolLabel );
add( mFrequencyLabel );
add( mChannelLabel, "wrap" );
add( mStateLabel );
add( mFromTalkgroupLabel );
add( mFromTalkgroupAliasLabel, "wrap" );
add( mToLabel );
add( mToTalkgroupLabel );
add( mToTalkgroupAliasLabel, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
ProcessingChain chain = getChannel().getProcessingChain();
if( chain != null )
{
final MPT1327ChannelState state =
(MPT1327ChannelState)chain.getChannelState();
if( state != null )
{
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( state.getState().getDisplayValue() );
break;
case CHANNEL_NUMBER:
mChannelLabel.setText( "Channel " + state.getChannelNumber() );
break;
case FROM_TALKGROUP:
String talkgroup = state.getFromTalkgroup();
mFromTalkgroupLabel.setText( talkgroup );
if( mAliasList != null )
{
Alias alias = mAliasList.getMPT1327Alias( talkgroup );
if( alias != null )
{
mFromTalkgroupAliasLabel.setText( alias.getName() );
String icon = alias.getIconName();
mFromTalkgroupAliasLabel.setIcon( getChannel()
.getResourceManager().getSettingsManager()
.getImageIcon( icon, 12 ) );
}
}
break;
case TO_TALKGROUP:
String toTalkgroup = state.getToTalkgroup();
mToTalkgroupLabel.setText( toTalkgroup );
if( mAliasList != null )
{
Alias alias = mAliasList.getMPT1327Alias( toTalkgroup );
if( alias != null )
{
mToTalkgroupAliasLabel.setText( alias.getName() );
String icon = alias.getIconName();
if( icon != null )
{
mToTalkgroupAliasLabel.setIcon( getChannel()
.getResourceManager().getSettingsManager()
.getImageIcon( icon, 12 ) );
}
}
}
break;
default:
break;
}
repaint();
}
}
}
} );
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
mProtocolLabel.setForeground( mColorLabelDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
mToTalkgroupLabel.setForeground( mColorLabelDecoder );
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
break;
case CHANNEL_STATE_LABEL_DETAILS:
mFrequencyLabel.setForeground( mColorLabelDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mToLabel.setForeground( mColorLabelDetails );
break;
}
}
}
}

View File

@@ -1,69 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.nbfm;
import alias.AliasList;
import audio.SquelchListener;
import audio.SquelchListener.SquelchState;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
/**
* Conventional channel state - this class doesn't do anything at the moment,
* it is a place holder for future expansion for the conventional decoder
*/
public class NBFMChannelState extends ChannelState
{
public NBFMChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
}
/**
* Make the ConventionalChannelState always unsquelched
*/
public void addListener( SquelchListener listener )
{
super.addListener( listener );
super.setSquelchState( SquelchState.UNSQUELCH );
}
@Override
public void setSquelchState( SquelchState state )
{
//do nothing ... we want the squelch state always unsquelched (for now)
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "\tDecoder:\tNBFM\n\n" );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
}

View File

@@ -1,99 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.nbfm;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import audio.AudioOutputImpl;
import audio.IAudioOutput;
import decode.Decoder;
import decode.DecoderType;
import decode.config.DecodeConfiguration;
import dsp.filter.DCRemovalFilter2;
import dsp.nbfm.FilteringNBFMDemodulator;
public class NBFMDecoder extends Decoder
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double sDC_REMOVAL_RATIO = 0.000003;
private FilteringNBFMDemodulator mDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "NBFM Decoder Audio Output" );
public NBFMDecoder( DecodeConfiguration config, SampleType sampleType )
{
super( config, sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
/**
* The Decoder super class is both a Complex listener and a float
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
mDemodulator = new FilteringNBFMDemodulator();
this.addComplexListener( mDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO );
mDemodulator.addListener( mDCRemovalFilter );
/**
* Route the demodulated, filtered samples back to this class to send
* to all registered listeners
*/
mDCRemovalFilter.setListener( this.getRealReceiver() );
}
addRealSampleListener( mAudioOutput );
}
@Override
public DecoderType getType()
{
return DecoderType.NBFM;
}
@Override
public void addUnfilteredRealSampleListener( RealSampleListener listener )
{
if( mDemodulator != null )
{
mDemodulator.addListener( listener );
}
}
@Override
public IAudioOutput getAudioOutput()
{
return mAudioOutput;
}
}

View File

@@ -1,136 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.nbfm;
import java.awt.EventQueue;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class NBFMPanel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mStateLabel;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private JLabel mProtocol = new JLabel( "NBFM" );
public NBFMPanel( Channel channel )
{
super( channel );
init();
}
public void init()
{
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
add( mStateLabel );
add( mSourceLabel );
add( mChannelLabel, "wrap" );
add( mProtocol, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
break;
case SOURCE:
mSourceLabel.setText( mChannel.getSourceConfiguration()
.getDescription() );
break;
case CHANNEL_NAME:
case SITE_NAME:
case SYSTEM_NAME:
mChannelLabel.setText( mChannel.getChannelDisplayName() );
break;
}
repaint();
}
});
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mStateLabel != null )
{
mStateLabel.setForeground( mColorLabelDecoder );
}
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mSourceLabel != null )
{
mSourceLabel.setForeground( mColorLabelDetails );
}
if( mChannelLabel != null )
{
mChannelLabel.setForeground( mColorLabelDetails );
}
break;
}
}
}
}

View File

@@ -1,350 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.p25;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
import decode.p25.message.IBandIdentifier;
import decode.p25.message.pdu.PDUMessage;
import decode.p25.message.pdu.osp.control.NetworkStatusBroadcastExtended;
import decode.p25.message.pdu.osp.control.ProtectionParameterBroadcast;
import decode.p25.message.pdu.osp.control.RFSSStatusBroadcastExtended;
import decode.p25.message.tsbk.TSBKMessage;
import decode.p25.message.tsbk.osp.control.AdjacentStatusBroadcast;
import decode.p25.message.tsbk.osp.control.IdentifierUpdate;
import decode.p25.message.tsbk.osp.control.NetworkStatusBroadcast;
import decode.p25.message.tsbk.osp.control.RFSSStatusBroadcast;
import decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast;
import decode.p25.message.tsbk.osp.control.SystemService;
import decode.p25.message.tsbk.osp.data.SNDCPDataChannelAnnouncementExplicit;
import decode.p25.reference.Service;
public class P25ActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private static final int sINT_NULL_VALUE = -1;
private final static Logger mLog =
LoggerFactory.getLogger( P25ActivitySummary.class );
private static final DecimalFormat mFrequencyFormatter =
new DecimalFormat( "0.000000" );
private AliasList mAliasList;
private NetworkStatusBroadcast mNetworkStatus;
private NetworkStatusBroadcastExtended mNetworkStatusExtended;
private ProtectionParameterBroadcast mProtectionParameterBroadcast;
private RFSSStatusBroadcast mRFSSStatusMessage;
private RFSSStatusBroadcastExtended mRFSSStatusMessageExtended;
private SNDCPDataChannelAnnouncementExplicit mSNDCPDataChannel;
private HashMap<String,AdjacentStatusBroadcast> mNeighborMap =
new HashMap<String,AdjacentStatusBroadcast>();
private Set<SecondaryControlChannelBroadcast> mSecondaryControlChannels =
new TreeSet<SecondaryControlChannelBroadcast>();
private HashMap<Integer,IdentifierUpdate> mBands =
new HashMap<Integer,IdentifierUpdate>();
public P25ActivitySummary( AliasList list )
{
mAliasList = list;
}
/**
* Cleanup method
*/
public void dispose()
{
}
@Override
public void receive( Message message )
{
if( !message.isValid() )
{
mLog.debug( "Got an invalid message!" );
}
if( message instanceof TSBKMessage )
{
if( message instanceof AdjacentStatusBroadcast )
{
AdjacentStatusBroadcast neighborMessage =
(AdjacentStatusBroadcast)message;
mNeighborMap.put( neighborMessage.getUniqueID(), neighborMessage );
}
else if( message instanceof IdentifierUpdate )
{
IdentifierUpdate iu = (IdentifierUpdate)message;
if( !mBands.containsKey( iu.getIdentifier() ) )
{
mBands.put( iu.getIdentifier(), iu );
}
}
else if( message instanceof NetworkStatusBroadcast )
{
mNetworkStatus = (NetworkStatusBroadcast)message;
}
else if( message instanceof RFSSStatusBroadcast )
{
mRFSSStatusMessage = (RFSSStatusBroadcast)message;
}
else if( message instanceof SecondaryControlChannelBroadcast )
{
SecondaryControlChannelBroadcast sccb =
(SecondaryControlChannelBroadcast)message;
if( sccb.getDownlinkFrequency1() > 0 )
{
mSecondaryControlChannels.add(
(SecondaryControlChannelBroadcast)message );
}
}
else if( message instanceof SNDCPDataChannelAnnouncementExplicit )
{
mSNDCPDataChannel = (SNDCPDataChannelAnnouncementExplicit)message;
}
}
else if( message instanceof PDUMessage )
{
if( message instanceof NetworkStatusBroadcastExtended )
{
mNetworkStatusExtended = (NetworkStatusBroadcastExtended)message;
}
else if( message instanceof ProtectionParameterBroadcast )
{
mProtectionParameterBroadcast =
(ProtectionParameterBroadcast)message;
}
else if( message instanceof RFSSStatusBroadcastExtended )
{
mRFSSStatusMessageExtended = (RFSSStatusBroadcastExtended)message;
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "Decoder:\tP25\n" );
sb.append( "===================== THIS SITE ======================" );
if( mNetworkStatus != null )
{
sb.append( "\nNAC:\t" + mNetworkStatus.getNAC() );
sb.append( "\nWACN-SYS:\t" + mNetworkStatus.getWACN() );
sb.append( "-" + mNetworkStatus.getSystemID() );
sb.append( " [" );
sb.append( mNetworkStatus.getNetworkCallsign() );
sb.append( "]" );
sb.append( "\nLRA:\t" + mNetworkStatus.getLocationRegistrationArea() );
}
else if( mNetworkStatusExtended != null )
{
sb.append( "\nNAC:\t" + mNetworkStatusExtended.getNAC() );
sb.append( "\nWACN-SYS:\t" + mNetworkStatusExtended.getWACN() );
sb.append( "-" + mNetworkStatusExtended.getSystemID() );
sb.append( " [" );
sb.append( mNetworkStatusExtended.getNetworkCallsign() );
sb.append( "]" );
sb.append( "\nLRA:\t" + mNetworkStatusExtended.getLocationRegistrationArea() );
}
String site = null;
if( mRFSSStatusMessage != null )
{
site = mRFSSStatusMessage.getRFSubsystemID() + "-" + mRFSSStatusMessage.getSiteID();
}
else if( mRFSSStatusMessageExtended != null )
{
site = mRFSSStatusMessageExtended.getRFSubsystemID() + "-" +
mRFSSStatusMessageExtended.getSiteID();
}
sb.append( "\nRFSS-SITE:\t" + site );
if( mAliasList != null )
{
Alias siteAlias = mAliasList.getSiteID( site );
if( siteAlias != null )
{
sb.append( " " + siteAlias.getName() );
}
}
if( mNetworkStatus != null )
{
sb.append( "\nSERVICES:\t" + SystemService.toString(
mNetworkStatus.getSystemServiceClass() ) );
sb.append( "\nPCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)mNetworkStatus.getDownlinkFrequency() / 1E6d ) +
" [" + mNetworkStatus.getIdentifier() + "-" +
mNetworkStatus.getChannel() + "]\n" );
sb.append( "\tUPLINK " + mFrequencyFormatter.format(
(double)mNetworkStatus.getUplinkFrequency() / 1E6d ) + " [" +
mNetworkStatus.getIdentifier() + "-" +
mNetworkStatus.getChannel() + "]\n" );
}
else if( mNetworkStatusExtended != null )
{
sb.append( "\nSERVICES:\t" + SystemService.toString(
mNetworkStatusExtended.getSystemServiceClass() ) );
sb.append( "\nPCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)mNetworkStatusExtended.getDownlinkFrequency() / 1E6d ) +
" [" + mNetworkStatusExtended.getTransmitIdentifier() + "-" +
mNetworkStatusExtended.getTransmitChannel() + "]\n" );
sb.append( "\tUPLINK " + mFrequencyFormatter.format(
(double)mNetworkStatusExtended.getUplinkFrequency() / 1E6d ) +
" [" + mNetworkStatusExtended.getReceiveIdentifier() + "-" +
mNetworkStatusExtended.getReceiveChannel() + "]" );
}
if( mSecondaryControlChannels.isEmpty() )
{
sb.append( "\nSCCH:\tNONE" );
}
else
{
for( SecondaryControlChannelBroadcast sec: mSecondaryControlChannels )
{
sb.append( "\nSCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)sec.getDownlinkFrequency1() / 1E6d ) +
" [" + sec.getIdentifier1() + "-" + sec.getChannel1() + "]\n" );
sb.append( "\tUPLINK " + mFrequencyFormatter.format(
(double)sec.getUplinkFrequency1() / 1E6d ) + " [" +
sec.getIdentifier1() + "-" + sec.getChannel1() + "]\n" );
if( sec.hasChannel2() )
{
sb.append( "\nSCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)sec.getDownlinkFrequency2() / 1E6d ) +
" [" + sec.getIdentifier2() + "-" + sec.getChannel2() + "]\n" );
sb.append( "\tUPLINK " + mFrequencyFormatter.format(
(double)sec.getUplinkFrequency2() / 1E6d ) + " [" +
sec.getIdentifier2() + "-" + sec.getChannel2() + "]" );
}
}
}
if( mSNDCPDataChannel != null )
{
sb.append( "\nSNDCP:" );
sb.append( "\tDNLINK " + mFrequencyFormatter.format(
(double)mSNDCPDataChannel.getDownlinkFrequency()/1E6D ) +
" [" + mSNDCPDataChannel.getTransmitChannel() + "]" );
sb.append( "\tUPLINK " + mFrequencyFormatter.format(
(double)mSNDCPDataChannel.getUplinkFrequency()/1E6D ) +
" [" + mSNDCPDataChannel.getReceiveChannel() + "]" );
}
if( mProtectionParameterBroadcast != null )
{
sb.append( "\nENCRYPTION:" );
sb.append( "\nTYPE:\t" + mProtectionParameterBroadcast
.getEncryptionType().name() );
sb.append( "\nALGORITHM:\t" + mProtectionParameterBroadcast
.getAlgorithmID() );
sb.append( "\nKEY:\t" + mProtectionParameterBroadcast.getKeyID() );
sb.append( "\nINBOUND IV:\t" + mProtectionParameterBroadcast
.getInboundInitializationVector() );
sb.append( "\nOUTBOUND IV:\t" + mProtectionParameterBroadcast
.getOutboundInitializationVector() );
}
List<Integer> identifiers = new ArrayList( mBands.keySet() );
Collections.sort( identifiers );
sb.append( "\nFREQUENCY BANDS:" );
for( Integer id: identifiers )
{
IBandIdentifier band = mBands.get( id );
sb.append( "\n\t" + id );
sb.append( "- BASE: " + mFrequencyFormatter.format(
(double)band.getBaseFrequency() / 1E6d ) );
sb.append( " CHANNEL SIZE: " + mFrequencyFormatter.format(
(double)band.getChannelSpacing() / 1E6d ) );
sb.append( " UPLINK OFFSET: " + mFrequencyFormatter.format(
(double)band.getTransmitOffset() / 1E6D ) );
}
sb.append( "\n\n=================== NEIGHBORS ======================" );
if( mNeighborMap.isEmpty() )
{
sb.append( "\n\tNONE\n" );
sb.append( "\n----------------------------------------------------" );
}
else
{
for( AdjacentStatusBroadcast neighbor: mNeighborMap.values() )
{
sb.append( "\nNAC:\t" + neighbor.getNAC() );
sb.append( "\nSYSTEM:\t" + neighbor.getSystemID() );
sb.append( "\nLRA:\t" + neighbor.getLocationRegistrationArea() );
String neighborID = neighbor.getRFSS() + "-" + neighbor.getSiteID();
sb.append( "\nRFSS-SITE:\t" + neighborID );
if( mAliasList != null )
{
Alias siteAlias = mAliasList.getSiteID( neighborID );
if( siteAlias != null )
{
sb.append( " " + siteAlias.getName() );
}
}
sb.append( "\nPCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)neighbor.getDownlinkFrequency() / 1E6d ) +
" [" + neighbor.getIdentifier() + "-" + neighbor.getChannel() + "]" );
sb.append( "\n\tUPLINK:" + mFrequencyFormatter.format(
(double)neighbor.getUplinkFrequency() / 1E6d ) + "\n" );
sb.append( "\nSERVICES:\t" + Service.getServices( neighbor.getSystemServiceClass() ) );
sb.append( "\n----------------------------------------------------" );
}
}
return sb.toString();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,406 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.p25;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.ComplexTap;
import instrument.tap.stream.DibitTap;
import instrument.tap.stream.FloatTap;
import instrument.tap.stream.QPSKTap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import source.tuner.frequency.FrequencyCorrectionControl;
import alias.AliasList;
import controller.ResourceManager;
import decode.Decoder;
import decode.DecoderType;
import decode.p25.audio.P25AudioOutput;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.FilterFactory;
import dsp.filter.FloatFIRFilter;
import dsp.filter.Window.WindowType;
import dsp.filter.equalizer.CMAEqualizer;
import dsp.gain.ComplexFeedForwardGainControl;
import dsp.gain.DirectGainControl;
import dsp.nbfm.FMDiscriminator;
import dsp.psk.CQPSKDemodulator;
import dsp.psk.QPSKPolarSlicer;
public class P25Decoder extends Decoder implements Instrumentable
{
private final static Logger mLog = LoggerFactory.getLogger( P25Decoder.class );
private final static int MAXIMUM_FREQUENCY_CORRECTION = 3000; //Hertz, +/-
/* Instrumentation Taps */
private static final String INSTRUMENT_COMPLEX_INPUT = "Tap Point: Complex Input";
private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output";
private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output";
private static final String INSTRUMENT_QPSK_DEMODULATOR_OUTPUT = "Tap Point: CQPSK Demodulator Output";
private static final String INSTRUMENT_CQPSK_SLICER_OUTPUT = "Tap Point: CQPSK Slicer Output";
private static final String INSTRUMENT_REAL_INPUT = "Tap Point: Real Input";
private static final String INSTRUMENT_DGC_OUTPUT = "Tap Point: DGC Output";
private static final String INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT = "Tap Point: Symbol Filter Output";
private static final String INSTRUMENT_C4FM_SLICER_OUTPUT = "Tap Point: C4FM Slicer Output";
private List<Tap> mAvailableTaps;
/* Demods */
private FMDiscriminator mFMDemodulator;
private CQPSKDemodulator mCQPSKDemodulator;
/* Gain */
private ComplexFeedForwardGainControl mAGC;
private DirectGainControl mDGC;
private CMAEqualizer mEqualizer;
/* Filters */
private ComplexFIRFilter mBasebandFilter;
private FloatFIRFilter mAudioFilter;
private C4FMSymbolFilter mSymbolFilter;
/* Slicers */
private C4FMSlicer mC4FMSlicer;
private QPSKPolarSlicer mCQPSKSlicer;
/* Message framers and processors */
private P25MessageFramer mMessageFramer;
private P25MessageProcessor mMessageProcessor;
/* Audio */
private P25AudioOutput mAudioOutput;
private AliasList mAliasList;
private Modulation mModulation;
public P25Decoder( ResourceManager resourceManager,
SampleType sampleType,
Modulation modulation,
AliasList aliasList )
{
super( sampleType );
mModulation = modulation;
mAliasList = aliasList;
mMessageProcessor = new P25MessageProcessor( mAliasList );
mMessageProcessor.addMessageListener( this );
if( modulation == Modulation.CQPSK )
{
/* Provide message framer with reference to the demod so that it
* can detect and issue corrections for costas phase lock errors */
mCQPSKDemodulator = new CQPSKDemodulator();
mMessageFramer = new P25MessageFramer( mAliasList, mCQPSKDemodulator );
}
else
{
mMessageFramer = new P25MessageFramer( mAliasList );
}
mMessageFramer.setListener( mMessageProcessor );
/* Setup demodulation chains based on sample type (real or complex) and
* modulation (C4FM or CQPSK) */
if( mSourceSampleType == SampleType.COMPLEX )
{
if( modulation == Modulation.CQPSK )
{
mBasebandFilter = new ComplexFIRFilter( FilterFactory.getLowPass(
48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0 );
this.addComplexListener( mBasebandFilter );
// mEqualizer = new CMAEqualizer( 1.0f, 0.005f );
// mBasebandFilter.setListener( mEqualizer );
mAGC = new ComplexFeedForwardGainControl( 32 );
// mEqualizer.setListener( mAGC );
mBasebandFilter.setListener( mAGC );
mAGC.setListener( mCQPSKDemodulator );
mCQPSKSlicer = new QPSKPolarSlicer();
mCQPSKDemodulator.setListener( mCQPSKSlicer );
mCQPSKSlicer.addListener( mMessageFramer );
}
else /* C4FM */
{
mBasebandFilter = new ComplexFIRFilter( FilterFactory.getLowPass(
48000, 6750, 7500, 60, WindowType.HANNING, true ), 1.0 );
this.addComplexListener( mBasebandFilter );
mFMDemodulator = new FMDiscriminator( 1.0f );
mBasebandFilter.setListener( mFMDemodulator );
/* Route output of the FM demod back to this channel so that we
* can process the output as if it were coming from any other
* real sample source */
mFMDemodulator.setListener( getRealReceiver() );
}
}
/* Processing chain for real samples & demodulated complex samples */
if( modulation == Modulation.C4FM )
{
mAudioFilter = new FloatFIRFilter( FilterFactory.getLowPass( 48000,
3000, 4000, 48, WindowType.HAMMING, true ), 1.0 );
addRealSampleListener( mAudioFilter );
mDGC = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
mAudioFilter.setListener( mDGC );
mFrequencyCorrection = new FrequencyCorrectionControl(
MAXIMUM_FREQUENCY_CORRECTION );
mSymbolFilter = new C4FMSymbolFilter( mDGC, mFrequencyCorrection );
mDGC.setListener( mSymbolFilter );
mC4FMSlicer = new C4FMSlicer();
mSymbolFilter.setListener( mC4FMSlicer );
mC4FMSlicer.addListener( mMessageFramer );
}
mAudioOutput = new P25AudioOutput( resourceManager );
mMessageProcessor.addMessageListener( mAudioOutput );
}
public P25AudioOutput getAudioOutput()
{
return mAudioOutput;
}
@Override
public DecoderType getType()
{
return DecoderType.P25_PHASE1;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
if( mSourceSampleType == SampleType.COMPLEX )
{
if( mCQPSKDemodulator != null )
{
mAvailableTaps.addAll( mCQPSKDemodulator.getTaps() );
}
// mAvailableTaps.add( new ComplexTap( INSTRUMENT_COMPLEX_INPUT, 0, 1.0f ) );
// mAvailableTaps.add( new ComplexTap( INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f ) );
}
else
{
mAvailableTaps.add( new FloatTap( INSTRUMENT_REAL_INPUT, 0, 1.0f ) );
}
if( mModulation == Modulation.C4FM )
{
mAvailableTaps.add( new FloatTap( INSTRUMENT_DGC_OUTPUT, 0, 1.0f ) );
mAvailableTaps.add( new FloatTap( INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT, 0, 0.1f ) );
mAvailableTaps.add( new DibitTap( INSTRUMENT_C4FM_SLICER_OUTPUT, 0, 0.1f ) );
if( mSymbolFilter != null )
{
mAvailableTaps.addAll( mSymbolFilter.getTaps() );
}
}
else
{
mAvailableTaps.add( new ComplexTap( INSTRUMENT_AGC_OUTPUT, 0, 1.0f ) );
mAvailableTaps.add( new QPSKTap( INSTRUMENT_QPSK_DEMODULATOR_OUTPUT, 0, 1.0f ) );
mAvailableTaps.add( new DibitTap( INSTRUMENT_CQPSK_SLICER_OUTPUT, 0, 0.1f ) );
}
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
if( mSymbolFilter != null )
{
mSymbolFilter.addTap( tap );
}
if( mCQPSKDemodulator != null )
{
mCQPSKDemodulator.addTap( tap );
}
switch( tap.getName() )
{
case INSTRUMENT_COMPLEX_INPUT:
addComplexListener( (ComplexTap)tap );
break;
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
ComplexTap baseband = (ComplexTap)tap;
mBasebandFilter.setListener( baseband );
baseband.setListener( mAGC );
break;
case INSTRUMENT_AGC_OUTPUT:
ComplexTap agcSymbol = (ComplexTap)tap;
mAGC.setListener( agcSymbol );
agcSymbol.setListener( mCQPSKDemodulator );
break;
case INSTRUMENT_QPSK_DEMODULATOR_OUTPUT:
QPSKTap qpsk = (QPSKTap)tap;
mCQPSKDemodulator.setListener( qpsk );
qpsk.setListener( mCQPSKSlicer );
break;
case INSTRUMENT_CQPSK_SLICER_OUTPUT:
mCQPSKSlicer.addListener( (DibitTap)tap );
break;
case INSTRUMENT_REAL_INPUT:
FloatTap inputAGC = (FloatTap)tap;
removeRealListener( mDGC );
addRealSampleListener( inputAGC );
inputAGC.setListener( mDGC );
break;
case INSTRUMENT_DGC_OUTPUT:
FloatTap dgcSymbol = (FloatTap)tap;
mDGC.setListener( dgcSymbol );
dgcSymbol.setListener( mSymbolFilter );
break;
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
FloatTap symbolSlicer = (FloatTap)tap;
mSymbolFilter.setListener( symbolSlicer );
symbolSlicer.setListener( mC4FMSlicer );
break;
case INSTRUMENT_C4FM_SLICER_OUTPUT:
DibitTap slicer = (DibitTap)tap;
if( mC4FMSlicer != null )
{
mC4FMSlicer.addListener( slicer );
}
if( mCQPSKSlicer != null )
{
mCQPSKSlicer.addListener( slicer );
}
break;
}
}
@Override
public void removeTap( Tap tap )
{
if( mSymbolFilter != null )
{
mSymbolFilter.removeTap( tap );
}
if( mCQPSKDemodulator != null )
{
mCQPSKDemodulator.removeTap( tap );
}
switch( tap.getName() )
{
case INSTRUMENT_COMPLEX_INPUT:
removeComplexListener( (ComplexTap)tap );
break;
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
mBasebandFilter.setListener( mAGC );
break;
case INSTRUMENT_AGC_OUTPUT:
mAGC.setListener( mCQPSKDemodulator );
break;
case INSTRUMENT_QPSK_DEMODULATOR_OUTPUT:
mCQPSKDemodulator.setListener( mCQPSKSlicer );
break;
case INSTRUMENT_CQPSK_SLICER_OUTPUT:
mCQPSKSlicer.removeListener( (DibitTap)tap );
break;
case INSTRUMENT_REAL_INPUT:
FloatTap inputAGC = (FloatTap)tap;
removeRealListener( inputAGC );
addRealSampleListener( mDGC );
break;
case INSTRUMENT_DGC_OUTPUT:
mDGC.setListener( mSymbolFilter );
break;
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
mSymbolFilter.setListener( mC4FMSlicer );
break;
case INSTRUMENT_C4FM_SLICER_OUTPUT:
DibitTap slicerTap = (DibitTap)tap;
if( mC4FMSlicer != null )
{
mC4FMSlicer.removeListener( slicerTap );
}
break;
}
}
@Override
public void addUnfilteredRealSampleListener( RealSampleListener listener )
{
throw new IllegalArgumentException( "unfiltered real sample provider "
+ "not implemented in P25 Decoder" );
}
public enum Modulation
{
CQPSK( "LSM SIMULCAST", "LSM" ),
C4FM( "C4FM", "C4FM" );
private String mLabel;
private String mShortLabel;
private Modulation( String label, String shortLabel )
{
mLabel = label;
mShortLabel = shortLabel;
}
public String getLabel()
{
return mLabel;
}
public String getShortLabel()
{
return mShortLabel;
}
public String toString()
{
return getLabel();
}
};
}

View File

@@ -1,273 +0,0 @@
package decode.p25.audio;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Control;
import javax.sound.sampled.Control.Type;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import audio.AudioFormats;
public class IMBETargetDataLine implements TargetDataLine
{
private final static Logger mLog =
LoggerFactory.getLogger( IMBETargetDataLine.class );
public static final int BUFFER_SIZE = 18;
public byte[] mFrame;
/**
* IMBE target dataline provides a java audio system compatible interface to
* ingest and buffer raw IMBE audio frames and expose a standard audio
* system interface for follow-on audio processors to consume the output audio
* stream provided by this interface.
*/
public IMBETargetDataLine()
{
}
@Override
public void open( AudioFormat format, int bufferSize )
throws LineUnavailableException
{
if( format != AudioFormats.IMBE_AUDIO_FORMAT )
{
throw new LineUnavailableException( "Unsupported format" );
}
}
@Override
public void open() throws LineUnavailableException
{
open( AudioFormats.IMBE_AUDIO_FORMAT, BUFFER_SIZE );
}
@Override
public void close()
{
}
@Override
public boolean isOpen()
{
return true;
}
@Override
public void open( AudioFormat format ) throws LineUnavailableException
{
open( format, BUFFER_SIZE );
}
@Override
public int read( byte[] buffer, int offset, int length )
{
if( mFrame != null )
{
System.arraycopy( mFrame, 0, buffer, 0, mFrame.length );
return mFrame.length;
}
return 0;
}
// private int getBytesAvailable()
// {
// return 1920;
// return mFrame.length;
// }
/**
* Primary inject point for submitting IMBE frame data into the audio
* system via this data line
*/
public void receive( byte[] data )
{
mFrame = data;
}
/**
* Not implemented
*/
@Override
public void drain()
{
}
/**
* Flushes (deletes) any buffered audio frames. If a frame is currently
* being processed it will continue and a stop event will be broadcast
* after the current frame has been dispatched.
*/
@Override
public void flush()
{
mFrame = null;
}
/**
* If the line is open, sets the running state to true and attempts to
* retrieve a frame from the buffer. If successful, a line start event is
* broadcast. If no frames are currently buffered, then a line start event
* will be broadcast once the first frame is received.
*
* If the line is closed, or if the running state is already set to true,
* then the start() is ignored.
*/
@Override
public void start()
{
}
/**
* Stops this data line, sets running and active states to false and purges
* any currently buffered data frames. Note: the line will remain open
* until close() is invoked and incoming frame data will continue to buffer.
*/
@Override
public void stop()
{
}
/**
* Indicates if this line us currently opened, but does not reflect if the
* line is currently streaming data. Use the isActive() method to determine
* streaming status.
*/
@Override
public boolean isRunning()
{
return true;
}
/**
* Indicates if this line is currently streaming data
*/
@Override
public boolean isActive()
{
return true;
}
/**
* IMBE AudioFormat produced by this target data line.
*/
@Override
public AudioFormat getFormat()
{
return AudioFormats.IMBE_AUDIO_FORMAT;
}
/**
* Size of the internal buffer in bytes. This method will not return the
* true size of the buffer if this data line was constructed with a buffer
* size other than the default (250 frames) buffer size.
*/
@Override
public int getBufferSize()
{
return 18;
}
/**
* Number of bytes of imbe frame data available. Value will be the number
* of buffered frames times 18 bytes.
*/
@Override
public int available()
{
return 18;
}
/**
* Current number of frames provided by this data line since it was opened.
* Counter rolls over once it exceeds the max integer value.
*/
@Override
public int getFramePosition()
{
return 0;
}
/**
* Current number of frames provided by this data line since it was opened
*/
@Override
public long getLongFramePosition()
{
return 0;
}
/**
* Number of microseconds worth of data that has been sourced by this
* target data line. Returns the frame counter times 20,000 microseconds.
*/
@Override
public long getMicrosecondPosition()
{
return 0;
}
/**
* Not implemented
*/
@Override
public float getLevel()
{
return AudioSystem.NOT_SPECIFIED;
}
@Override
public javax.sound.sampled.Line.Info getLineInfo()
{
return new DataLine.Info( IMBETargetDataLine.class,
AudioFormats.IMBE_AUDIO_FORMAT );
}
/**
* No controls are implemented for this data line
*/
@Override
public Control[] getControls()
{
return null;
}
/**
* No controls are implemented for this data line
*/
@Override
public boolean isControlSupported( Type control )
{
return false;
}
/**
* No controls are implemented for this data line
*/
@Override
public Control getControl( Type control )
{
return null;
}
@Override
public void addLineListener( LineListener listener )
{
// TODO Auto-generated method stub
}
@Override
public void removeLineListener( LineListener listener )
{
// TODO Auto-generated method stub
}
}

View File

@@ -1,50 +0,0 @@
package decode.p25.audio;
import java.util.Iterator;
import java.util.ServiceLoader;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.spi.FormatConversionProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import audio.AudioFormats;
public class P25AudioCodecTest
{
private final static Logger mLog = LoggerFactory.getLogger( P25AudioCodecTest.class );
public static void main( String[] args )
{
mLog.debug( "Starting ..." );
AudioFormat from = AudioFormats.IMBE_AUDIO_FORMAT;
AudioFormat to = AudioFormats.PCM_SIGNED_8KHZ_16BITS;
ServiceLoader<FormatConversionProvider> loader = ServiceLoader.load( FormatConversionProvider.class );
Iterator<FormatConversionProvider> it = loader.iterator();
while( it.hasNext() )
{
mLog.debug( "Provider:" + it.next().getClass() );
}
// try
// {
// TargetDataLine tdl = AudioSystem.getTargetDataLine( from );
//
// mLog.debug( "We got a tdl ..." );
// }
// catch ( Exception e )
// {
// e.printStackTrace();
// }
mLog.debug( "Finished" );
}
}

View File

@@ -1,154 +0,0 @@
package decode.p25.audio;
import jmbe.iface.AudioConversionLibrary;
import jmbe.iface.AudioConverter;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import audio.AudioFormats;
import audio.AudioPacket;
import audio.IAudioOutput;
import audio.MonoAudioOutput;
import audio.SquelchListener;
import audio.inverted.AudioType;
import audio.inverted.IAudioTypeListener;
import controller.ResourceManager;
import decode.p25.message.ldu.LDUMessage;
import decode.p25.message.tdu.TDUMessage;
import decode.p25.message.tdu.lc.TDULinkControlMessage;
/**
* P25 Phase 1 IMBE Audio Output converter. Processes 18-byte IMBE audio frames
* and produces PCM 48k MONO 16-bit sample output.
*
* Note: this class depends on the external JMBE library.
*/
public class P25AudioOutput implements IAudioOutput, Listener<Message>,
SquelchListener, IAudioTypeListener
{
private final static Logger mLog = LoggerFactory.getLogger( P25AudioOutput.class );
private static final String IMBE_CODEC = "IMBE";
private boolean mCanConvertAudio = false;
private boolean mEncryptedAudio = false;
private AudioConverter mAudioConverter;
private MonoAudioOutput mAudioOutput = new MonoAudioOutput();
public P25AudioOutput( ResourceManager resourceManager )
{
loadConverter();
}
/**
* Primary inject point for p25 imbe audio frame messages. Each LDU audio
* message contains 9 imbe voice frames. If the audio is not encrypted,
* we insert each audio frame asynchronously into the audio converter.
*/
public void receive( Message message )
{
if( mCanConvertAudio )
{
if( message instanceof LDUMessage )
{
LDUMessage ldu = (LDUMessage)message;
/* If we detect a valid LDU message with the encryption flag
* set to true, we toggle the encrypted audio state until we
* receive the first TDU or TDULC message, then reset it. */
if( ldu.isValid() && ldu.isEncrypted() )
{
mEncryptedAudio = true;
}
if( !mEncryptedAudio && mAudioConverter != null )
{
for( byte[] frame: ((LDUMessage)message).getIMBEFrames() )
{
byte[] audio = mAudioConverter.convert( frame );
mAudioOutput.receive( new AudioPacket( "P25 Audio", audio, 1 ) );
}
}
}
else if( message instanceof TDUMessage || message instanceof TDULinkControlMessage )
{
mEncryptedAudio = false;
}
}
}
/**
* Loads audio frame processing chain. Constructs an imbe targetdataline
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
*/
private void loadConverter()
{
AudioConversionLibrary library = null;
try
{
@SuppressWarnings( "rawtypes" )
Class temp = Class.forName( "jmbe.JMBEAudioLibrary" );
library = (AudioConversionLibrary)temp.newInstance();
mAudioConverter = library.getAudioConverter( IMBE_CODEC,
AudioFormats.PCM_SIGNED_48KHZ_16BITS );
if( mAudioConverter != null )
{
mCanConvertAudio = true;
mLog.info( "JMBE audio conversion library successfully loaded"
+ " - P25 audio will be available" );
}
else
{
mLog.info( "JMBE audio conversion library NOT FOUND" );
}
}
catch ( ClassNotFoundException e1 )
{
mLog.error( "Couldn't find/load JMBE audio conversion library" );
}
catch ( InstantiationException e1 )
{
mLog.error( "Couldn't instantiate JMBE audio conversion library class" );
}
catch ( IllegalAccessException e1 )
{
mLog.error( "Couldn't load JMBE audio conversion library due to "
+ "security restrictions" );
}
}
@Override
public void setAudioPlaybackEnabled( boolean enabled )
{
mAudioOutput.setMuted( !enabled );
}
@Override
public void dispose()
{
mAudioOutput.dispose();
}
@Override
public void setSquelch( SquelchState state )
{
/* not implemented */
}
@Override
public void setAudioType( AudioType type )
{
//Not implemented
}
}

View File

@@ -1,233 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.passport;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class PassportActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
private AliasList mAliasList;
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<String> mMobileIDs = new TreeSet<String>();
private HashMap<Integer,String> mSiteLCNs = new HashMap<Integer,String>();
private HashMap<Integer,String> mNeighborLCNs = new HashMap<Integer,String>();
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public PassportActivitySummary( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( Message message )
{
if( message instanceof PassportMessage )
{
PassportMessage passport = ((PassportMessage)message);
if( passport.isValid() )
{
switch( passport.getMessageType() )
{
case CA_STRT:
mSiteLCNs.put( passport.getLCN(),
passport.getLCNFrequencyFormatted() );
case CA_ENDD:
String talkgroup =
String.valueOf( passport.getTalkgroupID() );
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
break;
case ID_RDIO:
String min = passport.getMobileID();
if( min != null )
{
mMobileIDs.add( min );
}
break;
case SY_IDLE:
if( passport.getFree() != 0 )
{
mNeighborLCNs.put( passport.getFree(),
passport.getFreeFrequencyFormatted() );
}
break;
default:
break;
}
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "Decoder:\tPassport\n\n" );
sb.append( "Site Channels\n" );
if( mSiteLCNs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
ArrayList<Integer> channels = new ArrayList( mSiteLCNs.keySet() );
Collections.sort( channels );
for( Integer channel: channels )
{
sb.append( " " + channel );
sb.append( "\t" + mSiteLCNs.get( channel ) );
sb.append( "\n" );
}
}
sb.append( "\nNeighbor Channels\n" );
if( mNeighborLCNs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
ArrayList<Integer> channels = new ArrayList( mNeighborLCNs.keySet() );
Collections.sort( channels );
for( Integer channel: channels )
{
sb.append( " " + channel );
sb.append( "\t" + mNeighborLCNs.get( channel ) );
sb.append( "\n" );
}
}
sb.append( "\nTalkgroups\n" );
if( mTalkgroups.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mTalkgroups.iterator();
while( it.hasNext() )
{
String tgid = it.next();
sb.append( " " );
sb.append( tgid );
sb.append( " " );
Alias alias = mAliasList.getTalkgroupAlias( tgid );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
sb.append( "\nMobile ID Numbers\n" );
if( mMobileIDs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mMobileIDs.iterator();
while( it.hasNext() )
{
String min = it.next();
sb.append( " " );
sb.append( min );
sb.append( " " );
Alias alias = mAliasList.getMobileIDNumberAlias( min );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
return sb.toString();
}
public static String formatTalkgroup( String talkgroup )
{
StringBuilder sb = new StringBuilder();
if( talkgroup.length() == 6 )
{
sb.append( talkgroup.substring( 0, 1 ) );
sb.append( "-" );
sb.append( talkgroup.substring( 1, 3 ) );
sb.append( "-" );
sb.append( talkgroup.substring( 3, 6 ) );
return sb.toString();
}
else
{
return talkgroup;
}
}
}

View File

@@ -1,369 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.passport;
import gui.SDRTrunk;
import java.util.HashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import message.Message;
import alias.Alias;
import alias.AliasList;
import audio.SquelchListener;
import audio.inverted.AudioType;
import controller.activity.CallEvent;
import controller.activity.CallEvent.CallEventType;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
public class PassportChannelState extends ChannelState
{
private final static Logger mLog =
LoggerFactory.getLogger( PassportChannelState.class );
private String mTalkgroup;
private Alias mTalkgroupAlias;
private String mMobileID;
private Alias mMobileIDAlias;
private int mChannelNumber;
private int mSiteNumber;
private PassportBand mSiteBand;
private PassportActivitySummary mActivitySummary;
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
public PassportChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
mActivitySummary = new PassportActivitySummary( aliasList );
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( mActivitySummary.getSummary() );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
@Override
@SuppressWarnings( "incomplete-switch" )
public void receive( Message message )
{
super.receive( message );
if( message instanceof PassportMessage )
{
mActivitySummary.receive( message );
PassportMessage passport = (PassportMessage)message;
if( passport.isValid() )
{
switch( passport.getMessageType() )
{
case CA_STRT:
if( mChannelNumber == 0 )
{
mChannelNumber = passport.getLCN();
}
if( passport.getLCN() == mChannelNumber )
{
setTalkgroup( String.valueOf( passport.getTalkgroupID() ) );
setTalkgroupAlias( passport.getTalkgroupIDAlias() );
if( mTalkgroupAlias != null )
{
try
{
if( mTalkgroupAlias.hasAudioType() )
{
mAudioTypeListener.setAudioType(
mTalkgroupAlias.getAudioType() );
}
}
catch( Exception e )
{
mLog.error( "Error setting audio for "
+ "passport call", e );
}
}
PassportCallEvent current =
getCurrentPassportCallEvent();
/* If we're already in a call event, add the message
* to the current call event ... if false, then we
* have a different call ... cleanup the old one. */
if( current != null && !current.addMessage( passport ) )
{
if( current.isValid() )
{
final PassportCallEvent endEvent =
getCurrentPassportCallEvent();
mCallEventModel.setEnd( endEvent );
}
else
{
mCallEventModel.remove( current );
}
current = null;
setCurrentCallEvent( null );
}
if( current == null )
{
mCurrentCallEvent = new PassportCallEvent
.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( String.valueOf( mChannelNumber ) )
.frequency( passport.getLCNFrequency() )
.to( String.valueOf( passport.getTalkgroupID() ) )
.build();
mCallEventModel.add( mCurrentCallEvent );
}
State state = getState();
if( state == State.IDLE || state == State.CALL )
{
setState( State.CALL );
}
else if( state == State.DATA )
{
setState( State.DATA );
}
}
else
{
//Call Detection
int lcn = passport.getLCN();
String tg = String.valueOf( passport.getTalkgroupID() );
if( !mActiveCalls.containsKey( lcn ) ||
!mActiveCalls.get( lcn ).contentEquals( tg ) )
{
mActiveCalls.put( lcn, tg );
PassportCallEvent event = new PassportCallEvent
.Builder( CallEventType.CALL_DETECT )
.aliasList( mAliasList )
.channel( String.valueOf( lcn ) )
.details( "Site: " + passport.getSite() )
.frequency( passport.getLCNFrequency() )
.to( tg )
.build();
mCallEventModel.add( event );
}
}
break;
case DA_STRT:
//TODO: add channel number checking and log a call event if on another site
setTalkgroup( String.valueOf( passport.getTalkgroupID() ) );
setTalkgroupAlias( passport.getTalkgroupIDAlias() );
setState( State.DATA );
break;
case CA_ENDD:
setTalkgroup( String.valueOf( passport.getTalkgroupID() ) );
setTalkgroupAlias( passport.getTalkgroupIDAlias() );
fade( CallEventType.CALL_END );
break;
case ID_RDIO:
setMobileID( passport.getMobileID() );
setMobileIDAlias( passport.getMobileIDAlias() );
final String mid = passport.getMobileID();
final PassportCallEvent event =
getCurrentPassportCallEvent();
if( event != null && mid != null )
{
mCallEventModel.setFromID( event, mid );
}
setState( State.CALL );
break;
case RA_REGI:
mCallEventModel.add( new PassportCallEvent
.Builder( CallEventType.REGISTER )
.aliasList( mAliasList )
.channel( String.valueOf( passport.getLCN() ) )
.frequency( passport.getLCNFrequency() )
.to( passport.getToID() )
.build() );
//TODO: add logic to recognize the register event and suppress the subsequent
//call messages, but capture the group assignments.
setState( State.DATA );
break;
case SY_IDLE:
PassportBand band = passport.getSiteBand();
if( mSiteBand == null || mSiteBand != band )
{
mSiteBand = band;
mSiteNumber = passport.getSite();
long frequency = mProcessingChain.getChannel()
.getTunerChannel().getFrequency();
mChannelNumber = mSiteBand.getChannel( frequency );
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
}
default:
break;
}
}
}
}
/**
* Intercept the fade event so that we can generate a call end event
*/
@Override
public void fade( final CallEventType type )
{
/*
* We can receive multiple call tear-down messages -- only post a call
* end event for the one that can change the state to fade
*/
if( getState().canChangeTo( State.FADE ) )
{
final PassportCallEvent current = getCurrentPassportCallEvent();
if( current != null )
{
mCallEventModel.setEnd( current );
}
}
setCurrentCallEvent( null );
mActiveCalls.clear();
super.fade( type );
}
public PassportCallEvent getCurrentPassportCallEvent()
{
CallEvent current = getCurrentCallEvent();
if( current != null )
{
return (PassportCallEvent)current;
}
return null;
}
/**
* Make the ConventionalChannelState always unsquelched
*/
public void addListener( SquelchListener listener )
{
super.addListener( listener );
}
public void reset()
{
mAudioTypeListener.setAudioType( AudioType.NORMAL );
mCurrentCallEvent = null;
setTalkgroup( null );
setTalkgroupAlias( null );
setMobileID( null );
setMobileIDAlias( null );
super.reset();
}
public int getChannelNumber()
{
return mChannelNumber;
}
public int getSiteNumber()
{
return mSiteNumber;
}
public String getTalkgroup()
{
return mTalkgroup;
}
public void setTalkgroup( String talkgroup )
{
mTalkgroup = talkgroup;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
}
public Alias getTalkgroupAlias()
{
return mTalkgroupAlias;
}
public void setTalkgroupAlias( Alias alias )
{
mTalkgroupAlias = alias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getMobileID()
{
return mMobileID;
}
public void setMobileID( String id )
{
mMobileID = id;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getMobileIDAlias()
{
return mMobileIDAlias;
}
public void setMobileIDAlias( Alias alias )
{
mMobileIDAlias = alias;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
}

View File

@@ -1,165 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package decode.passport;
import instrument.Instrumentable;
import instrument.tap.Tap;
import java.util.ArrayList;
import java.util.List;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
import audio.AudioOutputImpl;
import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import decode.config.DecodeConfiguration;
import dsp.filter.DCRemovalFilter2;
import dsp.fsk.LTRFSKDecoder;
import dsp.nbfm.FilteringNBFMDemodulator;
public class PassportDecoder extends Decoder implements Instrumentable
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double sDC_REMOVAL_RATIO = 0.000003;
public static final int sPASSPORT_MESSAGE_LENGTH = 68;
public static final int sPASSPORT_SYNC_LENGTH = 9;
private FilteringNBFMDemodulator mNBFMDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
private LTRFSKDecoder mPassportFSKDecoder;
private MessageFramer mPassportMessageFramer;
private PassportMessageProcessor mPassportMessageProcessor;
private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "Passport Decoder Audio Output" );
private List<Tap> mAvailableTaps;
public PassportDecoder( DecodeConfiguration config,
SampleType sampleType,
AliasList aliasList )
{
super( config, sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
/**
* The Decoder super class is both a Complex listener and a Short
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
mNBFMDemodulator = new FilteringNBFMDemodulator();
this.addComplexListener( mNBFMDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO );
mNBFMDemodulator.addListener( mDCRemovalFilter );
mDCRemovalFilter.setListener( this.getRealReceiver() );
}
mPassportFSKDecoder =
new LTRFSKDecoder();
/**
* The DC removal filter will impact performance of the LTRFSKDemod,
* so we get the samples straight from the NBFM demod
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
mNBFMDemodulator.addListener( mPassportFSKDecoder );
}
else
{
addRealSampleListener( mPassportFSKDecoder );
}
mPassportMessageFramer =
new MessageFramer( SyncPattern.PASSPORT.getPattern(),
sPASSPORT_MESSAGE_LENGTH );
mPassportFSKDecoder.addListener( mPassportMessageFramer );
mPassportMessageProcessor = new PassportMessageProcessor( aliasList );
mPassportMessageFramer.addMessageListener( mPassportMessageProcessor );
mPassportMessageProcessor.addMessageListener( this );
addRealSampleListener( mAudioOutput );
}
@Override
public DecoderType getType()
{
return DecoderType.PASSPORT;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.addAll( mPassportFSKDecoder.getTaps() );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
mPassportFSKDecoder.addTap( tap );
}
@Override
public void removeTap( Tap tap )
{
mPassportFSKDecoder.removeTap( tap );
}
@Override
public void addUnfilteredRealSampleListener( RealSampleListener listener )
{
if( mNBFMDemodulator != null )
{
mNBFMDemodulator.addListener( listener );
}
}
@Override
public IAudioOutput getAudioOutput()
{
return mAudioOutput;
}
}

View File

@@ -1,35 +0,0 @@
package decode.tait;
import java.util.Collections;
import java.util.List;
import message.Message;
import filter.Filter;
import filter.FilterElement;
public class Tait1200MessageFilter extends Filter<Message>
{
public Tait1200MessageFilter()
{
super( "Tait-1200 Message Filter" );
}
@Override
public boolean passes( Message message )
{
return mEnabled && canProcess( message );
}
@Override
public boolean canProcess( Message message )
{
return message instanceof Tait1200GPSMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

Some files were not shown because too many files have changed in this diff Show More