Compare commits

...

57 Commits

Author SHA1 Message Date
Denny
d95c78c0f1 Updates version to 0.4.0 alpha 5 2018-09-23 05:51:28 -04:00
Denny Sheirer
aee5ece8d2 Merge pull request #430 from DSheirer/429-polyphase-channelizer-25khz-channel-rate
429 polyphase channelizer 25khz channel rate
2018-09-23 05:50:18 -04:00
Denny
ba358a7d48 Adjusted costas loop gain values. Updated channel filter values to mitigate adjacent channel interference. 2018-09-23 05:34:41 -04:00
Denny
09b209496b Resolves issue with NPE in 3/4 rate decoder. 2018-09-22 09:19:15 -04:00
Denny Sheirer
cc209683bd Merge pull request #428 from DSheirer/377-npe-in-trellis-decoder
377 NPE in P25 3/4 Rate Trellis Decoder
2018-09-22 02:53:16 -04:00
Denny
2771c28dba Resolves issue with NPE in 3/4 rate decoder. 2018-09-22 02:52:36 -04:00
Denny
d752917073 Resolves #427. Moves the module(s) reset operation to the stop() method
so that the modules are reset after they are shut down, in order to
prepare for processing chain reuse.

Previously, the modules were reset upon startup, but after any traffic
channel metadata had already been transferred to the channel state,
causing the metadata to be lost and allowing initial audio packets to
stream without any metadata and related streaming/monitoring rules.
2018-09-20 04:01:45 -04:00
Denny
28bc02e172 Updates channel state to ensure teardown processing is completed during
a call end and/or during a commanded stop.
2018-09-19 04:25:37 -04:00
Denny
0f6044d48b Updates mutable metadata to reset the buffer overflow flag between uses. 2018-09-18 19:15:06 -04:00
Denny Sheirer
58ecb83e2c Merge pull request #425 from DSheirer/417-channel-overflow-state-processing
417 Channel Overflow State Processing - Part 2
2018-09-16 10:23:15 -04:00
Denny
9e1de922e4 Resolves issue with overflow state processing. Displays the overflow
state in the Message field of the channel metadata table column instead
of displaying it in the channel state column.
2018-09-16 10:22:27 -04:00
Denny Sheirer
84d0b827d8 Merge pull request #422 from DSheirer/421-nbfm-25-khz-channel-bandwidth
421 Add 25 kHz Channel Bandwidth Support to NBFM Decoder
2018-09-16 07:22:38 -04:00
Denny
a17132be0d Updates the NBFM decoder to allow user to select between 12.5 and 25.0
kHz channel bandwidth so that 25 kHz channels can be demodulated correctly.
2018-09-16 07:21:14 -04:00
Denny Sheirer
dae68515a5 Merge pull request #420 from DSheirer/419-heterdyne-channelizer-center-frequency-tuning
419 Heterodyne Channelizer Center Frequency Calculations
2018-09-16 06:04:09 -04:00
Denny
0aaec8ff54 Updates the heterodyne channelizer's center frequency calculator to
only update the center frequency when needed and to place the set of
decoding channels adjacent to the right side of the tuner's center
frequency when the complete set of channels fit into the usable half
bandwidth of the tuner.
2018-09-16 06:02:46 -04:00
Denny Sheirer
001883edc2 Merge pull request #418 from DSheirer/417-channel-overflow-zombie-state
417 Traffic Channels in Buffer Overflow state do not tear down correctly
2018-09-16 05:23:05 -04:00
Denny
335fb51456 Updates buffer overflow handling to occur coincident to other channel
states rather than being a channel state itself.  This was preventing
traffic channels that overflow from being torn down correctly.
2018-09-16 05:21:46 -04:00
Denny Sheirer
e94b6ae172 Merge pull request #416 from DSheirer/415-overflow-state
415 Buffer Overflow State Listener Registration
2018-09-14 06:46:34 -04:00
Denny
785125da4f Correctly implements the buffer overflow listener registration so that
decoding channels can be notified when the internal buffer goes into
and out of overflow state.
2018-09-14 06:45:37 -04:00
Denny
81b8f5a423 Updates build to version 0.4.0 alpha 4 2018-09-12 06:23:09 -04:00
Denny Sheirer
bf186a7408 Merge pull request #414 from DSheirer/405-airspy-tuner-waterfall-freeze
405 Airspy Spectrum/Waterfall Freezes
2018-09-12 06:20:36 -04:00
Denny
43fc326f93 Resolves #405
Adds 500 ms delay to initial registering for samples from all tuners to
feed the spectral display/waterfall.  The existing approach of immediately
registering for samples was causing the airspy tuner to lockup
aperiodically.  Investigation via debugger didn't show any application
thread locks ... usb master processor was continuing to service libusb
timeouts.  Unable to debug internal airspy processes.  Adding the delay
to startup seems to be a usable solution.
2018-09-12 06:19:11 -04:00
Denny Sheirer
7cdbb75b42 Merge pull request #413 from DSheirer/375-poor-p25-decode-performance
375 P25 Decode Performance
2018-09-10 04:22:40 -04:00
Denny
96a56cab68 Resolves #375.
Reverts PLL gain values to version 0.3.4beta2 values to restore P25
decoder performance.

This change in conjuction with recent channel sample rate change should
restore P25 channel decoding to comparable levels from 0.3.4b2.
2018-09-09 09:55:40 -04:00
Denny Sheirer
29d4b36cf7 Merge pull request #412 from DSheirer/411-variable-channel-sample-rates
#411 Update Tuner Source Manager to provide variable sample rate channels
2018-09-09 09:50:11 -04:00
Denny
b15a3e59a0 Resolves #411
Updates decoder types to specify channel sample rate and pass/stop
frequencies for requested channels.  Updates tuner channel source
managers to accept a channel specification for channels that are
allocated.
2018-09-09 09:49:05 -04:00
Denny Sheirer
9950e46e5a Merge pull request #408 from DSheirer/406-tuner-sample-delay-buffer
406 Tuner Sample Delay Buffer
2018-09-09 08:47:17 -04:00
Denny
570321da3e Implements a complex sample delay buffer integrated into the tuner
channel manager so that channel source requests can have a start
timestamp specified and the channel can be preloaded with samples from
the delay buffer.  If no timestamp is specified, current time is assumed.

This feature requires corresponding updates to any decoder that wishes
to implement the timestamped sample preloading functionality.
2018-09-09 08:46:21 -04:00
Denny
bb0bcd3198 Updates build to 0.4.0 alpha 3 2018-09-05 07:34:09 -04:00
Denny Sheirer
cb54850f99 Merge pull request #402 from DSheirer/heterodyne_channelizer
401 Update Tuners to use Heterodyne Channelizer
2018-09-05 07:31:57 -04:00
Denny
5bb8c46bd5 Merge remote-tracking branch 'origin/heterodyne_channelizer' into heterodyne_channelizer 2018-09-05 07:27:39 -04:00
Denny
2a3fd2c5a3 Class renaming and cleanup. 2018-09-05 07:27:18 -04:00
Denny
d712feb45a Class renaming and cleanup. 2018-09-05 07:26:52 -04:00
Denny
fbb24a4c05 Class renaming and cleanup. 2018-09-05 07:24:42 -04:00
Denny
3992f8178c Merge remote-tracking branch 'origin/heterodyne_channelizer' into heterodyne_channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
2018-09-05 07:19:37 -04:00
Denny
8642def77e Cleanup, renaming and removing legacy code. 2018-09-05 07:17:51 -04:00
Denny
38e6de6cb2 Resolves cutoff frequency for 2 channel polyphase synthesizer. 2018-09-05 07:17:51 -04:00
Denny
994e78f42b Resolves issue with polyphase filter gain and channel output processor gain 2018-09-05 07:16:51 -04:00
Denny
b8a46af836 Creates new HeterodyneChannelSourceManager that uses frequency translation
and CIC decimation for each channel provided by the tuner.
2018-09-05 07:15:57 -04:00
Denny
1c6d19959a Class renaming and cleanup. 2018-09-05 06:34:13 -04:00
Denny
50bc3088b7 Removes unused legacy code. 2018-09-05 06:31:39 -04:00
Denny
09356848eb Merge branch 'master' into heterodyne_channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
2018-09-05 06:18:27 -04:00
Denny Sheirer
316bfb19c2 Merge pull request #400 from DSheirer/375-channelizer-synthesizer-gain-issue
375 channelizer synthesizer gain issue
2018-09-05 06:17:47 -04:00
Denny
c045c8f16d Updates heterodyne channel manager to correctly lock/unlock the tuner
controller.
2018-09-05 06:11:28 -04:00
Denny
8946d789ff WIP - updated tuners to use CIC decimation tuner channel sources instead
of polyphase channelizer.

Channel sample rate reduced to 30 kHz - seems to work better with P25
decoders than previous 25 kHz.
2018-09-04 08:16:57 -04:00
Denny
19399d9138 Resolves cutoff frequency for 2 channel polyphase synthesizer. 2018-09-01 06:32:24 -04:00
Denny
9acdf22b91 WIP - identifier framework and restructuring P25 messages to provide
identifiers.
2018-08-31 04:50:58 -04:00
Denny Sheirer
f963d821a1 Merge pull request #396 from DSheirer/394-p25-bitstream-output
394 P25 bit stream output
2018-08-18 03:52:36 -04:00
Denny
ade0cef50b Updates P25 C4FM and LSM decoders to provide demodulated bit streams
for recording using the new binary recorders/format.
2018-08-18 03:36:17 -04:00
Denny
2044c2a2de WIP - Updated P25 C4FM and LSM decoders to assemble dibits and push
reusable byte buffers out to the processing chain.  This enables the
processing chain to broadcast these buffers back to the decoder for
message assembly and to any running binary recorder.

Updated P25Decoder to be a reusable byte buffer listener.  The decoder
will now pass reusable byte buffers down to the message framer for
message processing.  This should make the code more efficient since it
can process byte arrays versus handling each individual dibit.

The P25MessageFramer has been updated to be a listener of reusable byte
buffers instead of Dibits, but the actual processing of byte arrays
has to be coded.

Added max recording size to the binary recorder so that it cycles at
500 kB max file size.
2018-08-17 07:27:04 -04:00
Denny Sheirer
1c312a50b6 Merge pull request #395 from DSheirer/393-raw-bitstream-recording
393 Recording raw decoded bitstream from supported decoder types
2018-08-11 08:09:22 -04:00
Denny
54f6bd09e2 Cleanup 2018-08-11 08:08:57 -04:00
Denny
5f8f2ec330 Resolves #393 - Adds support for recording demodulated (raw) bitstream
buffers.  None of the decoders currently support this yet, but the P25
will be updated to provide bitstream output for recording.

Renames the ReusableBuffer class to ReusableFloatBuffer for
consistency.
2018-08-11 08:02:58 -04:00
Denny Sheirer
d7e018f66e Merge pull request #392 from DSheirer/390-polyphase-channel-source-npe
390 PolyphaseChannelSource NPE
2018-08-11 05:43:35 -04:00
Denny
c2a232fdee Adds null checking to output processor. 2018-08-11 05:42:45 -04:00
Denny Sheirer
c5d2dcd0cf Merge pull request #391 from DSheirer/389-add-active-to-channel-states
389 Add ACTIVE Channel State and Update P25 Decoder State processing
2018-08-11 05:39:20 -04:00
Denny
8e4e4129ca Adds 'ACTIVE' as a valid channel state to indicate that the channel is
actively decoding, but no call or data is being transmitted.  This is
primarily used for P25 channels when TDU Link Control messages are
being transmitted in between voice or data transmissions on a channel.

Updated several of the P25 decoder state message processing sections to
keep the channel state UI updated with current call identifiers and
states.

Hard-coded channel state colors to remove inefficiency in the way that
channel state coloration was being handled.
2018-08-11 05:36:30 -04:00
150 changed files with 3954 additions and 1612 deletions

View File

@@ -15,7 +15,7 @@ repositories {
mavenCentral()
}
version = '0.4.0-alpha.2'
version = '0.4.0-alpha.5'
sourceCompatibility = 1.8
sourceSets {

View File

@@ -27,7 +27,7 @@ import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
* This class is designed to support 8 kHz sample rate demodulated audio.
*/
public class AudioModule extends Module implements IAudioPacketProvider, IReusableBufferListener,
ISquelchStateListener, Listener<ReusableBuffer>
ISquelchStateListener, Listener<ReusableFloatBuffer>
{
protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("AudioModule");
@@ -139,11 +139,11 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH)
{
ReusableBuffer highPassFiltered = mHighPassFilter.filter(reusableBuffer);
ReusableFloatBuffer highPassFiltered = mHighPassFilter.filter(reusableFloatBuffer);
ReusableAudioPacket audioPacket = mAudioPacketQueue.getBuffer(highPassFiltered.getSampleCount());
audioPacket.loadAudioFrom(highPassFiltered);
@@ -155,7 +155,7 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab
}
else
{
reusableBuffer.decrementUserCount();
reusableFloatBuffer.decrementUserCount();
}
}

View File

@@ -20,6 +20,7 @@ package io.github.dsheirer.channel.metadata;
public enum Attribute
{
BUFFER_OVERFLOW,
CHANNEL_CONFIGURATION_NAME,
CHANNEL_CONFIGURATION_SITE,
CHANNEL_CONFIGURATION_SYSTEM,

View File

@@ -116,6 +116,16 @@ public class AttributeChangeRequest<T>
return null;
}
public Boolean getBooleanValue()
{
if(mValue instanceof Boolean)
{
return (Boolean)mValue;
}
return false;
}
/**
* Returns the value as a DecoderType or null if the value is not a DecoderType instance.
*/

View File

@@ -123,6 +123,11 @@ public class AttributeMonitor<T>
{
mCurrentValue = null;
mOccurrenceCounts.clear();
if(mListener != null)
{
mListener.receive(new AttributeChangeRequest(mAttribute, null, null));
}
}
/**

View File

@@ -1,19 +1,16 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.channel.metadata;
@@ -21,9 +18,11 @@ package io.github.dsheirer.channel.metadata;
import io.github.dsheirer.channel.state.State;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
@@ -32,6 +31,8 @@ import java.util.Map;
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadataModel.class);
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" );
public static final int COLUMN_STATE = 0;
@@ -45,7 +46,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
public static final int COLUMN_CONFIGURATION_NAME = 8;
public static final int COLUMN_MESSAGE = 9;
private static final String[] COLUMNS = {"State", "Decoder", "Channel", "Frequency", "Primary From", "Primary To",
private static final String[] COLUMNS = {"Status", "Decoder", "Channel", "Frequency", "Primary From", "Primary To",
"Secondary From", "Secondary To", "Channel Name", "Message"};
private List<MutableMetadata> mChannelMetadata = new ArrayList();
@@ -184,7 +185,11 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
return null;
case COLUMN_MESSAGE:
if(metadata.hasMessageType() | metadata.hasMessage())
if(metadata.isBufferOverflow())
{
return "**OVERFLOW**";
}
else if(metadata.hasMessageType() || metadata.hasMessage())
{
StringBuilder sb = new StringBuilder();
if(metadata.hasMessageType())
@@ -248,6 +253,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
case CHANNEL_STATE:
fireTableCellUpdated(rowIndex, COLUMN_STATE);
break;
case BUFFER_OVERFLOW:
case MESSAGE:
case MESSAGE_TYPE:
fireTableCellUpdated(rowIndex, COLUMN_MESSAGE);

View File

@@ -25,32 +25,36 @@ import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.ChannelUtils;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
public class ChannelMetadataPanel extends JPanel implements ListSelectionListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadataPanel.class);
private static final String PROPERTY_PREFIX_BACKGROUND = "channel.metadata.panel.state.color.";
private static final String PROPERTY_PREFIX_FOREGROUND = "channel.metadata.panel.text.color.";
private ChannelProcessingManager mChannelProcessingManager;
private IconManager mIconManager;
private JTable mTable;
private Broadcaster<ProcessingChain> mSelectedProcessingChainBroadcaster = new Broadcaster<>();
private Map<State,Color> mBackgroundColors = new HashMap<>();
private Map<State,Color> mForegroundColors = new HashMap<>();
/**
* Table view for currently decoding channel metadata
@@ -94,7 +98,32 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
add(scrollPane);
// new TableColumnWidthMonitor(mTable, PROPERTY_PREFIX, new int[] {15,15,15,15,25,25,25,25,15,40});
setColors();
}
/**
* Setup the background and foreground color palette for the various channel states.
*/
private void setColors()
{
mBackgroundColors.put(State.ACTIVE, Color.CYAN);
mForegroundColors.put(State.ACTIVE, Color.YELLOW);
mBackgroundColors.put(State.CALL, Color.BLUE);
mForegroundColors.put(State.CALL, Color.YELLOW);
mBackgroundColors.put(State.CONTROL, Color.ORANGE);
mForegroundColors.put(State.CONTROL, Color.BLUE);
mBackgroundColors.put(State.DATA, Color.GREEN);
mForegroundColors.put(State.DATA, Color.BLUE);
mBackgroundColors.put(State.ENCRYPTED, Color.MAGENTA);
mForegroundColors.put(State.ENCRYPTED, Color.WHITE);
mBackgroundColors.put(State.FADE, Color.LIGHT_GRAY);
mForegroundColors.put(State.FADE, Color.DARK_GRAY);
mBackgroundColors.put(State.IDLE, Color.WHITE);
mForegroundColors.put(State.IDLE, Color.DARK_GRAY);
mBackgroundColors.put(State.RESET, Color.PINK);
mForegroundColors.put(State.RESET, Color.YELLOW);
mBackgroundColors.put(State.TEARDOWN, Color.DARK_GRAY);
mForegroundColors.put(State.TEARDOWN, Color.WHITE);
}
@Override
@@ -185,44 +214,14 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
State state = (State)value;
label.setText(state.getDisplayValue());
switch(state)
if(mBackgroundColors.containsKey(state))
{
case CALL:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "call", Color.BLUE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "call", Color.YELLOW);
break;
case CONTROL:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "control", Color.ORANGE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "control", Color.BLUE);
break;
case DATA:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "data", Color.GREEN);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "data", Color.BLUE);
break;
case ENCRYPTED:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "encrypted", Color.MAGENTA);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "encrypted", Color.WHITE);
break;
case FADE:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "fade", Color.LIGHT_GRAY.BLUE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "fade", Color.YELLOW);
break;
case IDLE:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "idle", Color.WHITE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "idle", Color.BLUE);
break;
case OVERFLOW:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "overflow", Color.RED);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "overflow", Color.YELLOW);
break;
case RESET:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "reset", Color.PINK);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "reset", Color.YELLOW);
break;
case TEARDOWN:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "teardown", Color.DARK_GRAY);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "teardown", Color.WHITE);
break;
background = mBackgroundColors.get(state);
}
if(mForegroundColors.containsKey(state))
{
foreground = mForegroundColors.get(state);
}
}
else

View File

@@ -41,6 +41,7 @@ public class Metadata
protected boolean mUpdated;
protected DecoderType mPrimaryDecoderType;
protected boolean mSelected;
protected boolean mBufferOverflow;
protected State mState = State.IDLE;
protected String mChannelConfigurationSystem;
protected String mChannelConfigurationSite;
@@ -173,6 +174,14 @@ public class Metadata
return mSelected;
}
/**
* Indicates if the channel for this metadata is in a buffer overflow state.
*/
public boolean isBufferOverflow()
{
return mBufferOverflow;
}
/**
* List of de-duplicated broadcast channels aggregated from the primary and secondary TO/FROM aliases.
*/

View File

@@ -80,6 +80,10 @@ public class MutableMetadata extends Metadata implements Listener<AttributeChang
{
switch(request.getAttribute())
{
case BUFFER_OVERFLOW:
mBufferOverflow = request.getBooleanValue();
broadcast(Attribute.BUFFER_OVERFLOW);
break;
case CHANNEL_CONFIGURATION_SYSTEM:
mChannelConfigurationSystem = request.getStringValue();
broadcast(Attribute.CHANNEL_CONFIGURATION_SYSTEM);
@@ -164,6 +168,7 @@ public class MutableMetadata extends Metadata implements Listener<AttributeChang
public void resetTemporalAttributes()
{
mDoNotRecord = null;
mBufferOverflow = false;
if(hasMessage())
{

View File

@@ -135,18 +135,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
@Override
public void stop()
{
mMutableMetadata.resetAllAttributes();
mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null)
{
mTrafficChannelCallEvent.end();
broadcast(mTrafficChannelCallEvent);
}
mTrafficChannelCallEvent = null;
processTeardownState();
mSquelchLocked = false;
}
@@ -303,6 +292,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{
switch(state)
{
case ACTIVE:
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
break;
case CONTROL:
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
if(isStandardChannel())
@@ -339,8 +333,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
break;
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
}
}
@@ -363,8 +356,19 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{
mSourceOverflow = overflow;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
mMutableMetadata.receive(new AttributeChangeRequest<Boolean>(Attribute.BUFFER_OVERFLOW, mSourceOverflow));
}
/**
* Indicates if this channel's sample buffer is in overflow state, meaning that the inbound sample
* stream is not being processed fast enough and samples are being thrown away until the processing can
* catch up.
*
* @return true if the channel is in overflow state.
*/
public boolean isOverflow()
{
return mSourceOverflow;
}
/**
@@ -403,9 +407,13 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
if(mTrafficChannelEndListener != null && mTrafficChannelCallEvent != null)
{
mTrafficChannelCallEvent.end();
broadcast(mTrafficChannelCallEvent);
mTrafficChannelEndListener.callEnd(mTrafficChannelCallEvent.getChannel());
mTrafficChannelEndListener = null;
mTrafficChannelCallEvent = null;
}
}

View File

@@ -24,62 +24,109 @@ import java.util.EnumSet;
*/
public enum State
{
IDLE("IDLE")
/**
* Active state indicates that valid messages are being decoded that do not indicate any call or data
* activity. For example, this is used for P25 when a channel is actively transmitting Terminator Data
* Units in between data packets or at the beginning or end of a call.
*/
ACTIVE("ACTIVE")
{
@Override
public boolean canChangeTo(State state)
{
return state != TEARDOWN && state != RESET && state != OVERFLOW;
return state == CALL ||
state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
/**
* Indicates that call messages are being decoded, or a call is in progress (ie producing audio).
*/
CALL("CALL")
{
@Override
public boolean canChangeTo(State state)
{
return state == CONTROL || state == DATA || state == ENCRYPTED || state == FADE || state == TEARDOWN;
}
},
DATA("DATA")
{
@Override
public boolean canChangeTo(State state)
{
return state == CALL || state == CONTROL || state == ENCRYPTED || state == FADE || state == TEARDOWN;
}
},
ENCRYPTED("ENCRYPTED")
{
@Override
public boolean canChangeTo(State state)
{
return state == FADE || state == TEARDOWN;
return state == ACTIVE ||
state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
/**
* Indicates a trunking control channel is being decoded.
*/
CONTROL("CONTROL")
{
@Override
public boolean canChangeTo(State state)
{
return state == IDLE || state == FADE;
return state == IDLE ||
state == FADE;
}
},
/**
* Indicates data packets are being decoded
*/
DATA("DATA")
{
@Override
public boolean canChangeTo(State state)
{
return state == ACTIVE ||
state == CALL ||
state == CONTROL ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
/**
* Indicates encrypted audio is detected.
*/
ENCRYPTED("ENCRYPTED")
{
@Override
public boolean canChangeTo(State state)
{
return state == FADE ||
state == TEARDOWN;
}
},
/**
* Fade state occurs when the channel is no longer actively decoding an messages. This is a transitory
* state before transition to IDLE or back to an active state.
*/
FADE("FADE")
{
@Override
public boolean canChangeTo(State state)
{
return state != FADE && state != RESET && state != OVERFLOW;
return state != FADE &&
state != RESET;
}
},
OVERFLOW("OVERFLOW")
/**
* Indicates the channel is idle and not actively decoding any messages
*/
IDLE("IDLE")
{
@Override
public boolean canChangeTo(State state)
{
return false;
return state != TEARDOWN &&
state != RESET;
}
},
/**
* Indicates that the channel has been reset and is ready to use for a new decoding session.
*/
RESET("RESET")
{
@Override
@@ -88,6 +135,10 @@ public enum State
return state == IDLE;
}
},
/**
* Call or data teardown. This state is used by traffic channels to indicate that a call or data session
* has concluded and the channel is ready for tear down and reuse.
*/
TEARDOWN("TEARDOWN")
{
@Override
@@ -99,7 +150,7 @@ public enum State
private String mDisplayValue;
public static final EnumSet<State> CALL_STATES = EnumSet.of(CALL, CONTROL, DATA, ENCRYPTED);
public static final EnumSet<State> CALL_STATES = EnumSet.of(ACTIVE, CALL, CONTROL, DATA, ENCRYPTED);
public static final EnumSet<State> IDLE_STATES = EnumSet.of(IDLE, FADE);
private State(String displayValue)
@@ -124,4 +175,10 @@ public enum State
{
return mDisplayValue;
}
@Override
public String toString()
{
return mDisplayValue;
}
}

View File

@@ -186,7 +186,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
if(frequency > 0)
{
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(frequency,
mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
mDecodeConfiguration.getChannelSpecification().getBandwidth()));
if(channel != null)
{

View File

@@ -489,14 +489,14 @@ public class Channel extends Configuration implements Listener<SourceEvent>
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
{
SourceConfigRecording config = (SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
}

View File

@@ -1,3 +1,18 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.controller.channel;
import com.jidesoft.swing.JideSplitPane;
@@ -24,8 +39,7 @@ import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChannelController extends JPanel
implements ActionListener, ListSelectionListener
public class ChannelController extends JPanel implements ActionListener, ListSelectionListener
{
private static final long serialVersionUID = 1L;
@@ -168,9 +182,13 @@ public class ChannelController extends JPanel
if( index >= 0 )
{
int translatedIndex = mChannelTable.convertRowIndexToView( index );
mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
mChannelTable.scrollRectToVisible(
new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
if(translatedIndex >= 0)
{
mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
mChannelTable.scrollRectToVisible(
new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
}
}
}

View File

@@ -33,6 +33,7 @@ import io.github.dsheirer.module.decode.event.MessageActivityModel;
import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.record.RecorderManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.binary.BinaryRecorder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.source.Source;
@@ -211,7 +212,7 @@ public class ChannelProcessingManager implements ChannelEventListener
try
{
source = mSourceManager.getSource(channel.getSourceConfiguration(),
channel.getDecodeConfiguration().getDecoderType().getChannelBandwidth());
channel.getDecodeConfiguration().getChannelSpecification());
}
catch(SourceException se)
{
@@ -308,6 +309,25 @@ public class ChannelProcessingManager implements ChannelEventListener
{
processingChain.addModule(mRecorderManager.getBasebandRecorder(channel.toString()));
}
/* Add decoded bit stream recorder if the decoder supports bitstream output */
if(DecoderFactory.getBitstreamDecoders().contains(channel.getDecodeConfiguration().getDecoderType()))
{
if((recorders.contains(RecorderType.DEMODULATED_BIT_STREAM) &&
channel.getChannelType() == Channel.ChannelType.STANDARD))
{
processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(),
channel.toString()));
}
/* Add traffic channel decoded bit stream recorder */
if(recorders.contains(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM) &&
channel.getChannelType() == Channel.ChannelType.TRAFFIC)
{
processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(),
channel.toString()));
}
}
}
processingChain.setSource(source);

View File

@@ -21,7 +21,7 @@ import io.github.dsheirer.dsp.filter.resample.RealResampler;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory;
*
* Provides normal or inverted decoded output.
*/
public class AFSK1200Decoder implements Listener<ReusableBuffer>
public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class);
@@ -110,7 +110,7 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
*/
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
mResampler.resample(buffer);
}
@@ -142,10 +142,10 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
}
public class Decoder implements Listener<ReusableBuffer>
public class Decoder implements Listener<ReusableFloatBuffer>
{
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
//Calculate correlation values against each 1200/1800 reference signal
mCorrelationValuesMark = mCorrelatorMark.process(buffer);
@@ -212,17 +212,17 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
* Processes each sample in the incoming sample buffer against a generated reference sample set for one symbol
* period to derive a correlation value that is in-turn averaged over one symbol period.
*
* @param reusableBuffer containing FM demodulated samples
* @param reusableFloatBuffer containing FM demodulated samples
* @return a reusable array of correlation values for each sample
*/
public float[] process(ReusableBuffer reusableBuffer)
public float[] process(ReusableFloatBuffer reusableFloatBuffer)
{
if(mCorrelationValues == null || mCorrelationValues.length != reusableBuffer.getSampleCount())
if(mCorrelationValues == null || mCorrelationValues.length != reusableFloatBuffer.getSampleCount())
{
mCorrelationValues = new float[reusableBuffer.getSampleCount()];
mCorrelationValues = new float[reusableFloatBuffer.getSampleCount()];
}
float[] samples = reusableBuffer.getSamples();
float[] samples = reusableFloatBuffer.getSamples();
int y;

View File

@@ -15,9 +15,9 @@
******************************************************************************/
package io.github.dsheirer.dsp.am;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
@@ -63,11 +63,11 @@ public class AMDemodulator
* @param complexBuffer to demodulate
* @return demodulated audio buffer.
*/
public ReusableBuffer demodulate(ReusableComplexBuffer complexBuffer)
public ReusableFloatBuffer demodulate(ReusableComplexBuffer complexBuffer)
{
ReusableBuffer reusableBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
ReusableFloatBuffer reusableFloatBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
float[] input = complexBuffer.getSamples();
float[] output = reusableBuffer.getSamples();
float[] output = reusableFloatBuffer.getSamples();
mOutputBufferPointer = 0;
for(int x= 0; x < complexBuffer.getSamples().length; x += 2)
@@ -77,6 +77,6 @@ public class AMDemodulator
complexBuffer.decrementUserCount();
return reusableBuffer;
return reusableFloatBuffer;
}
}

View File

@@ -22,7 +22,6 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter;
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
@@ -358,15 +357,6 @@ public class FilterFactory
return (int)(Math.round((double)attenuation / (22.0d * frequency)));
}
public static ComplexPrimeCICDecimate getDecimationFilter(int sampleRate, int decimatedRate, int order,
int passFrequency, int attenuation, Window.WindowType windowType)
{
int decimationRate = (int)(sampleRate / decimatedRate);
return new ComplexPrimeCICDecimate(decimationRate, order,
passFrequency, attenuation, windowType);
}
/**
* Determines decimation rate(s) for a polyphase decimation filter.
*
@@ -760,26 +750,28 @@ public class FilterFactory
* Polyphase M2 synthesizer sync filter. Designed for multiple M2 oversampled channel inputs and
* an M*channel count output.
*
* @param channelBandwidth per channel
* @param channels count
* @param channelSampleRate per input channel
* @param channelBandwidth per input channel
* @param channels input count
* @param tapsPerChannel minumum. This may be increased to meet the band edge -6.02dB requirement
* @return filter
* @throws FilterDesignException
*/
public static float[] getSincM2Synthesizer(double channelBandwidth, int channels, int tapsPerChannel)
throws FilterDesignException
public static float[] getSincM2Synthesizer(double channelSampleRate, double channelBandwidth, int channels,
int tapsPerChannel) throws FilterDesignException
{
int filterLength = (channels * tapsPerChannel) - 1;
double cutoff = (channelBandwidth * 1.10) / (channelSampleRate * (double)channels);
//Design the prototype synthesizer with 105% of the channel bandwidth produced by the channelizer.
float[] taps = FilterFactory.getKaiserSinc(filterLength, channelBandwidth * 1.05, 80.0);
float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0);
//This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient
float[] extendedTaps = new float[taps.length + 1];
System.arraycopy(taps, 0, extendedTaps, 1, taps.length);
//Adjust the overall gain of the filter to an objective of 1.0 divided by the number of channels
return normalize(extendedTaps, 1.0f);
return extendedTaps;
}
/**
@@ -918,17 +910,18 @@ public class FilterFactory
mLog.debug("Filter Length: " + (extendedTaps.length));
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency));
mLog.debug("Attenuation at 0.0 OBJECTIVE: " + 0.0);
mLog.debug("Attenuation at 1.0 OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0));
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25));
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5));
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75));
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.0));
mLog.debug("Attenuation at 0.25 Channels: " + evaluate(taps, bandEdge * 0.25) + "\tFrequency: " + (sampleRate * bandEdge * 0.25) + " [" + (bandEdge * 0.25) + "]");
mLog.debug("Attenuation at 0.50 Channels: " + evaluate(taps, bandEdge * 0.50) + "\tFrequency: " + (sampleRate * bandEdge * 0.50) + " [" + (bandEdge * 0.50) + "]");
mLog.debug("Attenuation at 0.75 Channels: " + evaluate(taps, bandEdge * 0.75) + "\tFrequency: " + (sampleRate * bandEdge * 0.75) + " [" + (bandEdge * 0.75) + "]");
mLog.debug("Attenuation OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0) + " [" + (bandEdge * 1.0) + "]");
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25) + " [" + (bandEdge * 1.25) + "]");
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5) + " [" + (bandEdge * 1.5) + "]");
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75) + " [" + (bandEdge * 1.75) + "]");
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.0) + " [" + (bandEdge * 2.0) + "]");
}
//Adjust the overall gain of the filter to an objective of 1.0 times the number of channels
return normalize(extendedTaps, 1.0f * (float)channels);
return extendedTaps;
}
/**

View File

@@ -1719,7 +1719,32 @@ public enum Filters
0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f,
0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f,
0.001695637278417295f, 0.0f, -0.000998606272947510f
} );
}),
/**
* FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
* .sampleRate(100)
* .gridDensity(16)
* .oddLength(true)
* .passBandCutoff(21)
* .passBandAmplitude(1.0)
* .passBandRipple(0.01)
* .stopBandStart(29)
* .stopBandAmplitude(0.0)
* .stopBandRipple(0.01) //Approximately -85 dB suppression
* .build();
*/
HALF_BAND_FILTER_27T(new float[]
{
0.007495115f, 0.0f, -0.011045938f, 0.0f,
0.019129561f, 0.0f, -0.031747308f, 0.0f,
0.053220540f, 0.0f, -0.099569574f, 0.0f,
0.316083550f, 0.5f, 0.316083550f, 0.0f,
-0.099569574f, 0.0f, 0.053220540f, 0.0f,
-0.031747308f, 0.0f, 0.019129561f, 0.0f,
-0.011045938f, 0.0f, 0.007495115f
});
float[] mCoefficients;

View File

@@ -63,7 +63,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 12500;
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000;
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152));

View File

@@ -136,6 +136,46 @@ public class ContinuousBufferProcessor<E> implements Listener<E>
}
}
/**
* Stops this buffer processor,stops queuing of incoming buffers and flushes the internal buffer queue
* contents to the listener.
*/
public void flushAndStop()
{
if(mRunning.compareAndSet(true, false))
{
if(mScheduledFuture != null)
{
mScheduledFuture.cancel(true);
mScheduledFuture = null;
}
process();
}
}
/**
* Distributes queued buffers to the listener
*/
private void process()
{
List<E> buffers = new ArrayList<>();
try
{
mQueue.drainTo(buffers);
if(mListener != null)
{
mListener.receive(buffers);
}
}
catch(Throwable throwable)
{
mLog.error("Error while dispatching buffers to listener", throwable);
}
}
/**
* Clears any buffers from the queue
*/
@@ -162,21 +202,7 @@ public class ContinuousBufferProcessor<E> implements Listener<E>
@Override
public void run()
{
List<E> buffers = new ArrayList<>();
try
{
mQueue.drainTo(buffers);
if(mListener != null)
{
mListener.receive(buffers);
}
}
catch(Throwable throwable)
{
mLog.error("Error while dispatching buffers to listeners", throwable);
}
process();
}
}
}

View File

@@ -15,10 +15,10 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
public class ContinuousReusableBufferProcessor<T extends ReusableBuffer> extends ContinuousBufferProcessor<T>
public class ContinuousReusableBufferProcessor<T extends AbstractReusableBuffer> extends ContinuousBufferProcessor<T>
{
/**
* Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing

View File

@@ -29,12 +29,12 @@ import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -63,7 +63,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 12500.0;
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
private static final double CHANNEL_OVERSAMPLING = 2.0;
private static final int POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL = 9;
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
@@ -142,7 +142,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* @param tunerChannel specifying center frequency and bandwidth.
* @return source or null.
*/
public TunerChannelSource getChannel(TunerChannel tunerChannel)
public TunerChannelSource getChannel(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{
PolyphaseChannelSource channelSource = null;
@@ -156,10 +156,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency);
try
{
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
mChannelSources.add(channelSource);
mChannelSources.add(channelSource);
}
catch(FilterDesignException fde)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
}
}
}
catch(IllegalArgumentException iae)
@@ -172,36 +179,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
return channelSource;
}
public TunerChannelSource getTestChannel(int index)
{
PolyphaseChannelSource channelSource = null;
try
{
List<Integer> polyphaseIndexes = new ArrayList<>();
polyphaseIndexes.add(index);
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
if(outputProcessor != null)
{
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
TunerChannel tunerChannel = new TunerChannel(centerFrequency, 12500);
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency);
}
}
catch(IllegalArgumentException iae)
{
mLog.error("Couldn't get test channel with index:" + index, iae);
}
return channelSource;
}
/**
* Creates a processor to process the channelizer channel indexes into a composite output stream providing
* channelized complex sample buffers to a registered source listener.
@@ -213,12 +190,14 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
switch(indexes.size())
{
case 1:
return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes);
return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes,
mChannelCalculator.getChannelCount());
case 2:
try
{
float[] filter = getOutputProcessorFilter(2);
return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter);
return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter,
mChannelCalculator.getChannelCount());
}
catch(FilterDesignException fde)
{
@@ -504,8 +483,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(taps == null)
{
taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelBandwidth(), channels,
POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL);
taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelSampleRate(),
mChannelCalculator.getChannelBandwidth(), channels, POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL);
mOutputProcessorFilters.put(channels, taps);
}

View File

@@ -16,22 +16,35 @@
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class PolyphaseChannelSource extends TunerChannelSource
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
private long mReplacementFrequency;
private ComplexFIRFilter2 mLowPassFilter;
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection;
@@ -46,15 +59,22 @@ public class PolyphaseChannelSource extends TunerChannelSource
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
* @param channelSampleRate for the downstream sample output
* @param centerFrequency of the incoming polyphase channel(s)
* @throws FilterDesignException if a channel low pass filter can't be designed to the channel specification
*/
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate,
long centerFrequency)
long centerFrequency, ChannelSpecification channelSpecification)
throws FilterDesignException
{
super(producerSourceEventListener, tunerChannel);
mPolyphaseChannelOutputProcessor = outputProcessor;
mChannelSampleRate = channelSampleRate;
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate);
float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(),
channelSpecification.getStopFrequency());
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
setFrequency(centerFrequency);
}
@@ -93,14 +113,33 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public void setPolyphaseChannelOutputProcessor(IPolyphaseChannelOutputProcessor outputProcessor, long frequency)
{
//Lock on the channel output processor to block the channel results processor thread from servicing the
//channel output processor's buffer while we change out the processors.
synchronized(mPolyphaseChannelOutputProcessor)
//If this is the first time, simply assign the output processor
if(mPolyphaseChannelOutputProcessor == null)
{
mPolyphaseChannelOutputProcessor = outputProcessor;
}
//Otherwise, we have to swap out the processor on the sample processing thread
else
{
mReplacementPolyphaseChannelOutputProcessor = outputProcessor;
mReplacementFrequency = frequency;
}
}
/**
* Updates the output processor to use the new output processor provided by the external
* polyphase channel manager. This method should only be executed on the sample processing
* thread, within the processChannelResults() method.
*/
private void swapOutputProcessor()
{
if(mReplacementPolyphaseChannelOutputProcessor != null)
{
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
mPolyphaseChannelOutputProcessor = outputProcessor;
mPolyphaseChannelOutputProcessor = mReplacementPolyphaseChannelOutputProcessor;
mReplacementPolyphaseChannelOutputProcessor = null;
//Remove the buffer overflow listener and clear any previous buffer overflow state
existingProcessor.setSourceOverflowListener(null);
@@ -109,18 +148,9 @@ public class PolyphaseChannelSource extends TunerChannelSource
//Register to receive buffer overflow notifications so we can push them up through the source chain
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
//Fully process the residual channel results buffer of the previous channel output processor
if(existingProcessor != null)
{
existingProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
//to allow consumers to adjust and calculate a new frequency correction value, if needed.
mIndexCenterFrequency = frequency;
mChannelFrequencyCorrection = 0;
//Finally, setup the frequency offset for the output processor.
mIndexCenterFrequency = mReplacementFrequency;
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
}
@@ -133,6 +163,11 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
{
if(mReplacementPolyphaseChannelOutputProcessor != null)
{
swapOutputProcessor();
}
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
}
@@ -169,7 +204,6 @@ public class PolyphaseChannelSource extends TunerChannelSource
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.dispose();
mPolyphaseChannelOutputProcessor = null;
}
}
@@ -197,7 +231,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
* @param frequency in hertz
*/
@Override
protected void setFrequency(long frequency)
public void setFrequency(long frequency)
{
mIndexCenterFrequency = frequency;
@@ -209,11 +243,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
@Override
protected void processSamples()
{
//Lock on the output processor so that it can't be changed in the middle of processing
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
/**
@@ -232,10 +262,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
private void updateFrequencyOffset()
{
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
@Override
@@ -244,4 +271,42 @@ public class PolyphaseChannelSource extends TunerChannelSource
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
getTunerChannel().getFrequency();
}
/**
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
*
* @param sampleRate for the final output channel rate
* @param passFrequency for half of the desired channel rate
* @param stopFrequency for the attenuated band
* @return a newly designed filter or a previously designed (cached) filter
* @throws FilterDesignException
*/
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
//Use existing filter if we've already designed one
if(sLowPassFilters.containsKey((int)sampleRate))
{
return sLowPassFilters.get((int)sampleRate);
}
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.passBandCutoff(passFrequency)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(stopFrequency)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//This will throw an exception if the filter cannot be designed
float[] taps = designer.getImpulseResponse();
sLowPassFilters.put((int)sampleRate, taps);
return taps;
}
}

View File

@@ -42,6 +42,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
private int mInputChannelCount;
private IOscillator mFrequencyCorrectionMixer;
private boolean mFrequencyCorrectionEnabled;
private double mGain = 1.0;
/**
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
@@ -51,9 +52,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
* channel sample rate for frequency translation/correction.
*/
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, double gain)
{
mInputChannelCount = inputChannelCount;
mGain = gain;
//TODO: swap this out and use the LowPhaseNoiseOscillator
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
@@ -62,6 +64,11 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
}
protected double getGain()
{
return mGain;
}
@Override
public int getPolyphaseChannelIndexCount()
{

View File

@@ -38,10 +38,11 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
*
* @param sampleRate of the output sample stream.
* @param channelIndexes containing a single channel index.
* @param gain value to apply. Typically this is the same as the channelizer's channel count.
*/
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes)
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, double gain)
{
super(1, sampleRate);
super(1, sampleRate, gain);
setPolyphaseChannelIndices(channelIndexes);
}
@@ -91,6 +92,8 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
}
channelBuffer.applyGain(getGain());
reusableComplexBufferAssembler.receive(channelBuffer);
}
catch(IllegalArgumentException iae)

View File

@@ -16,29 +16,18 @@
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private static Map<Integer,float[]> sLowPassFilterKernelMap = new HashMap<>();
// private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private TwoChannelSynthesizerM2 mSynthesizer;
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
private ComplexFIRFilter2 mLowPassFilter;
private int mChannelOffset1;
private int mChannelOffset2;
@@ -49,36 +38,20 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
*
* @param sampleRate of the output sample stream.
* @param channelIndexes containing two channel indices.
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
*/
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter)
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, double gain)
{
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
//after synthesizing both input channels
super(2, sampleRate);
super(2, sampleRate, gain);
setPolyphaseChannelIndices(channelIndexes);
setSynthesisFilter(filter);
float[] lowPassFilter = sLowPassFilterKernelMap.get((int)sampleRate);
if(lowPassFilter == null)
{
try
{
lowPassFilter = getLowPassFilter(sampleRate);
sLowPassFilterKernelMap.put((int)sampleRate, lowPassFilter);
}
catch(FilterDesignException fde)
{
mLog.error("Couldn't design a low-pass filter for 2-channel synthesizer", fde);
throw new IllegalStateException("Cannot design low-pass filter for 2 channel synthesizer", fde);
}
}
mLowPassFilter = new ComplexFIRFilter2(lowPassFilter, 1.0f);
}
/**
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
*
* @param frequencyOffset in hertz
*/
@Override
@@ -125,7 +98,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
{
for(ReusableChannelResultsBuffer buffer: channelResultsBuffers)
for(ReusableChannelResultsBuffer buffer : channelResultsBuffers)
{
ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1);
ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2);
@@ -139,46 +112,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples());
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
synthesized.applyGain(getGain());
reusableComplexBufferAssembler.receive(lowPassFiltered);
reusableComplexBufferAssembler.receive(synthesized);
buffer.decrementUserCount();
}
}
/**
* Creates a low-pass filter kernel for the specified sample rate.
* @param sampleRate for the channel
* @return filter
* @throws FilterDesignException if the filter cannot be designed
*/
private static float[] getLowPassFilter(double sampleRate) throws FilterDesignException
{
double cutoff = sampleRate / 4.0;
int passBandCutoff = (int)(cutoff * 0.92);
int stopBandStart = (int)(cutoff * 1.08);
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.oddLength(true)
.passBandCutoff((int)passBandCutoff)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart((int)stopBandStart)
.stopBandAmplitude(0.0)
.stopBandRipple(0.005) //Approximately 94 dB attenuation
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
return designer.getImpulseResponse();
}
throw new FilterDesignException("Couldn't design low-pass filter for sample rate: " + sampleRate +
" Pass End:" + passBandCutoff + " Stop Begin:" + stopBandStart);
}
}

View File

@@ -15,11 +15,10 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.cic;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandFilter;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
@@ -28,52 +27,93 @@ import io.github.dsheirer.sample.decimator.ComplexDecimator;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Non-Recursive Prime-Factor CIC Filter.
*
* Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This
* filter is comprised of multiple decimating stages each with a prime factor decimation rate. Multiple stages are
* cascaded to achieve the overall decimation rate.
*
* This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by
* adding additional prime factors to the PRIMES array.
*/
public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
{
// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate.class);
/**
* PRIME numbers to use for decimation stage sizing. This list contains values that should
* support input sample rates up to 70 MHz with an output channel rate of at least 25 kHz.
*/
public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359,
367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593,
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701,
709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827,
829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953,
967, 971, 977, 983, 991, 997, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051,
1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163,
1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279,
1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399,
1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489,
1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601,
1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709,
1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831,
1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951,
1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069,
2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179,
2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297,
2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399,
2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543,
2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671,
2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753,
2767, 2777, 2789, 2791, 2797, 2801
};
private ArrayList<DecimatingStage> mDecimatingStages =
new ArrayList<DecimatingStage>();
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
private static Map<Integer,List<Integer>> sPrimeFactors = new HashMap();
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage;
private Output mOutput;
/**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated, single paired i/q float sample output.
* Constructs a new decimation filter.
*
* Implements the CIC filter described in Understanding Digital Signal
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
* decimating stages each with a prime factor decimation rate. Multiple
* stages are cascaded to achieve the overall decimation rate.
* An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel
* pass band might be:
* sampleRate = 2,400,000.0
* decimation = 96
* passFrequency = 6250 (1/4 of the channel rate)
* stopFrequency = 7000
*
* This filter supports a maximum decimation rate of 700. This filter can
* be adapted to higher decimation rates by adding additional prime factors
* to the PRIMES array.
* Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter,
* where smaller transition bandwidth creates a longer filter that requires more processing effort.
*
* @param sampleRate of the input sample stream
* @param decimation - overall decimation rate
* @param order - filter order
* @param passFrequency for the final cleanup filter
* @param stopFrequency for the final cleanup filter
* @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate
* and specified pass/stop frequencies.
*/
public ComplexPrimeCICDecimate(int decimation, int order,
int passFrequency, int attenuation, Window.WindowType windowType)
public ComplexPrimeCICDecimate(double sampleRate, int decimation, double passFrequency, double stopFrequency)
throws FilterDesignException
{
Validate.isTrue(decimation <= 700);
Validate.isTrue(decimation <= PRIMES[PRIMES.length - 1]);
List<Integer> stageSizes = getPrimeFactors(decimation);
for(int x = 0; x < stageSizes.size(); x++)
{
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), 1);
mDecimatingStages.add(stage);
@@ -89,10 +129,11 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
}
mOutput = new Output(48000, passFrequency, attenuation, windowType);
double channelRate = sampleRate / (double)decimation;
mDecimatingStages.get(mDecimatingStages.size() - 1)
.setListener(mOutput);
mOutput = new Output(channelRate, passFrequency, stopFrequency);
mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput);
}
public void dispose()
@@ -127,16 +168,26 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
/**
* Calculates the prime factors of the decimation rate up to a maximum
* decimation rate of 700. If you wish to have decimation rates higher
* than 700, then add additional prime factors to the PRIMES array.
* Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the
* highest rate listed in the PRIMES array, then add additional prime factors to the PRIMES array.
*
* @param decimation - integral decimation rate
* @return - ordered list (smallest to largest) of prime factors
*/
public static List<Integer> getPrimeFactors(int decimation)
{
ArrayList<Integer> stages = new ArrayList<Integer>();
if(decimation > PRIMES[PRIMES.length - 1])
{
throw new IllegalArgumentException("Decimation rates higher than [" + PRIMES[PRIMES.length - 1] +
"] are not supported. Update the PRIMES array to add support for higher decimation rates");
}
if(sPrimeFactors.containsKey(decimation))
{
return sPrimeFactors.get(decimation);
}
List<Integer> primeFactors = new ArrayList<Integer>();
int pointer = 0;
@@ -146,8 +197,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
if(decimation % prime == 0)
{
stages.add(prime);
primeFactors.add(prime);
decimation /= prime;
}
else
@@ -156,7 +206,9 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
}
return stages;
sPrimeFactors.put(decimation, primeFactors);
return primeFactors;
}
/**
@@ -185,7 +237,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
*/
public class DecimatingStage implements ComplexSampleListener
{
private ArrayList<Stage> mStages = new ArrayList<Stage>();
private List<Stage> mStages = new ArrayList<Stage>();
private Stage mFirstStage;
private ComplexDecimator mDecimator;
@@ -217,7 +269,6 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
mDecimator = new ComplexDecimator(size);
mStages.get(mStages.size() - 1).setListener(mDecimator);
}
@@ -248,9 +299,8 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
/**
* Single non-decimating CIC stage component. Uses a circular buffer and a
* running average internally to implement the stage so that stage size has
* essentially no impact on the computational requirements of the stage
* Single non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement
* the stage so that stage size has essentially no impact on the computational requirements of the stage
*/
public class Stage implements ComplexSampleListener
{
@@ -267,7 +317,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
protected float mGain;
public Stage()
protected Stage()
{
}
@@ -278,7 +328,8 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
mISamples = new float[mSize];
mQSamples = new float[mSize];
mGain = 1.0f / (float)size;
float baseGain = 1.0f / (float)size;
mGain = baseGain * (1.0f / (1.0f - baseGain));
}
public void dispose()
@@ -288,7 +339,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
public void receive(float i, float q)
{
/* Subtract the oldest sample and add back in the newest */
/* Subtract the oldest sample and add in the newest sample */
mISum = mISum - mISamples[mSamplePointer] + i;
mQSum = mQSum - mQSamples[mSamplePointer] + q;
@@ -316,30 +367,24 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
/**
* Size 2 stage that removes the unnecessary circular buffer management for
* a two-stage.
* Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage.
*/
public class TwoStage extends Stage
{
public TwoStage()
{
super();
mGain = 0.5f;
}
public void receive(float i, float q)
{
float iSum = mISum + i;
float qSum = mQSum + q;
if(mListener != null)
{
mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain));
}
mISum = i;
mQSum = q;
if(mListener != null)
{
mListener.receive((iSum * mGain), (qSum * mGain));
}
}
}
@@ -350,31 +395,29 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
*/
public class Output implements ComplexSampleListener
{
/* Decimated output buffers will contain 1024 samples */
private ReusableComplexBufferAssembler mAssembler = new ReusableComplexBufferAssembler(2048, 48000.0);
private ComplexFIRFilter2 mCleanupFilter;
private ComplexHalfBandFilter mHalfBandFilter = new ComplexHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
/* Decimated output buffers will contain 1024 complex samples */
private ReusableComplexBufferAssembler mBufferAssembler;
private ComplexFIRFilter2 mLowPassFilter;
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
public Output(int outputSampleRate, int passFrequency, int attenuation,
Window.WindowType windowType)
public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
mCleanupFilter = new ComplexFIRFilter2(FilterFactory
.getCICCleanupFilter(outputSampleRate, passFrequency, attenuation, windowType), 0.4f);
mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate);
//Bypassing the CIC cleanup filter for now
mAssembler.setListener(new Listener<ReusableComplexBuffer>()
//This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies
float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency);
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
mBufferAssembler.setListener(new Listener<ReusableComplexBuffer>()
{
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
if(mReusableComplexBufferListener != null)
{
ReusableComplexBuffer cleanupFiltered = mCleanupFilter.filter(reusableComplexBuffer);
ReusableComplexBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
mReusableComplexBufferListener.receive(halfBandFiltered);
ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer);
mReusableComplexBufferListener.receive(filteredBuffer);
// mReusableComplexBufferListener.receive(reusableComplexBuffer);
}
else
{
@@ -386,20 +429,17 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
public void dispose()
{
mAssembler.dispose();
mCleanupFilter.dispose();
mHalfBandFilter.dispose();
mBufferAssembler.dispose();
mLowPassFilter.dispose();
}
/**
* Receiver method for the output adapter to receive a filtered,
* decimated sample, apply gain correction, apply cleanup filtering
* and output the sample values as a complex sample.
* Interface for receiving CIC decimated output samples
*/
@Override
public void receive(float inphase, float quadrature)
{
mAssembler.receive(new float[]{inphase, quadrature});
mBufferAssembler.receive(inphase, quadrature);
}
public void setListener(Listener<ReusableComplexBuffer> listener)
@@ -411,5 +451,43 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
{
mReusableComplexBufferListener = null;
}
/**
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
*
* @param sampleRate for the final output channel rate
* @param passFrequency for half of the desired channel rate
* @param stopFrequency for the attenuated band
* @return a newly designed filter or a previously designed (cached) filter
* @throws FilterDesignException
*/
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
//Use existing filter if we've already designed one
if(sLowPassFilters.containsKey((int)sampleRate))
{
return sLowPassFilters.get((int)sampleRate);
}
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.passBandCutoff(passFrequency)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(stopFrequency)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//This will throw an exception if the filter cannot be designed
float[] taps = designer.getImpulseResponse();
sLowPassFilters.put((int)sampleRate, taps);
return taps;
}
}
}

View File

@@ -23,8 +23,8 @@ import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.decimator.RealDecimator;
import io.github.dsheirer.sample.real.RealSampleListener;
import org.apache.commons.lang3.Validate;
@@ -124,7 +124,7 @@ public class RealPrimeCICDecimate
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener(Listener<ReusableBuffer> listener)
public void setListener(Listener<ReusableFloatBuffer> listener)
{
mOutput.setListener(listener);
}
@@ -173,7 +173,7 @@ public class RealPrimeCICDecimate
/**
* Primary input method for receiving real buffers.
*/
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
if(mFirstDecimatingStage != null)
{
@@ -353,7 +353,7 @@ public class RealPrimeCICDecimate
{
/* Decimated output buffers will contain 1024 samples */
private ReusableBufferAssembler mAssembler;
private Listener<ReusableBuffer> mReusableBufferListener;
private Listener<ReusableFloatBuffer> mReusableBufferListener;
private RealFIRFilter2 mCleanupFilter;
private HalfBandFilter mHalfBandFilter = new HalfBandFilter(
@@ -365,21 +365,21 @@ public class RealPrimeCICDecimate
attenuation, windowType), 0.4f);
mAssembler = new ReusableBufferAssembler(outputBufferSize);
mAssembler.setListener(new Listener<ReusableBuffer>()
mAssembler.setListener(new Listener<ReusableFloatBuffer>()
{
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
if(mReusableBufferListener != null)
{
ReusableBuffer cleanupFiltered = mCleanupFilter.filter(reusableBuffer);
ReusableBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
ReusableFloatBuffer cleanupFiltered = mCleanupFilter.filter(reusableFloatBuffer);
ReusableFloatBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
mReusableBufferListener.receive(halfBandFiltered);
}
else
{
reusableBuffer.decrementUserCount();
reusableFloatBuffer.decrementUserCount();
}
}
});
@@ -406,7 +406,7 @@ public class RealPrimeCICDecimate
/**
* Adds a listener to receive output samples
*/
public void setListener(Listener<ReusableBuffer> listener)
public void setListener(Listener<ReusableFloatBuffer> listener)
{
mReusableBufferListener = listener;
}

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -77,11 +77,11 @@ public class BufferDCFilter
}
}
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
float[] unfilteredSamples = unfilteredBuffer.getSamples();
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
float[] filteredSamples = filteredBuffer.getSamples();
float offset = getOffset(unfilteredSamples);

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* DC removal filter for removing any DC offset that may be present when a signal is not aligned within the
@@ -77,9 +77,9 @@ public class DCRemovalFilter
* @param unfilteredBuffer containing samples to filter
* @return a new reusable buffer containing the filtered samples with the user count set to 1.
*/
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
float[] unfilteredSamples = unfilteredBuffer.getSamples();
float[] filteredSamples = filteredBuffer.getSamples();

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
@@ -67,9 +67,9 @@ public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
* @param inputBuffer to filter
* @return filtered samples in a new (ie reused) output buffer
*/
public ReusableBuffer filter(ReusableBuffer inputBuffer)
public ReusableFloatBuffer filter(ReusableFloatBuffer inputBuffer)
{
ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
float[] inputSamples = inputBuffer.getSamples();
float[] outputSamples = outputBuffer.getSamples();

View File

@@ -61,14 +61,15 @@ public class FilterViewer extends Application
// .passBandRipple(0.01)
// .build();
FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
.sampleRate(8000)
.stopFrequency1(1000)
.passFrequencyBegin(1100)
.passFrequencyEnd(1900)
.stopFrequency2(2000)
.stopRipple(0.000001)
.passRipple(0.00001)
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(25000.0)
.gridDensity(16)
.passBandCutoff(11800)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(12400)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
float[] taps = null;
@@ -102,7 +103,6 @@ public class FilterViewer extends Application
throw new IllegalStateException("Couldn't design filter");
}
return taps;
}
}

View File

@@ -299,8 +299,8 @@ public class FIRFilterSpecification
private int mOrder;
private Boolean mOddLength;
private int mGridDensity = 16;
private int mPassBandEndFrequency;
private int mStopBandStartFrequency;
private double mPassBandEndFrequency;
private double mStopBandStartFrequency;
private double mPassBandRipple;
private double mStopBandRipple;
private double mPassBandAmplitude = 1.0;
@@ -342,13 +342,13 @@ public class FIRFilterSpecification
return this;
}
public LowPassBuilder passBandCutoff(int frequencyHz)
public LowPassBuilder passBandCutoff(double frequencyHz)
{
mPassBandEndFrequency = frequencyHz;
return this;
}
public LowPassBuilder stopBandStart(int frequencyHz)
public LowPassBuilder stopBandStart(double frequencyHz)
{
mStopBandStartFrequency = frequencyHz;
return this;
@@ -905,7 +905,7 @@ public class FIRFilterSpecification
* @param stopBandRipple stop band ripple in dB
* @return estimated filter length
*/
public static int estimateFilterOrder(double sampleRate, int frequency1, int frequency2,
public static int estimateFilterOrder(double sampleRate, double frequency1, double frequency2,
double passBandRipple, double stopBandRipple)
{
double df = Math.abs(frequency2 - frequency1) / sampleRate;
@@ -1013,13 +1013,13 @@ public class FIRFilterSpecification
* @param amplitude of the frequency band relative to unity (0.0 - 1.0)
* @param ripple of the frequency band specified as db ripple
*/
public FrequencyBand(double sampleRate, int start, int end, double amplitude, double ripple)
public FrequencyBand(double sampleRate, double start, double end, double amplitude, double ripple)
{
this((double)start / sampleRate, (double)end / sampleRate,
this(start / sampleRate, end / sampleRate,
amplitude, ripple);
}
public FrequencyBand(double sampleRate, int start, int end, double amplitude, double ripple, double weight)
public FrequencyBand(double sampleRate, double start, double end, double amplitude, double ripple, double weight)
{
this(sampleRate, start, end, amplitude, ripple);
mWeight = weight;

View File

@@ -16,8 +16,8 @@
package io.github.dsheirer.dsp.filter.fir.real;
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays.
@@ -113,11 +113,11 @@ public class RealFIRFilter2 extends FIRFilter
* @param unfilteredBuffer containing a sample array to be filtered
* @return a new reusable buffer with the filtered samples.
*/
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
float[] unfilteredSamples = unfilteredBuffer.getSamples();
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
float[] filteredSamples = filteredBuffer.getSamples();
for(int x = 0; x < unfilteredSamples.length; x++)

View File

@@ -15,92 +15,94 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.halfband.complex;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter2;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
public class ComplexHalfBandFilter
{
private HalfBandFilter mIFilter;
private HalfBandFilter mQFilter;
private boolean mDecimate;
private boolean mDecimateFlag;
private ReusableComplexBufferQueue mBufferQueue = new ReusableComplexBufferQueue("ComplexHalfBandFilter");
/**
* @param coefficients
* @param gain
* @param decimate
*/
public ComplexHalfBandFilter(float[] coefficients, float gain, boolean decimate)
private HalfBandFilter2 mIFilter;
private HalfBandFilter2 mQFilter;
private float mResidualISample;
private float mResidualQSample;
private boolean mHasResidual;
private int mOutputBufferLength;
private float[] mSamples;
private int mSamplesPointer;
private float[] mFilteredSamples;
private int mFilteredSamplesPointer;
public ComplexHalfBandFilter(float[] coefficients, float gain)
{
mIFilter = new HalfBandFilter(coefficients, gain);
mQFilter = new HalfBandFilter(coefficients, gain);
mDecimate = decimate;
mIFilter = new HalfBandFilter2(coefficients, gain);
mQFilter = new HalfBandFilter2(coefficients, gain);
}
public void dispose()
public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer)
{
mIFilter.dispose();
mQFilter.dispose();
mSamples = originalBuffer.getSamples();
mSamplesPointer = 0;
mOutputBufferLength = (mHasResidual ? (mSamples.length + 2) : mSamples.length) / 4 * 2;
ReusableComplexBuffer filteredBuffer = mBufferQueue.getBuffer(mOutputBufferLength);
mFilteredSamples = filteredBuffer.getSamples();
mFilteredSamplesPointer = 0;
if(mHasResidual)
{
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mResidualISample, mSamples[mSamplesPointer++]);
mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mResidualQSample, mSamples[mSamplesPointer++]);
mHasResidual = false;
}
while(mSamplesPointer + 3 < mSamples.length)
{
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer], mSamples[mSamplesPointer + 2]);
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer + 1], mSamples[mSamplesPointer + 3]);
mSamplesPointer += 4;
}
if(mSamplesPointer < mSamples.length)
{
mHasResidual = true;
mResidualISample = mSamples[mSamplesPointer++];
mResidualQSample = mSamples[mSamplesPointer];
}
originalBuffer.decrementUserCount();
return filteredBuffer;
}
public ReusableComplexBuffer filter(ReusableComplexBuffer buffer)
public static void main(String[] args)
{
throw new IllegalStateException("This filter has to be updated to work with reusable buffers");
Oscillator oscillator = new Oscillator(1, 16);
ComplexHalfBandFilter filter = new ComplexHalfBandFilter(Filters.HALF_BAND_FILTER_27T.getCoefficients(), 1.0f);
ReusableComplexBufferQueue bufferQueue = new ReusableComplexBufferQueue("Test");
ReusableComplexBuffer complexBuffer = bufferQueue.getBuffer(8);
oscillator.generateComplex(complexBuffer);
ReusableComplexBuffer filteredBuffer = filter.filter(complexBuffer);
filteredBuffer.decrementUserCount();
ReusableComplexBuffer complexBuffer2 = bufferQueue.getBuffer(10);
oscillator.generateComplex(complexBuffer2);
ReusableComplexBuffer filteredBuffer2 = filter.filter(complexBuffer2);
filteredBuffer2.decrementUserCount();
ReusableComplexBuffer complexBuffer3 = bufferQueue.getBuffer(8);
oscillator.generateComplex(complexBuffer3);
ReusableComplexBuffer filteredBuffer3 = filter.filter(complexBuffer3);
filteredBuffer3.decrementUserCount();
// if(mDecimate)
// {
// float[] samples = buffer.getSamples();
//
// int half = samples.length / 2;
//
// float[] decimated;
//
// if(half % 2 == 0 || mDecimateFlag)
// {
// decimated = new float[half];
// }
// else
// {
// /* If inbound buffer size is odd-length, then we have to
// * adjust when the first operation is non-decimation, since
// * that will produce an outbound buffer 1 sample larger */
// decimated = new float[half + 1];
// }
//
// int decimatedPointer = 0;
//
// for(int x = 0; x < samples.length; x += 2)
// {
// /* Insert the sample but don't filter */
// if(mDecimateFlag)
// {
// mIFilter.insert(samples[x]);
// mQFilter.insert(samples[x + 1]);
// }
// else
// {
// decimated[decimatedPointer++] = mIFilter.filter(samples[x]);
// decimated[decimatedPointer++] = mQFilter.filter(samples[x + 1]);
// }
//
// /* Toggle the decimation flag for every sample */
// mDecimateFlag = !mDecimateFlag;
// }
//
// //return buffer
// }
// else
// {
// /* Non Decimate - filter each of the values and return the buffer */
// float[] samples = buffer.getSamples();
//
// for(int x = 0; x < samples.length; x += 2)
// {
// samples[x] = mIFilter.filter(samples[x]);
// samples[x + 1] = mQFilter.filter(samples[x + 1]);
// }
//
// //return buffer
// }
}
}

View File

@@ -17,10 +17,11 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.halfband.real;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.apache.commons.lang3.Validate;
@Deprecated
public class HalfBandFilter
{
private float[] mBuffer;
@@ -84,10 +85,10 @@ public class HalfBandFilter
mBufferPointer = mBufferPointer % mBufferSize;
}
public ReusableBuffer filter(ReusableBuffer input)
public ReusableFloatBuffer filter(ReusableFloatBuffer input)
{
float[] inputSamples = input.getSamples();
ReusableBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
float[] outputSamples = output.getSamples();
for(int x = 0; x < inputSamples.length; x++)

View File

@@ -0,0 +1,89 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.halfband.real;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when
* supported by the host CPU.
*/
public class HalfBandFilter2
{
private static final float CENTER_COEFFICIENT = 0.5f;
private float[] mCoefficients;
private float[] mEvenSamples;
private float[] mOddSamples;
private int mCenterSampleIndex;
private float mAccumulator;
private int mPointer;
private float mGain;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
* @param gain value to apply to the output
*/
public HalfBandFilter2(float[] coefficients, float gain)
{
if(coefficients.length % 2 == 0)
{
throw new IllegalArgumentException("Half-band filter coefficients must be odd-length");
}
mGain = gain;
int half = coefficients.length / 2 + 1;
mCoefficients = new float[half];
mEvenSamples = new float[half];
//Use only the even coefficients since the odd coefficients are all zero-valued
for(int x = 0; x < coefficients.length; x += 2)
{
mCoefficients[x / 2] = coefficients[x];
}
//Setup the odd samples array as a simple delay line, half the size of the even samples array
mOddSamples = new float[half / 2];
mCenterSampleIndex = mOddSamples.length - 1;
}
public float filter(float sample1, float sample2)
{
//Structured for SIMD array copy optimization when supported by host CPU
System.arraycopy(mEvenSamples, 0, mEvenSamples, 1, mEvenSamples.length - 1);
System.arraycopy(mOddSamples, 0, mOddSamples, 1, mOddSamples.length - 1);
mEvenSamples[0] = sample1;
mOddSamples[0] = sample2;
mAccumulator = 0.0f;
//Structured for SIMD dot.product optimization when supported by host CPU
for(mPointer = 0; mPointer < mEvenSamples.length; mPointer++)
{
mAccumulator += (mEvenSamples[mPointer] * mCoefficients[mPointer]);
}
mAccumulator += (mOddSamples[mCenterSampleIndex] * CENTER_COEFFICIENT);
mAccumulator *= mGain;
return mAccumulator;
}
}

View File

@@ -18,8 +18,8 @@ package io.github.dsheirer.dsp.filter.resample;
import com.laszlosystems.libresample4j.Resampler;
import com.laszlosystems.libresample4j.SampleBuffers;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,7 +32,7 @@ public class RealResampler
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealResampler");
private Resampler mResampler;
private Listener<ReusableBuffer> mResampledListener;
private Listener<ReusableFloatBuffer> mResampledListener;
private BufferManager mBufferManager;
private double mResampleFactor;
@@ -50,11 +50,11 @@ public class RealResampler
/**
* Primary input method to the resampler
* @param reusableBuffer to resample
* @param reusableFloatBuffer to resample
*/
public void resample(ReusableBuffer reusableBuffer)
public void resample(ReusableFloatBuffer reusableFloatBuffer)
{
mBufferManager.load(reusableBuffer);
mBufferManager.load(reusableFloatBuffer);
mResampler.process(mResampleFactor, mBufferManager, false);
}
@@ -62,7 +62,7 @@ public class RealResampler
* Registers the listener to receive the resampled buffer output
* @param resampledBufferListener to receive buffers
*/
public void setListener(Listener<ReusableBuffer> resampledBufferListener)
public void setListener(Listener<ReusableFloatBuffer> resampledBufferListener)
{
mResampledListener = resampledBufferListener;
}
@@ -89,10 +89,10 @@ public class RealResampler
/**
* Queues the buffer sample for resampling
*/
public void load(ReusableBuffer reusableBuffer)
public void load(ReusableFloatBuffer reusableFloatBuffer)
{
mInputBuffer.put(reusableBuffer.getSamples());
reusableBuffer.decrementUserCount();
mInputBuffer.put(reusableFloatBuffer.getSamples());
reusableFloatBuffer.decrementUserCount();
}
@Override
@@ -128,7 +128,7 @@ public class RealResampler
while(mOutputBuffer.position() > mOutputBufferSize)
{
ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize);
ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize);
mOutputBuffer.flip();
mOutputBuffer.get(outputBuffer.getSamples());

View File

@@ -17,9 +17,9 @@
******************************************************************************/
package io.github.dsheirer.dsp.fm;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.complex.Complex;
/**
@@ -129,9 +129,9 @@ public class FMDemodulator
* @param basebandSampleBuffer containing samples to demodulate
* @return demodulated sample buffer.
*/
public ReusableBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer)
public ReusableFloatBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer)
{
ReusableBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount());
ReusableFloatBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount());
float[] basebandSamples = basebandSampleBuffer.getSamples();
float[] demodulatedSamples = demodulatedBuffer.getSamples();

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory;
* adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However,
* the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission.
*/
public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener, ISyncStateListener
public class LTRDecoder implements Listener<ReusableFloatBuffer>, ISyncDetectListener, ISyncStateListener
{
private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
@@ -148,10 +148,10 @@ public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
*/
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
ReusableBuffer dcFiltered = mDCFilter.filter(buffer);
ReusableBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered);
ReusableFloatBuffer dcFiltered = mDCFilter.filter(buffer);
ReusableFloatBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered);
for(float sample : lowPassFiltered.getSamples())
{

View File

@@ -2,8 +2,8 @@ package io.github.dsheirer.dsp.gain;
import io.github.dsheirer.buffer.DoubleCircularBuffer;
import io.github.dsheirer.buffer.RealCircularBuffer;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/*******************************************************************************
* SDR Trunk
@@ -147,9 +147,9 @@ public class AutomaticGainControl
* @param input samples to process
* @return output samples with gain applied
*/
public ReusableBuffer process(ReusableBuffer input)
public ReusableFloatBuffer process(ReusableFloatBuffer input)
{
ReusableBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount());
ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount());
float[] inputSamples = input.getSamples();
float[] outputSamples = output.getSamples();

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.mixer;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.complex.Complex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -127,8 +127,8 @@ public abstract class AbstractOscillator implements IOscillator
for(int x = 0; x < sampleCount; x++)
{
samples[x] = inphase();
rotate();
samples[x] = quadrature();
}
return samples;
@@ -182,11 +182,11 @@ public abstract class AbstractOscillator implements IOscillator
/**
* Generates enough real samples to fill the reusable buffer
* @param reusableBuffer to fill with real samples
* @param reusableFloatBuffer to fill with real samples
*/
@Override
public void generateReal(ReusableBuffer reusableBuffer)
public void generateReal(ReusableFloatBuffer reusableFloatBuffer)
{
reusableBuffer.reloadFrom(generateReal(reusableBuffer.getSampleCount()), System.currentTimeMillis());
reusableFloatBuffer.reloadFrom(generateReal(reusableFloatBuffer.getSampleCount()), System.currentTimeMillis());
}
}

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.mixer;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Oscillator interface.
@@ -102,9 +102,9 @@ public interface IOscillator
* Generates real samples and fills the reusable buffer using the current system time in millis for
* the update timestamp.
*
* @param reusableBuffer to fill with complex samples
* @param reusableFloatBuffer to fill with complex samples
*/
void generateReal(ReusableBuffer reusableBuffer);
void generateReal(ReusableFloatBuffer reusableFloatBuffer);
/**
* Mixes (heterodynes) the complex sample array using the current settings of this oscillator.

View File

@@ -18,6 +18,10 @@ package io.github.dsheirer.dsp.mixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class LowPhaseNoiseOscillator extends AbstractOscillator
{
private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class);
@@ -82,4 +86,54 @@ public class LowPhaseNoiseOscillator extends AbstractOscillator
//Update the gain value for the next rotation
mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature));
}
public static long process(AbstractOscillator oscillator, int iterations, int sampleCount)
{
long start = System.nanoTime();
for(int x = 0; x < iterations; x++)
{
float[] samples = oscillator.generateComplex(sampleCount);
}
long duration = System.nanoTime() - start;
return duration;
}
public static void main(String[] args)
{
mLog.debug("Starting ...");
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS");
AbstractOscillator lpno = new LowPhaseNoiseOscillator(100, 800);
AbstractOscillator regular = new Oscillator(100, 800);
int iterations = 10000;
int sampleCount = 1000000;
mLog.debug("Warm up - lpno");
process(lpno, iterations, sampleCount);
process(lpno, iterations, sampleCount);
process(lpno, iterations, sampleCount);
mLog.debug("Warm up - regular");
process(regular, iterations, sampleCount);
process(regular, iterations, sampleCount);
process(regular, iterations, sampleCount);
mLog.debug("Test - lpno");
process(lpno, iterations, sampleCount);
process(lpno, iterations, sampleCount);
long duration = process(lpno, iterations, sampleCount);
mLog.debug("Duration Low Phase Noise: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration))));
mLog.debug("Test - regular");
process(regular, iterations, sampleCount);
process(regular, iterations, sampleCount);
duration = process(regular, iterations, sampleCount);
mLog.debug("Duration Regular: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration))));
// mLog.debug(" Delta: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration2 - duration))));
}
}

View File

@@ -95,10 +95,45 @@ public class DQPSKDecisionDirectedSymbolEvaluator implements IPSKSymbolEvaluator
//Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the
//arctan of the error angle relative to 0 radians and this provides our error value
mPhaseError = -mEvaluationSymbol.quadrature();
float errorNormalized = normalize(mEvaluationSymbol.quadrature(), 0.3f);
mPhaseError = -errorNormalized;
//Timing error is the same as phase error with the sign corrected according to the vector's angular rotation
mTimingError = mEvaluationSymbol.quadrature() * mTimingErrorPolarity;
mTimingError = errorNormalized * mTimingErrorPolarity;
}
/**
* Constrains value to the range of ( -maximum <> maximum )
*/
private static float clip(float value, float maximum)
{
if(value > maximum)
{
return maximum;
}
else if(value < -maximum)
{
return -maximum;
}
return value;
}
/**
* Constrains timing error to +/- the maximum value and corrects any
* floating point invalid numbers
*/
private float normalize(float error, float maximum)
{
if(Float.isNaN(error))
{
return 0.0f;
}
else
{
return clip(error, maximum);
}
}
/**

View File

@@ -100,7 +100,7 @@ public class DQPSKGardnerSymbolEvaluator implements IPSKSymbolEvaluator<Dibit>
//Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the
//arctan of the error angle relative to 0 radians and this provides our error value
mPhaseError = -mEvaluationSymbol.quadrature();
mPhaseError = normalize(-mEvaluationSymbol.quadrature(), 0.3f);
}
/**

View File

@@ -20,11 +20,13 @@ package io.github.dsheirer.dsp.psk.pll;
*/
public enum PLLGain
{
LEVEL_1(50.0, 0, 1),
LEVEL_2(75.0, 2, 4),
LEVEL_3(100.0, 5, 6),
LEVEL_4(125.0, 7, 8),
LEVEL_5(150.0, 9, 10);
//NOTE: static gain level of 200 produced the best results for releases prior to 0.3.4b2
LEVEL_1(150.0, 0, 1),
LEVEL_2(170.0, 2, 4),
LEVEL_3(190.0, 5, 6),
LEVEL_4(200.0, 7, 8),
LEVEL_5(200.0, 9, 10);
private double mLoopBandwidth;
private int mRangeStart;

View File

@@ -0,0 +1,125 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.symbol;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Assembles reusable byte buffers from an incoming stream of Dibits.
*/
public class DibitToByteBufferAssembler implements Listener<Dibit>, IReusableByteBufferProvider
{
private final static Logger mLog = LoggerFactory.getLogger(DibitToByteBufferAssembler.class);
private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("DibitToByteBufferAssembler");
private ReusableByteBuffer mCurrentBuffer;
private int mBufferPointer;
private int mBufferSize;
private byte mCurrentByte;
private int mDibitCount;
private Listener<ReusableByteBuffer> mBufferListener;
/**
* Constructs an assembler to produce reusable byte buffers of the specified size
*
* @param bufferSize
*/
public DibitToByteBufferAssembler(int bufferSize)
{
mBufferSize = bufferSize;
getNextBuffer();
}
/**
* Broadcasts the current buffer to the registered listener and creates a new buffer, resetting
* the buffer pointer to zero so that new dibits can be loaded.
*/
private void getNextBuffer()
{
if(mCurrentBuffer != null && mBufferListener != null)
{
mBufferListener.receive(mCurrentBuffer);
}
mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize);
mBufferPointer = 0;
}
@Override
public void receive(Dibit dibit)
{
mCurrentByte <<= 2;
switch(dibit)
{
case D01_PLUS_3:
mCurrentByte |= 0x01;
break;
case D00_PLUS_1:
//no-op - value is already 00
break;
case D10_MINUS_1:
mCurrentByte |= 0x02;
break;
case D11_MINUS_3:
mCurrentByte |= 0x03;
break;
}
mDibitCount++;
if(mDibitCount >= 4)
{
mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte;
mCurrentByte = 0x00;
mDibitCount = 0;
if(mBufferPointer >= mBufferSize)
{
getNextBuffer();
}
}
}
/**
* Registers the listener to receive fully assembled byte buffers from this assembler.
*/
@Override
public void setBufferListener(Listener<ReusableByteBuffer> listener)
{
mBufferListener = listener;
}
/**
* Removes the listener from receiving buffers from this assembler
*/
@Override
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
{
mBufferListener = null;
}
@Override
public boolean hasBufferListeners()
{
return mBufferListener != null;
}
}

View File

@@ -259,7 +259,8 @@ public class ChannelizerViewer extends JFrame
for(int x = 0; x < mChannelCount; x++)
{
TunerChannelSource source = mTestTuner.getChannelSourceManager().getTestingSource(x);
TunerChannel tunerChannel = new TunerChannel(100000000, 12500);
TunerChannelSource source = mTestTuner.getChannelSourceManager().getSource(tunerChannel, null);
DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x);
channelPanel.setDFTSize(mChannelPanelDFTSize);
@@ -361,7 +362,7 @@ public class ChannelizerViewer extends JFrame
mComplexDecibelConverter.addListener(mSpectrumPanel);
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, null);
if(mSource != null)
{
@@ -555,7 +556,7 @@ public class ChannelizerViewer extends JFrame
{
if(sourceCount < maxSourceCount)
{
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel);
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel, null);
if(source != null)
{

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
@@ -376,10 +377,11 @@ public class ChannelizerViewer2 extends JFrame
mComplexDecibelConverter.addListener(mSpectrumPanel);
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, new ChannelSpecification(50000, 12500, 6000, 7000));
if(mSource != null)
{
mLog.debug("Channel: " + mSource.getTunerChannel() + " Rate:" + mSource.getSampleRate());
mSource.setListener(new Listener<ReusableComplexBuffer>()
{
@Override

View File

@@ -281,7 +281,7 @@ public class SynthesizerViewer extends JFrame
{
try
{
float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 12);
float[] taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 12);
mSynthesizer = new TwoChannelSynthesizerM2(taps);
}
catch(FilterDesignException fde)

View File

@@ -34,6 +34,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.prefs.Preferences;
/**
* Application for viewing and working with demodulators and recording files.
@@ -42,6 +43,9 @@ public class DemodulatorViewerFX extends Application
{
private final static Logger mLog = LoggerFactory.getLogger(DemodulatorViewerFX.class);
private static final String PREFERENCE_FILE_OPEN_DEFAULT_FOLDER = "default.folder";
private Preferences mPreferences = Preferences.userNodeForPackage(DemodulatorViewerFX.class);
private Stage mStage;
private MenuBar mMenuBar;
private RecentFilesMenu mRecentFilesMenu;
@@ -117,9 +121,22 @@ public class DemodulatorViewerFX extends Application
{
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open an I/Q recording file");
String directory = mPreferences.get(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, null);
if(directory != null)
{
fileChooser.setInitialDirectory(new File(directory));
}
File file = fileChooser.showOpenDialog(mStage);
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
if(file != null)
{
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
mPreferences.put(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, file.getParent());
}
}
});

View File

@@ -17,8 +17,8 @@ package io.github.dsheirer.gui.instrument;
import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.source.IControllableFileSource;
import io.github.dsheirer.source.IFrameLocationListener;
import io.github.dsheirer.source.wave.ComplexWaveSource;
@@ -177,7 +177,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener
/**
* Adds listener to receive complex buffers from this playback
*/
public void addListener(Listener<ReusableBuffer> listener)
public void addListener(Listener<ReusableFloatBuffer> listener)
{
mReusableBufferBroadcaster.addListener(listener);
}
@@ -185,7 +185,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener
/**
* Removes the listener from receiving complex buffers from this playback
*/
public void removeListener(Listener<ReusableBuffer> listener)
public void removeListener(Listener<ReusableFloatBuffer> listener)
{
mReusableBufferBroadcaster.removeListener(listener);
}

View File

@@ -26,6 +26,7 @@ import java.util.prefs.Preferences;
public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
{
private static final String PREFERENCE_RECENT_FILE = ".recent.file.";
private Preferences mPreferences = Preferences.userNodeForPackage(RecentFilesMenu.class);
private IFileSelectionListener mFileSelectionListener;
private String mIdentifier;
@@ -111,7 +112,7 @@ public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
*/
private String getKey(int index)
{
return mIdentifier + ".recent.file." + index;
return mIdentifier + PREFERENCE_RECENT_FILE + index;
}
/**

View File

@@ -21,6 +21,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ValueAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +43,24 @@ public class DoubleLineChart extends LineChart implements Listener<Double>
init(length);
}
/**
* Sets the minimum displayable value for the y axis
* @param min
*/
public void setMin(double min)
{
((ValueAxis)getYAxis()).setLowerBound(min);
}
/**
* Sets the maximum displayable value for the y axis
* @param max
*/
public void setMax(double max)
{
((ValueAxis)getYAxis()).setUpperBound(max);
}
private void init(int length)
{
mDoubleCircularBuffer = new DoubleCircularBuffer(length);

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.buffer.RealCircularBuffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
@@ -27,7 +27,7 @@ import javafx.scene.layout.StackPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RealSampleLineChart extends LineChart implements Listener<ReusableBuffer>
public class RealSampleLineChart extends LineChart implements Listener<ReusableFloatBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(RealSampleLineChart.class);
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
@@ -65,7 +65,7 @@ public class RealSampleLineChart extends LineChart implements Listener<ReusableB
}
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
for(float sample: buffer.getSamples())
{

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import javafx.collections.ObservableList;
public class SamplesPerSymbolChart extends DoubleLineChart
{
private double mFractionalSamplesPerSymbol;
private double mPreviousSamplesPerSymbol;
public SamplesPerSymbolChart(int length, double samplesPerSymbol)
{
super("Samples Per Symbol", -1.0, 1.0, 0.1, length);
setSamplesPerSymbol(samplesPerSymbol);
}
public void setSamplesPerSymbol(double samplesPerSymbol)
{
mFractionalSamplesPerSymbol = samplesPerSymbol - ((int)samplesPerSymbol);
mPreviousSamplesPerSymbol = samplesPerSymbol;
ObservableList<Series> seriesList = getData();
seriesList.get(0).setName("Samples Per Symbol:" + samplesPerSymbol);
}
@Override
public void receive(Double samplesPerSymbol)
{
double delta = samplesPerSymbol - mPreviousSamplesPerSymbol - mFractionalSamplesPerSymbol;
while(delta < (-1.0 + mFractionalSamplesPerSymbol))
{
delta++;
}
while(delta > 1.0)
{
delta--;
}
super.receive(delta);
mPreviousSamplesPerSymbol = samplesPerSymbol;
}
}

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
@@ -52,7 +52,7 @@ public abstract class AbstractAFSK1200Pane extends RealDecoderPane
private void init()
{
//This is force-cast without type checking ....
addListener((Listener<ReusableBuffer>)getDecoder());
addListener((Listener<ReusableFloatBuffer>)getDecoder());
addListener(getSampleLineChart());

View File

@@ -17,10 +17,10 @@ package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.scene.layout.VBox;
public abstract class AbstractDecoderPane<T extends ReusableBuffer> extends VBox implements Listener<T>
public abstract class AbstractDecoderPane<T extends ReusableFloatBuffer> extends VBox implements Listener<T>
{
abstract SampleType getSampleType();

View File

@@ -19,6 +19,7 @@ import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
import io.github.dsheirer.gui.instrument.chart.SamplesPerSymbolChart;
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.decode.DecoderType;
@@ -34,14 +35,14 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class);
private HBox mSampleChartBox;
private ComplexSampleLineChart mSampleLineChart;
private HBox mTopChartBox;
private ComplexSampleLineChart mDifferentialDemodulatedSamplesChartBox;
private EyeDiagramChart mEyeDiagramChart;
private HBox mDecoderChartBox;
private SymbolChart mSymbolChart;
private HBox mBottomChartBox;
private SymbolChart mSymbolConstellationChart;
private PhaseLineChart mPLLPhaseErrorLineChart;
private DoubleLineChart mPLLFrequencyLineChart;
private DoubleLineChart mSamplesPerSymbolLineChart;
private SamplesPerSymbolChart mSamplesPerSymbolLineChart;
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
private P25DecoderLSMInstrumented mDecoder = new P25DecoderLSMInstrumented(null);
@@ -56,16 +57,16 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
addListener(getDecoder());
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
getDecoder().setComplexSymbolListener(getSymbolChart());
getDecoder().setComplexSymbolListener(getSymbolConstellationChart());
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
mFilteredBufferBroadcaster.addListener(getDifferentialDemodulatedSamplesChartBox());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
HBox.setHgrow(getTopChartBox(), Priority.ALWAYS);
HBox.setHgrow(getBottomChartBox(), Priority.ALWAYS);
getChildren().addAll(getTopChartBox(), getBottomChartBox());
mDecoder.setMessageListener(new Listener<Message>()
{
@@ -86,7 +87,8 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
mDecoder.setSampleRate(sampleRate);
double samplesPerSymbol = sampleRate / 4800.0;
getSampleLineChart().setSamplesPerSymbol((int)samplesPerSymbol);
getDifferentialDemodulatedSamplesChartBox().setSamplesPerSymbol((int)samplesPerSymbol);
getSamplesPerSymbolLineChart().setSamplesPerSymbol(samplesPerSymbol);
}
private P25DecoderLSMInstrumented getDecoder()
@@ -94,69 +96,69 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
return mDecoder;
}
private SymbolChart getSymbolChart()
private SymbolChart getSymbolConstellationChart()
{
if(mSymbolChart == null)
if(mSymbolConstellationChart == null)
{
mSymbolChart = new SymbolChart(10);
mSymbolConstellationChart = new SymbolChart(10);
}
return mSymbolChart;
return mSymbolConstellationChart;
}
private HBox getDecoderChartBox()
private HBox getBottomChartBox()
{
if(mDecoderChartBox == null)
if(mBottomChartBox == null)
{
mDecoderChartBox = new HBox();
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
mBottomChartBox = new HBox();
mBottomChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolConstellationChart().setMaxWidth(Double.MAX_VALUE);
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
HBox.setHgrow(getSymbolConstellationChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
mBottomChartBox.getChildren().addAll(getSymbolConstellationChart(), getPLLPhaseErrorLineChart(),
getPLLFrequencyLineChart());
}
return mDecoderChartBox;
return mBottomChartBox;
}
private HBox getSampleChartBox()
private HBox getTopChartBox()
{
if(mSampleChartBox == null)
if(mTopChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
mTopChartBox = new HBox();
mTopChartBox.setMaxHeight(Double.MAX_VALUE);
getDifferentialDemodulatedSamplesChartBox().setMaxWidth(Double.MAX_VALUE);
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
HBox.setHgrow(getDifferentialDemodulatedSamplesChartBox(), Priority.ALWAYS);
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
mTopChartBox.getChildren().addAll(getDifferentialDemodulatedSamplesChartBox(), getEyeDiagramChart(),
getSamplesPerSymbolLineChart());
}
return mSampleChartBox;
return mTopChartBox;
}
private ComplexSampleLineChart getSampleLineChart()
private ComplexSampleLineChart getDifferentialDemodulatedSamplesChartBox()
{
if(mSampleLineChart == null)
if(mDifferentialDemodulatedSamplesChartBox == null)
{
mSampleLineChart = new ComplexSampleLineChart(100, 10);
mDifferentialDemodulatedSamplesChartBox = new ComplexSampleLineChart(100, 10);
}
return mSampleLineChart;
return mDifferentialDemodulatedSamplesChartBox;
}
private EyeDiagramChart getEyeDiagramChart()
{
if(mEyeDiagramChart == null)
{
mEyeDiagramChart = new EyeDiagramChart(10, "Middle: 3-4 / Symbol: 8-9");
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol/Eye Diagram");
}
return mEyeDiagramChart;
@@ -182,11 +184,11 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
return mPLLFrequencyLineChart;
}
private DoubleLineChart getSamplesPerSymbolLineChart()
private SamplesPerSymbolChart getSamplesPerSymbolLineChart()
{
if(mSamplesPerSymbolLineChart == null)
{
mSamplesPerSymbolLineChart = new DoubleLineChart( "Sample Point", 4.5, 7.0, 0.1, 40);
mSamplesPerSymbolLineChart = new SamplesPerSymbolChart(40, 6.25);
}
return mSamplesPerSymbolLineChart;

View File

@@ -18,11 +18,11 @@ package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.scene.text.Text;
public class RealDecoderPane extends AbstractDecoderPane<ReusableBuffer>
public class RealDecoderPane extends AbstractDecoderPane<ReusableFloatBuffer>
{
private ReusableBufferBroadcaster mBufferBroadcaster = new ReusableBufferBroadcaster();
private DecoderType mDecoderType;
@@ -47,17 +47,17 @@ public class RealDecoderPane extends AbstractDecoderPane<ReusableBuffer>
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mBufferBroadcaster.broadcast(reusableBuffer);
mBufferBroadcaster.broadcast(reusableFloatBuffer);
}
public void addListener(Listener<ReusableBuffer> listener)
public void addListener(Listener<ReusableFloatBuffer> listener)
{
mBufferBroadcaster.addListener(listener);
}
public void removeListener(Listener<ReusableBuffer> listener)
public void removeListener(Listener<ReusableFloatBuffer> listener)
{
mBufferBroadcaster.removeListener(listener);
}

View File

@@ -49,11 +49,14 @@ import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableByteBufferListener;
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.source.ComplexSource;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
@@ -93,8 +96,9 @@ public class ProcessingChain implements IChannelEventListener
private final static Logger mLog = LoggerFactory.getLogger(ProcessingChain.class);
private ReusableBufferBroadcaster<ReusableAudioPacket> mAudioPacketBroadcaster = new ReusableBufferBroadcaster<>();
private ReusableBufferBroadcaster<ReusableBuffer> mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
private ReusableBufferBroadcaster<ReusableFloatBuffer> mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
private ReusableBufferBroadcaster<ReusableComplexBuffer> mBasebandComplexBufferBroadcaster = new ReusableBufferBroadcaster();
private ReusableBufferBroadcaster<ReusableByteBuffer> mDemodulatedBitstreamBufferBroadcaster = new ReusableBufferBroadcaster();
private Broadcaster<AttributeChangeRequest> mAttributeChangeRequestBroadcaster = new Broadcaster<>();
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster<>();
@@ -163,6 +167,7 @@ public class ProcessingChain implements IChannelEventListener
mCallEventBroadcaster.dispose();
mChannelEventBroadcaster.dispose();
mBasebandComplexBufferBroadcaster.dispose();
mDemodulatedBitstreamBufferBroadcaster.dispose();
mMessageBroadcaster.dispose();
mSquelchStateBroadcaster.dispose();
}
@@ -323,6 +328,11 @@ public class ProcessingChain implements IChannelEventListener
mDemodulatedAudioBufferBroadcaster.addListener(((IReusableBufferListener)module).getReusableBufferListener());
}
if(module instanceof IReusableByteBufferListener)
{
mDemodulatedBitstreamBufferBroadcaster.addListener(((IReusableByteBufferListener)module).getReusableByteBufferListener());
}
if(module instanceof IReusableComplexBufferListener)
{
mBasebandComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener());
@@ -385,6 +395,11 @@ public class ProcessingChain implements IChannelEventListener
mDemodulatedAudioBufferBroadcaster.removeListener(((IReusableBufferListener)module).getReusableBufferListener());
}
if(module instanceof IReusableByteBufferListener)
{
mDemodulatedBitstreamBufferBroadcaster.removeListener(((IReusableByteBufferListener) module).getReusableByteBufferListener());
}
if(module instanceof IReusableComplexBufferListener)
{
mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
@@ -452,6 +467,11 @@ public class ProcessingChain implements IChannelEventListener
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
}
if(module instanceof IReusableByteBufferProvider)
{
((IReusableByteBufferProvider)module).setBufferListener(mDemodulatedBitstreamBufferBroadcaster);
}
if(module instanceof IReusableBufferProvider)
{
((IReusableBufferProvider)module).setBufferListener(mDemodulatedAudioBufferBroadcaster);
@@ -484,6 +504,11 @@ public class ProcessingChain implements IChannelEventListener
((IAudioPacketProvider) module).setAudioPacketListener(null);
}
if(module instanceof IReusableByteBufferProvider)
{
((IReusableByteBufferProvider)module).removeBufferListener(mDemodulatedBitstreamBufferBroadcaster);
}
if(module instanceof ICallEventProvider)
{
((ICallEventProvider) module).removeCallEventListener(mCallEventBroadcaster);
@@ -540,16 +565,10 @@ public class ProcessingChain implements IChannelEventListener
{
if(mSource != null)
{
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
//Broadcast the source sample rate so that each of the modules can self-configure
mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup"));
//Broadcast the source center frequencysample rate so that each of the modules can self-configure
//Broadcast the source center frequency / sample rate so that each of the modules can self-configure
mSourceEventBroadcaster.broadcast(SourceEvent.frequencyChange(mSource.getFrequency(), "Processing Chain Startup"));
//Setup the channel state to monitor source overflow conditions
@@ -627,6 +646,12 @@ public class ProcessingChain implements IChannelEventListener
{
module.stop();
}
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
}
}
@@ -790,7 +815,7 @@ public class ProcessingChain implements IChannelEventListener
/**
* Adds listener to receive demodulated audio buffers from an modules that produce demodulated audio.
*/
public void addDemodulatedAudioListener(Listener<ReusableBuffer> listener)
public void addDemodulatedAudioListener(Listener<ReusableFloatBuffer> listener)
{
mDemodulatedAudioBufferBroadcaster.addListener(listener);
}
@@ -798,7 +823,7 @@ public class ProcessingChain implements IChannelEventListener
/**
* Removes the listener from receiving demodulated audio buffers.
*/
public void removeDemodulatedAudioListener(Listener<ReusableBuffer> listener)
public void removeDemodulatedAudioListener(Listener<ReusableFloatBuffer> listener)
{
mDemodulatedAudioBufferBroadcaster.removeListener(listener);
}

View File

@@ -93,6 +93,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
public class DecoderFactory
@@ -346,7 +347,6 @@ public class DecoderFactory
filters.add(new MPT1327MessageFilter());
break;
case P25_PHASE1:
case P25_PHASE2:
filters.add(new P25MessageFilterSet());
break;
case PASSPORT:
@@ -467,4 +467,14 @@ public class DecoderFactory
return null;
}
/**
* Decoder(s) that support bitstreams.
*
* @return set of decoders that support bitstreams.
*/
public static EnumSet<DecoderType> getBitstreamDecoders()
{
return EnumSet.of(DecoderType.P25_PHASE1);
}
}

View File

@@ -1,19 +1,17 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.module.decode;
@@ -25,37 +23,28 @@ import java.util.EnumSet;
*/
public enum DecoderType
{
ACARS( "ACARS", "ACARS", "images/acars.png", 12500 ),
AIS( "AIS", "AIS", "images/ais.png", 12500 ),
AM( "AM", "AM", "images/am.png", 10000 ),
APRS( "APRS", "APRS", "images/aprs.png", 12500 ),
DMR( "DMR", "DMR", "images/dmr.png", 12500 ),
FLEETSYNC2( "Fleetsync II", "Fleetsync2", "images/fm.png", 12500 ),
LJ_1200( "LJ1200 173.075", "LJ1200", "images/lj1200.png", 12500 ),
LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr.png", 10000 ),
LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net.png", 12500 ),
MDC1200( "MDC1200", "MDC1200", "images/fm.png", 12500 ),
MOTOROLA_TYPE_2( "MOTO T2", "MOTO T2", "images/motorola_type_2.png", 15000 ),
MPT1327( "MPT1327", "MPT1327", "images/mpt1327.png", 12500 ),
NXDN( "NXDN", "NXDN", "images/nxdn.png", 8000 ),
NBFM( "NBFM", "NBFM", "images/fm.png", 12500 ),
PASSPORT( "Passport", "Passport", "images/passport.png", 12500 ),
P25_PHASE1( "P25 Phase I", "P25-1", "images/p25_1.png", 12500 ),
P25_PHASE2( "P25 Phase II", "P25-2", "images/p25_2.png", 15000 ),
TAIT_1200( "Tait 1200", "Tait 1200", "images/tait.png", 12500 );
//Primary Decoders
AM("AM", "AM"),
LTR_STANDARD("LTR-Standard", "LTR"),
LTR_NET("LTR-Net", "LTR-Net"),
MPT1327("MPT1327", "MPT1327"),
NBFM("NBFM", "NBFM"),
PASSPORT("Passport", "Passport"),
P25_PHASE1("P25 Phase I", "P25-1"),
//Auxiliary Decoders
FLEETSYNC2("Fleetsync II", "Fleetsync2"),
LJ_1200("LJ1200 173.075", "LJ1200"),
MDC1200("MDC1200", "MDC1200"),
TAIT_1200("Tait 1200", "Tait 1200");
private String mDisplayString;
private String mShortDisplayString;
private String mIconFilename;
private int mChannelBandwidth;
DecoderType( String displayString, String shortDisplayString,
String iconFilename, int bandwidth )
DecoderType(String displayString, String shortDisplayString)
{
mDisplayString = displayString;
mShortDisplayString = shortDisplayString;
mIconFilename = iconFilename;
mChannelBandwidth = bandwidth;
}
/**
@@ -63,68 +52,43 @@ public enum DecoderType
*/
public static EnumSet<DecoderType> getPrimaryDecoders()
{
return EnumSet.of( DecoderType.AM,
DecoderType.LTR_NET,
DecoderType.LTR_STANDARD,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.P25_PHASE1,
DecoderType.PASSPORT );
return EnumSet.of(DecoderType.AM,
DecoderType.LTR_NET,
DecoderType.LTR_STANDARD,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.P25_PHASE1,
DecoderType.PASSPORT);
}
/**
* Decoders that can be used in the viewer application
*/
public static EnumSet<DecoderType> getInstrumentableDecoders()
{
return EnumSet.of( DecoderType.FLEETSYNC2,
DecoderType.LJ_1200,
DecoderType.LTR_NET,
DecoderType.MDC1200,
DecoderType.MPT1327,
DecoderType.PASSPORT,
DecoderType.P25_PHASE1,
DecoderType.TAIT_1200 );
}
/**
* Available auxiliary decoders.
*/
public static ArrayList<DecoderType> getAuxDecoders()
{
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
decoders.add( DecoderType.FLEETSYNC2 );
decoders.add( DecoderType.LJ_1200 );
decoders.add( DecoderType.MDC1200 );
decoders.add( DecoderType.TAIT_1200 );
return decoders;
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
decoders.add(DecoderType.FLEETSYNC2);
decoders.add(DecoderType.LJ_1200);
decoders.add(DecoderType.MDC1200);
decoders.add(DecoderType.TAIT_1200);
return decoders;
}
public String getDisplayString()
{
return mDisplayString;
}
public String getShortDisplayString()
{
return mShortDisplayString;
return mShortDisplayString;
}
public String getIconFilename()
{
return mIconFilename;
}
public int getChannelBandwidth()
{
return mChannelBandwidth;
}
@Override
public String toString()
{
return mDisplayString;
return mDisplayString;
}
}

View File

@@ -19,7 +19,7 @@ import io.github.dsheirer.dsp.afsk.AFSK1200Decoder;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Abstract class for Audio Frequency Shift Keying (AFSK) 1200-baud decoder based decoder modules.
@@ -28,7 +28,7 @@ import io.github.dsheirer.sample.buffer.ReusableBuffer;
*
* Sub-class implementations should invoke getDecoder().setListener() to receive decoded symbol stream.
*/
public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
{
private AFSK1200Decoder mAFSK1200Decoder;
@@ -48,13 +48,13 @@ public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBu
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mAFSK1200Decoder.receive(reusableBuffer);
mAFSK1200Decoder.receive(reusableFloatBuffer);
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
return this;
}

View File

@@ -17,9 +17,11 @@
******************************************************************************/
package io.github.dsheirer.module.decode.am;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigAM extends DecodeConfiguration
{
@@ -27,6 +29,17 @@ public class DecodeConfigAM extends DecodeConfiguration
{
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
3000, 3000.0, 5000.0);
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public DecoderType getDecoderType()
{

View File

@@ -29,6 +29,7 @@ import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@@ -58,4 +59,7 @@ public abstract class DecodeConfiguration extends Configuration
@JsonIgnore
public abstract DecoderType getDecoderType();
@JsonIgnore
public abstract ChannelSpecification getChannelSpecification();
}

View File

@@ -30,10 +30,17 @@ import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
@@ -86,64 +93,6 @@ public class MessageActivityPanel extends JPanel implements Listener<ProcessingC
});
}
// @Override
// public void channelChanged(ChannelEvent event)
// {
// boolean changed = false;
//
// if(event.getEvent() == Event.NOTIFICATION_SELECTION_CHANGE &&
// event.getChannel().isSelected())
// {
// if(mDisplayedChannel == null || (mDisplayedChannel != null && mDisplayedChannel != event.getChannel()))
// {
// mDisplayedChannel = event.getChannel();
//
// ProcessingChain chain = mChannelProcessingManager.getProcessingChain(mDisplayedChannel);
//
// if(chain != null)
// {
// mDisplayedModel = chain.getMessageActivityModel();
// }
// else
// {
// mDisplayedModel = EMPTY_MODEL;
// }
//
// mMessageTable.setModel(mDisplayedModel);
//
// if(mDisplayedChannel != null)
// {
// mManagementPanel.enableButtons();
// }
// else
// {
// mManagementPanel.disableButtons();
// }
//
// changed = true;
// }
// }
// else if(event.getEvent() == Event.NOTIFICATION_PROCESSING_STOP || event.getEvent() == Event.REQUEST_DISABLE)
// {
// if(mDisplayedChannel != null && mDisplayedChannel == event.getChannel())
// {
// mDisplayedChannel = null;
//
// mMessageTable.setModel(EMPTY_MODEL);
//
// mManagementPanel.disableButtons();
//
// changed = true;
// }
// }
//
// if(changed)
// {
// revalidate();
// repaint();
// }
// }
public class MessageManagementPanel extends JPanel
{
private static final long serialVersionUID = 1L;

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder implements
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.lj1200;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class LJ1200DecoderInstrumented extends LJ1200Decoder implements IInstrum
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -17,10 +17,12 @@
******************************************************************************/
package io.github.dsheirer.module.decode.ltrnet;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigLTRNet extends DecodeConfiguration
{
@@ -46,4 +48,15 @@ public class DecodeConfigLTRNet extends DecodeConfiguration
{
mMessageDirection = direction;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

View File

@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
{
public static final int LTR_NET_MESSAGE_LENGTH = 40;
protected LTRDecoder mLTRDecoder;
@@ -73,15 +73,15 @@ public class LTRNetDecoder extends Decoder implements IReusableBufferListener, L
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
return mLTRDecoder;
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mLTRDecoder.receive(reusableBuffer);
mLTRDecoder.receive(reusableFloatBuffer);
}
@Override

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.ltrnet;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.dsp.fsk.LTRDecoderInstrumented;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
public class LTRNetDecoderInstrumented extends LTRNetDecoder
@@ -35,9 +35,9 @@ public class LTRNetDecoderInstrumented extends LTRNetDecoder
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -17,10 +17,12 @@
******************************************************************************/
package io.github.dsheirer.module.decode.ltrstandard;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigLTRStandard extends DecodeConfiguration
{
@@ -47,4 +49,15 @@ public class DecodeConfigLTRStandard extends DecodeConfiguration
{
mMessageDirection = direction;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

View File

@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
{
public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
@@ -65,15 +65,15 @@ public class LTRStandardDecoder extends Decoder implements IReusableBufferListen
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
return mLTRDecoder;
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mLTRDecoder.receive(reusableBuffer);
mLTRDecoder.receive(reusableFloatBuffer);
}
@Override

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.mdc1200;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class MDCDecoderInstrumented extends MDCDecoder implements IInstrumentedA
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -17,9 +17,11 @@
******************************************************************************/
package io.github.dsheirer.module.decode.mpt1327;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigMPT1327 extends DecodeConfiguration
{
@@ -102,4 +104,15 @@ public class DecodeConfigMPT1327 extends DecodeConfiguration
{
mTrafficChannelPoolSize = size;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.mpt1327;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class MPT1327DecoderInstrumented extends MPT1327Decoder implements IInstr
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -17,12 +17,16 @@
******************************************************************************/
package io.github.dsheirer.module.decode.nbfm;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigNBFM extends DecodeConfiguration
{
private Bandwidth mBandwidth = Bandwidth.BW_12_5;
public DecodeConfigNBFM()
{
}
@@ -33,4 +37,53 @@ public class DecodeConfigNBFM extends DecodeConfiguration
return DecoderType.NBFM;
}
/**
* Channel bandwidth
*/
@JacksonXmlProperty(isAttribute = true, localName = "bandwidth")
public Bandwidth getBandwidth()
{
return mBandwidth;
}
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
if(mBandwidth == Bandwidth.BW_12_5)
{
return new ChannelSpecification(25000.0, 12500, 6000.0, 7000.0);
}
else
{
return new ChannelSpecification(50000.0, 25000, 12500.0, 13500.0);
}
}
/**
* Sets the channel bandwidth
*/
public void setBandwidth(Bandwidth bandwidth)
{
mBandwidth = bandwidth;
}
public enum Bandwidth
{
BW_12_5("12.5 kHz"),
BW_25_0("25.0 kHz");
private String mLabel;
Bandwidth(String label)
{
mLabel = label;
}
@Override
public String toString()
{
return mLabel;
}
};
}

View File

@@ -1,19 +1,17 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.module.decode.nbfm;
@@ -24,70 +22,94 @@ import io.github.dsheirer.gui.editor.ValidatingEditor;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import net.miginfocom.swing.MigLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class NBFMDecoderEditor extends ValidatingEditor<Channel>
{
private static final long serialVersionUID = 1L;
public NBFMDecoderEditor()
{
init();
}
private void init()
{
setLayout( new MigLayout( "insets 0 0 0 0,wrap 2", "[right][grow,fill]", "" ) );
add( new JLabel( "Narrow Band FM Decoder" ) );
}
@Override
public void validate( Editor<Channel> editor ) throws EditorValidationException
{
//No validation
}
private JComboBox<DecodeConfigNBFM.Bandwidth> mComboBandwidth;
@Override
public void save()
{
if( hasItem() && isModified() )
{
DecodeConfigNBFM nbfm = new DecodeConfigNBFM();
getItem().setDecodeConfiguration( nbfm );
}
setModified( false );
}
public NBFMDecoderEditor()
{
init();
}
private void setControlsEnabled( boolean enabled )
{
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0,wrap 2", "[right][grow,fill]", ""));
@Override
public void setItem( Channel item )
{
super.setItem( item );
if( hasItem() )
{
setControlsEnabled( true );
DecodeConfiguration config = getItem().getDecodeConfiguration();
if( config instanceof DecodeConfigNBFM )
{
DecodeConfigNBFM nbfm = (DecodeConfigNBFM)config;
setModified( false );
}
else
{
setModified( true );
}
}
else
{
setControlsEnabled( false );
setModified( false );
}
}
mComboBandwidth = new JComboBox<>();
mComboBandwidth.setModel(new DefaultComboBoxModel<DecodeConfigNBFM.Bandwidth>(
DecodeConfigNBFM.Bandwidth.values()));
mComboBandwidth.setEnabled(false);
mComboBandwidth.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(new JLabel("Bandwidth:"));
add(mComboBandwidth);
}
@Override
public void validate(Editor<Channel> editor) throws EditorValidationException
{
//No validation
}
@Override
public void save()
{
if(hasItem() && isModified())
{
DecodeConfigNBFM nbfm = new DecodeConfigNBFM();
nbfm.setBandwidth((DecodeConfigNBFM.Bandwidth)mComboBandwidth.getSelectedItem());
getItem().setDecodeConfiguration(nbfm);
}
setModified(false);
}
private void setControlsEnabled(boolean enabled)
{
mComboBandwidth.setEnabled(enabled);
}
@Override
public void setItem(Channel item)
{
super.setItem(item);
if(hasItem())
{
setControlsEnabled(true);
DecodeConfiguration config = getItem().getDecodeConfiguration();
if(config instanceof DecodeConfigNBFM)
{
DecodeConfigNBFM nbfm = (DecodeConfigNBFM)config;
mComboBandwidth.setSelectedItem(nbfm.getBandwidth());
setModified(false);
}
else
{
setModified(true);
}
}
else
{
setControlsEnabled(false);
setModified(false);
}
}
}

View File

@@ -2,13 +2,13 @@ package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.dsp.gain.DirectGainControl;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
import io.github.dsheirer.sample.real.RealSampleProvider;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventListener, RealSampleProvider
public class C4FMSymbolFilter implements Listener<ReusableFloatBuffer>, ISourceEventListener, RealSampleProvider
{
private static final float TAPS[][] =
{
@@ -215,7 +215,7 @@ public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventL
}
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
for(float sample : buffer.getSamples())
{

View File

@@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigP25Phase1 extends DecodeConfiguration
{
@@ -104,4 +105,14 @@ public class DecodeConfigP25Phase1 extends DecodeConfiguration
{
mTrafficChannelPoolSize = size;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(50000.0, 12500, 5750.0, 6500.0);
}
}

View File

@@ -16,19 +16,26 @@
package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
public abstract class P25Decoder extends Decoder implements ISourceEventListener, ISourceEventProvider,
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>, IReusableByteBufferProvider
{
private double mSampleRate;
private Broadcaster<Dibit> mDibitBroadcaster = new Broadcaster<>();
private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300);
private P25MessageProcessor mMessageProcessor;
private AliasList mAliasList;
private Listener<SourceEvent> mSourceEventListener;
@@ -40,6 +47,42 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener
mAliasList = aliasList;
mMessageProcessor = new P25MessageProcessor(mAliasList);
mMessageProcessor.setMessageListener(getMessageListener());
mDibitBroadcaster.addListener(mByteBufferAssembler);
}
/**
* Assembler for packaging Dibit stream into reusable byte buffers.
*/
protected Broadcaster<Dibit> getDibitBroadcaster()
{
return mDibitBroadcaster;
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public void setBufferListener(Listener<ReusableByteBuffer> listener)
{
mByteBufferAssembler.setBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
{
mByteBufferAssembler.removeBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public boolean hasBufferListeners()
{
return mByteBufferAssembler.hasBufferListeners();
}
protected double getSymbolRate()

View File

@@ -71,12 +71,20 @@ public class P25DecoderC4FM extends P25Decoder
mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
if(mMessageFramer != null)
{
getDibitBroadcaster().removeListener(mMessageFramer);
mMessageFramer.dispose();
}
//The Costas Loop receives symbol-inversion correction requests when detected.
//The PLL gain monitor receives sync detect/loss signals from the message framer
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor);
mMessageFramer.setListener(getMessageProcessor());
mMessageFramer.setSampleRate(sampleRate);
mQPSKDemodulator.setSymbolListener(mMessageFramer);
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
/**

View File

@@ -78,7 +78,8 @@ public class P25DecoderC4FMInstrumented extends P25DecoderC4FM
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
instrumented.setSymbolListener(mMessageFramer);
instrumented.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
public void setComplexSymbolListener(Listener<Complex> listener)

View File

@@ -75,10 +75,17 @@ public class P25DecoderLSM extends P25Decoder
//The Costas Loop receives symbol-inversion correction requests when detected.
//The PLL gain monitor receives sync detect/loss signals from the message framer
if(mMessageFramer != null)
{
getDibitBroadcaster().removeListener(mMessageFramer);
mMessageFramer.dispose();
}
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor);
mMessageFramer.setListener(getMessageProcessor());
mMessageFramer.setSampleRate(sampleRate);
mQPSKDemodulator.setSymbolListener(mMessageFramer);
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
/**

View File

@@ -79,7 +79,8 @@ public class P25DecoderLSMInstrumented extends P25DecoderLSM
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
instrumented.setSymbolListener(mMessageFramer);
instrumented.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
public void setComplexSymbolListener(Listener<Complex> listener)

View File

@@ -254,6 +254,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent != null)
{
mCurrentCallEvent.end();
broadcast(mCurrentCallEvent);
}
mCurrentCallEvent = null;
@@ -395,6 +396,10 @@ public class P25DecoderState extends DecoderState
*/
private void processTDU(TDUMessage tdu)
{
if(mChannelType == ChannelType.STANDARD)
{
broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE));
}
}
/**
@@ -432,19 +437,21 @@ public class P25DecoderState extends DecoderState
*/
private void processTDULC(TDULinkControlMessage tdulc)
{
if(mCurrentCallEvent != null)
{
mCurrentCallEvent.end();
broadcast(mCurrentCallEvent);
mCurrentCallEvent = null;
}
if(tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION)
{
broadcast(new DecoderStateEvent(this, Event.END, State.FADE));
if(mCurrentCallEvent != null)
{
mCurrentCallEvent.end();
mCurrentCallEvent = null;
}
}
else
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE));
}
switch(tdulc.getOpcode())
@@ -495,22 +502,17 @@ public class P25DecoderState extends DecoderState
/* This opcode as handled at the beginning of the method */
break;
case CHANNEL_IDENTIFIER_UPDATE:
//TODO: does the activity summary need this message?
/* This message is handled by the P25MessageProcessor and
* inserted into any channels needing frequency band info */
break;
case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT:
//TODO: does the activity summary need this message?
/* This message is handled by the P25MessageProcessor and
* inserted into any channels needing frequency band info */
break;
case EXTENDED_FUNCTION_COMMAND:
if(tdulc instanceof ExtendedFunctionCommand)
{
ExtendedFunctionCommand efc =
(ExtendedFunctionCommand)tdulc;
ExtendedFunctionCommand efc = (ExtendedFunctionCommand)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
.aliasList(getAliasList())
@@ -606,42 +608,25 @@ public class P25DecoderState extends DecoderState
}
break;
case GROUP_VOICE_CHANNEL_USER:
/* Used on a traffic channel to reflect current call entities */
//Used on a traffic channel to reflect that the channel is currently being used by the
//TO talkgroup, but that channel is currently quiet (ie no voice) */
if(tdulc instanceof GroupVoiceChannelUser)
{
GroupVoiceChannelUser gvcuser =
(GroupVoiceChannelUser)tdulc;
GroupVoiceChannelUser gvcuser = (GroupVoiceChannelUser)tdulc;
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE));
//The from value should always be "000000" indicating that nobody is currently talking
mFromTalkgroupMonitor.reset();
String from = gvcuser.getSourceAddress();
String to = gvcuser.getGroupAddress();
mFromTalkgroupMonitor.process(from);
mToTalkgroupMonitor.process(to);
if(mCurrentCallEvent == null)
{
mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.GROUP_CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details("TDULC GROUP VOICE CHANNEL USER" +
(gvcuser.isEncrypted() ? "ENCRYPTED " : "") +
(gvcuser.isEmergency() ? "EMERGENCY " : ""))
.frequency(mCurrentChannelFrequency)
.from(from)
.to(to)
.build();
broadcast(mCurrentCallEvent);
}
}
break;
case MESSAGE_UPDATE:
if(tdulc instanceof MessageUpdate)
{
MessageUpdate mu =
(MessageUpdate)tdulc;
MessageUpdate mu = (MessageUpdate)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.SDM)
.aliasList(getAliasList())
@@ -748,8 +733,7 @@ public class P25DecoderState extends DecoderState
io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast sccb =
(io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast)tdulc;
String site = sccb.getRFSubsystemID() + "-" +
sccb.getSiteID();
String site = sccb.getRFSubsystemID() + "-" + sccb.getSiteID();
mSiteAttributeMonitor.process(site);
}
@@ -761,11 +745,9 @@ public class P25DecoderState extends DecoderState
case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT:
if(tdulc instanceof SecondaryControlChannelBroadcastExplicit)
{
SecondaryControlChannelBroadcastExplicit sccb =
(SecondaryControlChannelBroadcastExplicit)tdulc;
SecondaryControlChannelBroadcastExplicit sccb = (SecondaryControlChannelBroadcastExplicit)tdulc;
String site = sccb.getRFSubsystemID() + "-" +
sccb.getSiteID();
String site = sccb.getRFSubsystemID() + "-" + sccb.getSiteID();
mSiteAttributeMonitor.process(site);
}
@@ -777,8 +759,7 @@ public class P25DecoderState extends DecoderState
case STATUS_QUERY:
if(tdulc instanceof StatusQuery)
{
StatusQuery sq =
(StatusQuery)tdulc;
StatusQuery sq = (StatusQuery)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.QUERY)
.aliasList(getAliasList())
@@ -816,8 +797,7 @@ public class P25DecoderState extends DecoderState
case TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
if(tdulc instanceof TelephoneInterconnectAnswerRequest)
{
TelephoneInterconnectAnswerRequest tiar =
(TelephoneInterconnectAnswerRequest)tdulc;
TelephoneInterconnectAnswerRequest tiar = (TelephoneInterconnectAnswerRequest)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE)
.aliasList(getAliasList())
@@ -835,8 +815,7 @@ public class P25DecoderState extends DecoderState
if(mChannelType == ChannelType.STANDARD &&
tdulc instanceof TelephoneInterconnectVoiceChannelUser)
{
TelephoneInterconnectVoiceChannelUser tivcu =
(TelephoneInterconnectVoiceChannelUser)tdulc;
TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)tdulc;
String to = tivcu.getAddress();
@@ -860,8 +839,7 @@ public class P25DecoderState extends DecoderState
case UNIT_AUTHENTICATION_COMMAND:
if(tdulc instanceof UnitAuthenticationCommand)
{
UnitAuthenticationCommand uac =
(UnitAuthenticationCommand)tdulc;
UnitAuthenticationCommand uac = (UnitAuthenticationCommand)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
.aliasList(getAliasList())
@@ -877,8 +855,7 @@ public class P25DecoderState extends DecoderState
case UNIT_REGISTRATION_COMMAND:
if(tdulc instanceof UnitRegistrationCommand)
{
UnitRegistrationCommand urc =
(UnitRegistrationCommand)tdulc;
UnitRegistrationCommand urc = (UnitRegistrationCommand)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
.aliasList(getAliasList())
@@ -894,8 +871,7 @@ public class P25DecoderState extends DecoderState
case UNIT_TO_UNIT_ANSWER_REQUEST:
if(tdulc instanceof UnitToUnitAnswerRequest)
{
UnitToUnitAnswerRequest uuar =
(UnitToUnitAnswerRequest)tdulc;
UnitToUnitAnswerRequest uuar = (UnitToUnitAnswerRequest)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE)
.aliasList(getAliasList())
@@ -913,27 +889,12 @@ public class P25DecoderState extends DecoderState
/* Used on traffic channels to indicate the current call entities */
if(tdulc instanceof UnitToUnitVoiceChannelUser)
{
UnitToUnitVoiceChannelUser uuvcu =
(UnitToUnitVoiceChannelUser)tdulc;
UnitToUnitVoiceChannelUser uuvcu = (UnitToUnitVoiceChannelUser)tdulc;
String from = uuvcu.getSourceAddress();
mFromTalkgroupMonitor.process(from);
mFromTalkgroupMonitor.reset();
String to = uuvcu.getTargetAddress();
mToTalkgroupMonitor.process(to);
if(mCurrentCallEvent != null)
{
mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.UNIT_TO_UNIT_CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details((uuvcu.isEncrypted() ? "ENCRYPTED " : "") +
(uuvcu.isEmergency() ? "EMERGENCY " : ""))
.frequency(mCurrentChannelFrequency)
.from(from)
.to(to)
.build();
}
}
break;
default:
@@ -1166,9 +1127,17 @@ public class P25DecoderState extends DecoderState
(io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu;
mFromTalkgroupMonitor.process(gvcuser.getSourceAddress());
mCurrentCallEvent.setFromID(mFromTalkgroupMonitor.getValue());
mToTalkgroupMonitor.process(gvcuser.getGroupAddress());
String to = mToTalkgroupMonitor.getValue();
if(mCurrentCallEvent.getToID() == null || !mCurrentCallEvent.getToID().contentEquals(to))
{
mCurrentCallEvent.setToID(to);
}
if(mChannelType == ChannelType.STANDARD)
{
if(mCurrentCallEvent.getCallEventType() != CallEvent.CallEventType.GROUP_CALL)
@@ -1631,14 +1600,13 @@ public class P25DecoderState extends DecoderState
*/
private void processPDU(PDUMessage pdu)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
if(pdu instanceof PacketData || pdu instanceof PDUTypeUnknown)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
}
else if(pdu instanceof PDUConfirmedMessage)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
PDUConfirmedMessage pduc = (PDUConfirmedMessage)pdu;
switch(pduc.getPDUType())
@@ -1723,6 +1691,11 @@ public class P25DecoderState extends DecoderState
sbTo.append(sud.getUDPDestinationPort());
}
mFromTalkgroupMonitor.reset();
mFromTalkgroupMonitor.process(sbFrom.toString());
mToTalkgroupMonitor.reset();
mToTalkgroupMonitor.process(pduc.getLogicalLinkID());
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL)
@@ -1750,9 +1723,6 @@ public class P25DecoderState extends DecoderState
}
else
{
/* These are alternate trunking control blocks in PDU format */
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL));
switch(pdu.getOpcode())
{
case GROUP_DATA_CHANNEL_GRANT:

View File

@@ -35,9 +35,6 @@ import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class P25MessageFramer implements Listener<Dibit>
{
private final static Logger mLog = LoggerFactory.getLogger(P25MessageFramer.class);
@@ -77,7 +74,7 @@ public class P25MessageFramer implements Listener<Dibit>
private PLLPhaseInversionDetector mInversionDetector180;
private MultiSyncPatternMatcher mMatcher;
private List<P25MessageAssembler> mAssemblers = new ArrayList<P25MessageAssembler>();
private P25MessageAssembler mMessageAssembler = new P25MessageAssembler();
private Listener<Message> mListener;
private AliasList mAliasList;
@@ -104,13 +101,9 @@ public class P25MessageFramer implements Listener<Dibit>
@Override
public void syncDetected()
{
for(P25MessageAssembler assembler : mAssemblers)
if(!mMessageAssembler.isActive())
{
if(!assembler.isActive())
{
assembler.setActive(true);
break;
}
mMessageAssembler.setActive(true);
}
}
@@ -122,17 +115,6 @@ public class P25MessageFramer implements Listener<Dibit>
});
mMatcher.add(mPrimarySyncDetector);
/**
* We use two message assemblers to catch any sync detections, so that
* if we have a false trigger then we still have a chance to catch the
* valid message. If we have two false triggers before the arrival of
* the actual message sync, then the third or subsequent real or false
* sync pattern will produce a debug message indicating no assemblers
* are available..
*/
mAssemblers.add(new P25MessageAssembler());
mAssemblers.add(new P25MessageAssembler());
}
public P25MessageFramer(AliasList aliasList, IPhaseLockedLoop phaseLockedLoop, ISyncDetectListener syncDetectListener)
@@ -172,12 +154,7 @@ public class P25MessageFramer implements Listener<Dibit>
public void dispose()
{
for(P25MessageAssembler a : mAssemblers)
{
a.dispose();
}
mAssemblers.clear();
mMessageAssembler.dispose();
mListener = null;
mAliasList = null;
@@ -201,16 +178,13 @@ public class P25MessageFramer implements Listener<Dibit>
@Override
public void receive(Dibit symbol)
{
for(P25MessageAssembler assembler : mAssemblers)
if(mMessageAssembler.isActive())
{
if(assembler.isActive())
{
assembler.receive(symbol);
mMessageAssembler.receive(symbol);
if(assembler.complete())
{
assembler.reset();
}
if(mMessageAssembler.complete())
{
mMessageAssembler.reset();
}
}
@@ -742,13 +716,9 @@ public class P25MessageFramer implements Listener<Dibit>
mPhaseLockedLoop.correctInversion(mPllCorrection);
/* Since we detected a sync pattern, start a message assembler */
for(P25MessageAssembler assembler : mAssemblers)
if(!mMessageAssembler.isActive())
{
if(!assembler.isActive())
{
assembler.setActive(true);
break;
}
mMessageAssembler.setActive(true);
}
}

View File

@@ -1,3 +1,18 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.alias.AliasList;
@@ -13,8 +28,7 @@ import java.util.HashMap;
public class P25MessageProcessor implements Listener<Message>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageProcessor.class );
private final static Logger mLog = LoggerFactory.getLogger( P25MessageProcessor.class );
private Listener<Message> mMessageListener;

View File

@@ -350,9 +350,13 @@ public class Trellis_3_4_Rate
public void add( Con con )
{
Node current = getLastNode();
Tribit input = INPUT_FROM_CONSTELLATION_MAP
.get( con )[ current.getState().getValue() ];
Tribit input = null;
if(current != null)
{
input = INPUT_FROM_CONSTELLATION_MAP.get( con )[ current.getState().getValue() ];
}
/* A non-null input tribit from the lookup table is valid, otherwise
* create 8 alternate branches from the original path to explore

View File

@@ -1,7 +1,6 @@
/*
* *********************************************************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
@@ -12,13 +11,15 @@
*
* 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 io.github.dsheirer.module.decode.passport;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigPassport extends DecodeConfiguration
{
@@ -31,4 +32,14 @@ public class DecodeConfigPassport extends DecodeConfiguration
{
return DecoderType.PASSPORT;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0, 12500, 6000.0, 7000.0);
}
}

View File

@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
public class PassportDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
public class PassportDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
{
public static final int PASSPORT_MESSAGE_LENGTH = 68;
@@ -52,15 +52,15 @@ public class PassportDecoder extends Decoder implements IReusableBufferListener,
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
return mLTRDecoder;
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mLTRDecoder.receive(reusableBuffer);
mLTRDecoder.receive(reusableFloatBuffer);
}
@Override

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.tait;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class Tait1200DecoderInstrumented extends Tait1200Decoder implements IIns
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -29,8 +29,8 @@ import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
@@ -55,7 +55,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
private double mChannelBandwidth;
private double mOutputSampleRate;
private RealResampler mResampler;
private Listener<ReusableBuffer> mResampledReusableBufferListener;
private Listener<ReusableFloatBuffer> mResampledReusableBufferListener;
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
/**
@@ -109,7 +109,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
public void receive(ReusableComplexBuffer basebandBuffer)
{
ReusableComplexBuffer filteredBuffer = mIQFilter.filter(basebandBuffer);
ReusableBuffer demodulated = mDemodulator.demodulate(filteredBuffer);
ReusableFloatBuffer demodulated = mDemodulator.demodulate(filteredBuffer);
mResampler.resample(demodulated);
}
@@ -117,12 +117,12 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
* Processes resampled audio buffers produced by the resampler.
* @param resampledAudio buffer to process.
*/
private void processResampledAudio(ReusableBuffer resampledAudio)
private void processResampledAudio(ReusableFloatBuffer resampledAudio)
{
if(mResampledReusableBufferListener != null)
{
ReusableBuffer filteredAudio = mLowPassFilter.filter(resampledAudio);
ReusableBuffer gainApplied = mAGC.process(filteredAudio);
ReusableFloatBuffer filteredAudio = mLowPassFilter.filter(resampledAudio);
ReusableFloatBuffer gainApplied = mAGC.process(filteredAudio);
mResampledReusableBufferListener.receive(gainApplied);
}
else
@@ -172,7 +172,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
}
@Override
public void setBufferListener(Listener<ReusableBuffer> listener)
public void setBufferListener(Listener<ReusableFloatBuffer> listener)
{
mResampledReusableBufferListener = listener;
}
@@ -253,10 +253,10 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000);
mResampler.setListener(new Listener<ReusableBuffer>()
mResampler.setListener(new Listener<ReusableFloatBuffer>()
{
@Override
public void receive(ReusableBuffer resampledAudioBuffer)
public void receive(ReusableFloatBuffer resampledAudioBuffer)
{
processResampledAudio(resampledAudioBuffer);
}

View File

@@ -26,8 +26,8 @@ import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
@@ -47,7 +47,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
private FMDemodulator mDemodulator = new FMDemodulator();
private RealResampler mResampler;
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
private Listener<ReusableBuffer> mResampledReusableBufferListener;
private Listener<ReusableFloatBuffer> mResampledReusableBufferListener;
private double mChannelBandwidth;
private double mOutputSampleRate;
@@ -106,7 +106,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
}
@Override
public void setBufferListener(Listener<ReusableBuffer> listener)
public void setBufferListener(Listener<ReusableFloatBuffer> listener)
{
mResampledReusableBufferListener = listener;
}
@@ -128,7 +128,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
}
ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(reusableComplexBuffer);
ReusableBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer);
ReusableFloatBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer);
if(mResampler != null)
{
@@ -204,18 +204,18 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000);
mResampler.setListener(new Listener<ReusableBuffer>()
mResampler.setListener(new Listener<ReusableFloatBuffer>()
{
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
if(mResampledReusableBufferListener != null)
{
mResampledReusableBufferListener.receive(reusableBuffer);
mResampledReusableBufferListener.receive(reusableFloatBuffer);
}
else
{
reusableBuffer.decrementUserCount();
reusableFloatBuffer.decrementUserCount();
}
}
});

View File

@@ -1,19 +1,17 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.record;
@@ -22,7 +20,7 @@ import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.record.config.RecordConfiguration;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.JCheckBox;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
@@ -30,121 +28,168 @@ import java.util.List;
public class RecordConfigurationEditor extends Editor<Channel>
{
private static final long serialVersionUID = 1L;
private JCheckBox mAudioRecorder;
private JCheckBox mBasebandRecorder;
private JCheckBox mTrafficBasebandRecorder;
private JCheckBox mBitstreamRecorder;
private JCheckBox mTrafficBitstreamRecorder;
public RecordConfigurationEditor()
{
init();
}
{
init();
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "", "[][grow]" ) );
mAudioRecorder = new JCheckBox( "Audio" );
mAudioRecorder.setEnabled( false );
mAudioRecorder.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( mAudioRecorder );
setLayout(new MigLayout("fill,wrap 2", "", "[][][][grow]"));
mBasebandRecorder = new JCheckBox( "Baseband I/Q" );
mBasebandRecorder.setEnabled( false );
mBasebandRecorder.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( mBasebandRecorder );
mAudioRecorder = new JCheckBox("Audio");
mAudioRecorder.setEnabled(false);
mAudioRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mAudioRecorder, "wrap");
mTrafficBasebandRecorder = new JCheckBox( "Traffic Channel Baseband I/Q" );
mTrafficBasebandRecorder.setEnabled( false );
mTrafficBasebandRecorder.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( mTrafficBasebandRecorder );
mBasebandRecorder = new JCheckBox("Baseband I/Q");
mBasebandRecorder.setEnabled(false);
mBasebandRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mBasebandRecorder);
mTrafficBasebandRecorder = new JCheckBox("Traffic Channel Baseband I/Q");
mTrafficBasebandRecorder.setEnabled(false);
mTrafficBasebandRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mTrafficBasebandRecorder);
mBitstreamRecorder = new JCheckBox("Demodulated Bitstream");
mBitstreamRecorder.setEnabled(false);
mBitstreamRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mBitstreamRecorder);
mTrafficBitstreamRecorder = new JCheckBox("Traffic Channel Bitstream");
mTrafficBitstreamRecorder.setEnabled(false);
mTrafficBitstreamRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mTrafficBitstreamRecorder);
}
private void setControlsEnabled( boolean enabled )
private void setControlsEnabled(boolean enabled)
{
if( mAudioRecorder.isEnabled() != enabled )
{
mAudioRecorder.setEnabled( enabled );
}
if( mBasebandRecorder.isEnabled() != enabled )
{
mBasebandRecorder.setEnabled( enabled );
}
if( mTrafficBasebandRecorder.isEnabled() != enabled )
{
mTrafficBasebandRecorder.setEnabled( enabled );
}
if(mAudioRecorder.isEnabled() != enabled)
{
mAudioRecorder.setEnabled(enabled);
}
if(mBasebandRecorder.isEnabled() != enabled)
{
mBasebandRecorder.setEnabled(enabled);
}
if(mTrafficBasebandRecorder.isEnabled() != enabled)
{
mTrafficBasebandRecorder.setEnabled(enabled);
}
if(mBitstreamRecorder.isEnabled() != enabled)
{
mBitstreamRecorder.setEnabled(enabled);
}
if(mTrafficBitstreamRecorder.isEnabled() != enabled)
{
mTrafficBitstreamRecorder.setEnabled(enabled);
}
}
@Override
@Override
public void save()
{
if( hasItem() && isModified() )
{
RecordConfiguration config = getItem().getRecordConfiguration();
config.clearRecorders();
if( mAudioRecorder.isSelected() )
{
config.addRecorder( RecorderType.AUDIO );
}
if( mBasebandRecorder.isSelected() )
{
config.addRecorder( RecorderType.BASEBAND );
}
if( mTrafficBasebandRecorder.isSelected() )
{
config.addRecorder( RecorderType.TRAFFIC_BASEBAND );
}
}
setModified( false );
if(hasItem() && isModified())
{
RecordConfiguration config = getItem().getRecordConfiguration();
config.clearRecorders();
if(mAudioRecorder.isSelected())
{
config.addRecorder(RecorderType.AUDIO);
}
if(mBasebandRecorder.isSelected())
{
config.addRecorder(RecorderType.BASEBAND);
}
if(mTrafficBasebandRecorder.isSelected())
{
config.addRecorder(RecorderType.TRAFFIC_BASEBAND);
}
if(mBitstreamRecorder.isSelected())
{
config.addRecorder(RecorderType.DEMODULATED_BIT_STREAM);
}
if(mTrafficBitstreamRecorder.isSelected())
{
config.addRecorder(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM);
}
}
setModified(false);
}
@Override
public void setItem(Channel channel)
{
super.setItem(channel);
if(hasItem())
{
setControlsEnabled(true);
List<RecorderType> recorders = getItem().getRecordConfiguration().getRecorders();
mAudioRecorder.setSelected(recorders.contains(RecorderType.AUDIO));
mBasebandRecorder.setSelected(recorders.contains(RecorderType.BASEBAND));
mTrafficBasebandRecorder.setSelected(recorders.contains(RecorderType.TRAFFIC_BASEBAND));
mBitstreamRecorder.setSelected(recorders.contains(RecorderType.DEMODULATED_BIT_STREAM));
mTrafficBitstreamRecorder.setSelected(recorders.contains(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM));
}
else
{
setControlsEnabled(false);
}
}
@Override
public void setItem( Channel channel )
{
super.setItem( channel );
if( hasItem() )
{
setControlsEnabled( true );
List<RecorderType> recorders = getItem().getRecordConfiguration().getRecorders();
mAudioRecorder.setSelected( recorders.contains( RecorderType.AUDIO ) );
mBasebandRecorder.setSelected( recorders.contains( RecorderType.BASEBAND ) );
mTrafficBasebandRecorder.setSelected( recorders.contains( RecorderType.TRAFFIC_BASEBAND ) );
}
else
{
setControlsEnabled( false );
}
}
}

View File

@@ -1,60 +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 io.github.dsheirer.record;
import java.io.IOException;
public class Recorder
{
RecorderType mRecorderType;
public Recorder()
{
}
public String getFileName()
{
return "No Recording";
}
public void start() throws IOException
{
}
public void stop() throws IOException
{
}
public void pause()
{
}
public void resume()
{
}
public Recorder( RecorderType type )
{
mRecorderType = type;
}
public RecorderType getRecorderType()
{
return mRecorderType;
}
}

View File

@@ -1,7 +1,6 @@
/*
* *********************************************************************************************************************
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
@@ -12,8 +11,8 @@
*
* 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 io.github.dsheirer.record;
import io.github.dsheirer.channel.metadata.Metadata;
@@ -29,6 +28,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -228,13 +228,18 @@ public class RecorderManager implements Listener<ReusableAudioPacket>
return sb.toString();
}
public Path getRecordingBasePath()
{
return SystemProperties.getInstance().getApplicationFolder("recordings");
}
/**
* Constructs a baseband recorder for use in a processing chain.
*/
public ComplexBufferWaveRecorder getBasebandRecorder(String channelName)
{
StringBuilder sb = new StringBuilder();
sb.append(SystemProperties.getInstance().getApplicationFolder("recordings"));
sb.append(getRecordingBasePath());
sb.append(File.separator).append(channelName).append("_baseband");
return new ComplexBufferWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString());

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