Compare commits

..

19 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
44 changed files with 762 additions and 516 deletions

View File

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

View File

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

View File

@@ -116,6 +116,16 @@ public class AttributeChangeRequest<T>
return null; 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. * Returns the value as a DecoderType or null if the value is not a DecoderType instance.
*/ */

View File

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

View File

@@ -120,8 +120,6 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
mForegroundColors.put(State.FADE, Color.DARK_GRAY); mForegroundColors.put(State.FADE, Color.DARK_GRAY);
mBackgroundColors.put(State.IDLE, Color.WHITE); mBackgroundColors.put(State.IDLE, Color.WHITE);
mForegroundColors.put(State.IDLE, Color.DARK_GRAY); mForegroundColors.put(State.IDLE, Color.DARK_GRAY);
mBackgroundColors.put(State.OVERFLOW, Color.RED);
mForegroundColors.put(State.OVERFLOW, Color.YELLOW);
mBackgroundColors.put(State.RESET, Color.PINK); mBackgroundColors.put(State.RESET, Color.PINK);
mForegroundColors.put(State.RESET, Color.YELLOW); mForegroundColors.put(State.RESET, Color.YELLOW);
mBackgroundColors.put(State.TEARDOWN, Color.DARK_GRAY); mBackgroundColors.put(State.TEARDOWN, Color.DARK_GRAY);

View File

@@ -41,6 +41,7 @@ public class Metadata
protected boolean mUpdated; protected boolean mUpdated;
protected DecoderType mPrimaryDecoderType; protected DecoderType mPrimaryDecoderType;
protected boolean mSelected; protected boolean mSelected;
protected boolean mBufferOverflow;
protected State mState = State.IDLE; protected State mState = State.IDLE;
protected String mChannelConfigurationSystem; protected String mChannelConfigurationSystem;
protected String mChannelConfigurationSite; protected String mChannelConfigurationSite;
@@ -173,6 +174,14 @@ public class Metadata
return mSelected; 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. * 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()) switch(request.getAttribute())
{ {
case BUFFER_OVERFLOW:
mBufferOverflow = request.getBooleanValue();
broadcast(Attribute.BUFFER_OVERFLOW);
break;
case CHANNEL_CONFIGURATION_SYSTEM: case CHANNEL_CONFIGURATION_SYSTEM:
mChannelConfigurationSystem = request.getStringValue(); mChannelConfigurationSystem = request.getStringValue();
broadcast(Attribute.CHANNEL_CONFIGURATION_SYSTEM); broadcast(Attribute.CHANNEL_CONFIGURATION_SYSTEM);
@@ -164,6 +168,7 @@ public class MutableMetadata extends Metadata implements Listener<AttributeChang
public void resetTemporalAttributes() public void resetTemporalAttributes()
{ {
mDoNotRecord = null; mDoNotRecord = null;
mBufferOverflow = false;
if(hasMessage()) if(hasMessage())
{ {

View File

@@ -135,21 +135,8 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
@Override @Override
public void stop() public void stop()
{ {
mMutableMetadata.resetAllAttributes(); processTeardownState();
mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null)
{
mTrafficChannelCallEvent.end();
broadcast(mTrafficChannelCallEvent);
}
mTrafficChannelCallEvent = null;
mSquelchLocked = false; mSquelchLocked = false;
setState(State.TEARDOWN);
} }
public void dispose() public void dispose()
@@ -346,8 +333,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
break; break;
} }
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
mSourceOverflow ? State.OVERFLOW : mState));
} }
} }
@@ -370,8 +356,19 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{ {
mSourceOverflow = overflow; mSourceOverflow = overflow;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mMutableMetadata.receive(new AttributeChangeRequest<Boolean>(Attribute.BUFFER_OVERFLOW, mSourceOverflow));
mSourceOverflow ? State.OVERFLOW : mState)); }
/**
* 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;
} }
/** /**
@@ -410,9 +407,13 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState)); 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.callEnd(mTrafficChannelCallEvent.getChannel());
mTrafficChannelEndListener = null;
mTrafficChannelCallEvent = null;
} }
} }

View File

@@ -34,7 +34,7 @@ public enum State
@Override @Override
public boolean canChangeTo(State state) public boolean canChangeTo(State state)
{ {
return state == CALL | return state == CALL ||
state == CONTROL || state == CONTROL ||
state == DATA || state == DATA ||
state == ENCRYPTED || state == ENCRYPTED ||
@@ -108,8 +108,7 @@ public enum State
public boolean canChangeTo(State state) public boolean canChangeTo(State state)
{ {
return state != FADE && return state != FADE &&
state != RESET && state != RESET;
state != OVERFLOW;
} }
}, },
/** /**
@@ -121,25 +120,10 @@ public enum State
public boolean canChangeTo(State state) public boolean canChangeTo(State state)
{ {
return state != TEARDOWN && return state != TEARDOWN &&
state != RESET && state != RESET;
state != OVERFLOW;
}
},
/**
* Indicates that the channel's sample buffer has overflowed and new buffer samples are no longer being
* delivered to the channel. This state persists until the processing catches up and reduces the number
* of samples in the input buffer below a threshold value. Transition to and from this state is exclusively
* managed by the Channel State and no other channel states can be processed from the channel decoders
* until the channel state is taken out of overflow.
*/
OVERFLOW("OVERFLOW")
{
@Override
public boolean canChangeTo(State state)
{
return false;
} }
}, },
/** /**
* Indicates that the channel has been reset and is ready to use for a new decoding session. * Indicates that the channel has been reset and is ready to use for a new decoding session.
*/ */
@@ -191,4 +175,10 @@ public enum State
{ {
return mDisplayValue; return mDisplayValue;
} }
@Override
public String toString()
{
return mDisplayValue;
}
} }

View File

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

View File

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

View File

@@ -212,8 +212,7 @@ public class ChannelProcessingManager implements ChannelEventListener
try try
{ {
source = mSourceManager.getSource(channel.getSourceConfiguration(), source = mSourceManager.getSource(channel.getSourceConfiguration(),
channel.getDecodeConfiguration().getDecoderType().getChannelBandwidth(), channel.getDecodeConfiguration().getChannelSpecification());
channel.getDecodeConfiguration().getDecoderType().getChannelSpecification());
} }
catch(SourceException se) catch(SourceException se)
{ {

View File

@@ -63,7 +63,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
{ {
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class); 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 //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)); private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152));

View File

@@ -29,6 +29,7 @@ import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController; 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.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -62,7 +63,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class PolyphaseChannelManager implements ISourceEventProcessor public class PolyphaseChannelManager implements ISourceEventProcessor
{ {
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class); 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 double CHANNEL_OVERSAMPLING = 2.0;
private static final int POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL = 9; private static final int POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL = 9;
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9; private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
@@ -141,7 +142,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* @param tunerChannel specifying center frequency and bandwidth. * @param tunerChannel specifying center frequency and bandwidth.
* @return source or null. * @return source or null.
*/ */
public TunerChannelSource getChannel(TunerChannel tunerChannel) public TunerChannelSource getChannel(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{ {
PolyphaseChannelSource channelSource = null; PolyphaseChannelSource channelSource = null;
@@ -155,10 +156,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{ {
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes); long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener, try
mChannelCalculator.getChannelSampleRate(), centerFrequency); {
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) catch(IllegalArgumentException iae)

View File

@@ -16,22 +16,35 @@
package io.github.dsheirer.dsp.filter.channelizer; package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor; 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.Listener;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.SourceEvent; 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.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class PolyphaseChannelSource extends TunerChannelSource public class PolyphaseChannelSource extends TunerChannelSource
{ {
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class); private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler; private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor; private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
private long mReplacementFrequency;
private ComplexFIRFilter2 mLowPassFilter;
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
private double mChannelSampleRate; private double mChannelSampleRate;
private long mIndexCenterFrequency; private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection; 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 producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
* @param channelSampleRate for the downstream sample output * @param channelSampleRate for the downstream sample output
* @param centerFrequency of the incoming polyphase channel(s) * @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, public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate, Listener<SourceEvent> producerSourceEventListener, double channelSampleRate,
long centerFrequency) long centerFrequency, ChannelSpecification channelSpecification)
throws FilterDesignException
{ {
super(producerSourceEventListener, tunerChannel); super(producerSourceEventListener, tunerChannel);
mPolyphaseChannelOutputProcessor = outputProcessor; mPolyphaseChannelOutputProcessor = outputProcessor;
mChannelSampleRate = channelSampleRate; mChannelSampleRate = channelSampleRate;
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate); mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate);
float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(),
channelSpecification.getStopFrequency());
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
setFrequency(centerFrequency); setFrequency(centerFrequency);
} }
@@ -93,14 +113,33 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/ */
public void setPolyphaseChannelOutputProcessor(IPolyphaseChannelOutputProcessor outputProcessor, long frequency) public void setPolyphaseChannelOutputProcessor(IPolyphaseChannelOutputProcessor outputProcessor, long frequency)
{ {
//Lock on the channel output processor to block the channel results processor thread from servicing the //If this is the first time, simply assign the output processor
//channel output processor's buffer while we change out the processors. if(mPolyphaseChannelOutputProcessor == null)
synchronized(mPolyphaseChannelOutputProcessor) {
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; IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
//Swap out the processor so that incoming samples can accumulate in the new channel output processor //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 //Remove the buffer overflow listener and clear any previous buffer overflow state
existingProcessor.setSourceOverflowListener(null); 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 //Register to receive buffer overflow notifications so we can push them up through the source chain
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this); mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
//Fully process the residual channel results buffer of the previous channel output processor //Finally, setup the frequency offset for the output processor.
if(existingProcessor != null) mIndexCenterFrequency = mReplacementFrequency;
{
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;
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset()); mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
} }
} }
@@ -133,12 +163,13 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/ */
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer) public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
{ {
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp()); if(mReplacementPolyphaseChannelOutputProcessor != null)
if(mPolyphaseChannelOutputProcessor != null)
{ {
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer); swapOutputProcessor();
} }
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
} }
/** /**
@@ -173,7 +204,6 @@ public class PolyphaseChannelSource extends TunerChannelSource
if(mPolyphaseChannelOutputProcessor != null) if(mPolyphaseChannelOutputProcessor != null)
{ {
mPolyphaseChannelOutputProcessor.dispose(); mPolyphaseChannelOutputProcessor.dispose();
mPolyphaseChannelOutputProcessor = null;
} }
} }
@@ -213,11 +243,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
@Override @Override
protected void processSamples() protected void processSamples()
{ {
//Lock on the output processor so that it can't be changed in the middle of processing mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
} }
/** /**
@@ -236,10 +262,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/ */
private void updateFrequencyOffset() private void updateFrequencyOffset()
{ {
synchronized(mPolyphaseChannelOutputProcessor) mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
{
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
} }
@Override @Override
@@ -248,4 +271,42 @@ public class PolyphaseChannelSource extends TunerChannelSource
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " + return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
getTunerChannel().getFrequency(); 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

@@ -16,29 +16,18 @@
package io.github.dsheirer.dsp.filter.channelizer.output; package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2; 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.dsp.mixer.FS4DownConverter;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; 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.List;
import java.util.Map;
public class TwoChannelOutputProcessor extends ChannelOutputProcessor public class TwoChannelOutputProcessor extends ChannelOutputProcessor
{ {
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class); // private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private static Map<Integer,float[]> sLowPassFilterKernelMap = new HashMap<>();
private TwoChannelSynthesizerM2 mSynthesizer; private TwoChannelSynthesizerM2 mSynthesizer;
private FS4DownConverter mFS4DownConverter = new FS4DownConverter(); private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
private ComplexFIRFilter2 mLowPassFilter;
private int mChannelOffset1; private int mChannelOffset1;
private int mChannelOffset2; private int mChannelOffset2;
@@ -58,28 +47,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
super(2, sampleRate, gain); super(2, sampleRate, gain);
setPolyphaseChannelIndices(channelIndexes); setPolyphaseChannelIndices(channelIndexes);
setSynthesisFilter(filter); 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. * Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
*
* @param frequencyOffset in hertz * @param frequencyOffset in hertz
*/ */
@Override @Override
@@ -126,7 +98,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers, public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
ReusableComplexBufferAssembler reusableComplexBufferAssembler) ReusableComplexBufferAssembler reusableComplexBufferAssembler)
{ {
for(ReusableChannelResultsBuffer buffer: channelResultsBuffers) for(ReusableChannelResultsBuffer buffer : channelResultsBuffers)
{ {
ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1); ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1);
ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2); ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2);
@@ -140,48 +112,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
//Apply offset and frequency correction to center the signal of interest within the synthesized channel //Apply offset and frequency correction to center the signal of interest within the synthesized channel
getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples()); getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples());
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized); synthesized.applyGain(getGain());
lowPassFiltered.applyGain(getGain()); reusableComplexBufferAssembler.receive(synthesized);
reusableComplexBufferAssembler.receive(lowPassFiltered);
buffer.decrementUserCount(); 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

@@ -62,12 +62,12 @@ public class FilterViewer extends Application
// .build(); // .build();
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(50000.0) .sampleRate(25000.0)
.gridDensity(16) .gridDensity(16)
.passBandCutoff(6000) .passBandCutoff(11800)
.passBandAmplitude(1.0) .passBandAmplitude(1.0)
.passBandRipple(0.01) .passBandRipple(0.01)
.stopBandStart(6700) .stopBandStart(12400)
.stopBandAmplitude(0.0) .stopBandAmplitude(0.0)
.stopBandRipple(0.01) .stopBandRipple(0.01)
.build(); .build();

View File

@@ -23,8 +23,8 @@ public enum PLLGain
//NOTE: static gain level of 200 produced the best results for releases prior to 0.3.4b2 //NOTE: static gain level of 200 produced the best results for releases prior to 0.3.4b2
LEVEL_1(150.0, 0, 1), LEVEL_1(150.0, 0, 1),
LEVEL_2(175.0, 2, 4), LEVEL_2(170.0, 2, 4),
LEVEL_3(200.0, 5, 6), LEVEL_3(190.0, 5, 6),
LEVEL_4(200.0, 7, 8), LEVEL_4(200.0, 7, 8),
LEVEL_5(200.0, 9, 10); LEVEL_5(200.0, 9, 10);

View File

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

View File

@@ -565,12 +565,6 @@ public class ProcessingChain implements IChannelEventListener
{ {
if(mSource != null) 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 //Broadcast the source sample rate so that each of the modules can self-configure
mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup")); mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup"));
@@ -652,6 +646,12 @@ public class ProcessingChain implements IChannelEventListener
{ {
module.stop(); module.stop();
} }
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
} }
} }

View File

@@ -15,8 +15,6 @@
******************************************************************************/ ******************************************************************************/
package io.github.dsheirer.module.decode; package io.github.dsheirer.module.decode;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
@@ -26,36 +24,27 @@ import java.util.EnumSet;
public enum DecoderType public enum DecoderType
{ {
//Primary Decoders //Primary Decoders
AM("AM", "AM", 10000, new ChannelSpecification(25000.0, 3000.0, 5000.0)), AM("AM", "AM"),
LTR_STANDARD("LTR-Standard", "LTR", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)), LTR_STANDARD("LTR-Standard", "LTR"),
LTR_NET("LTR-Net", "LTR-Net", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)), LTR_NET("LTR-Net", "LTR-Net"),
MPT1327("MPT1327", "MPT1327", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)), MPT1327("MPT1327", "MPT1327"),
NBFM("NBFM", "NBFM", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)), NBFM("NBFM", "NBFM"),
PASSPORT("Passport", "Passport", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)), PASSPORT("Passport", "Passport"),
P25_PHASE1("P25 Phase I", "P25-1", 12500, new ChannelSpecification(50000.0, 6000.0, 7000.0)), P25_PHASE1("P25 Phase I", "P25-1"),
//Auxiliary Decoders //Auxiliary Decoders
FLEETSYNC2("Fleetsync II", "Fleetsync2", 12500), FLEETSYNC2("Fleetsync II", "Fleetsync2"),
LJ_1200("LJ1200 173.075", "LJ1200", 12500), LJ_1200("LJ1200 173.075", "LJ1200"),
MDC1200("MDC1200", "MDC1200", 12500), MDC1200("MDC1200", "MDC1200"),
TAIT_1200("Tait 1200", "Tait 1200", 12500); TAIT_1200("Tait 1200", "Tait 1200");
private String mDisplayString; private String mDisplayString;
private String mShortDisplayString; private String mShortDisplayString;
private int mChannelBandwidth;
private ChannelSpecification mChannelSpecification;
DecoderType(String displayString, String shortDisplayString, int bandwidth, ChannelSpecification channelSpecification) DecoderType(String displayString, String shortDisplayString)
{ {
mDisplayString = displayString; mDisplayString = displayString;
mShortDisplayString = shortDisplayString; mShortDisplayString = shortDisplayString;
mChannelBandwidth = bandwidth;
mChannelSpecification = channelSpecification;
}
DecoderType(String displayString, String shortDisplayString, int bandwidth)
{
this(displayString, shortDisplayString, bandwidth, null);
} }
/** /**
@@ -97,20 +86,6 @@ public enum DecoderType
return mShortDisplayString; return mShortDisplayString;
} }
public int getChannelBandwidth()
{
return mChannelBandwidth;
}
/**
* Channel specification for a channel for this decoder type.
* @return specification or null
*/
public ChannelSpecification getChannelSpecification()
{
return mChannelSpecification;
}
@Override @Override
public String toString() public String toString()
{ {

View File

@@ -17,9 +17,11 @@
******************************************************************************/ ******************************************************************************/
package io.github.dsheirer.module.decode.am; package io.github.dsheirer.module.decode.am;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigAM extends DecodeConfiguration 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") @JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public DecoderType getDecoderType() 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.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1; import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport; import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@@ -58,4 +59,7 @@ public abstract class DecodeConfiguration extends Configuration
@JsonIgnore @JsonIgnore
public abstract DecoderType getDecoderType(); 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.Logger;
import org.slf4j.LoggerFactory; 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.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import java.awt.*; import java.awt.EventQueue;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; 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 public class MessageManagementPanel extends JPanel
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -17,10 +17,12 @@
******************************************************************************/ ******************************************************************************/
package io.github.dsheirer.module.decode.ltrnet; package io.github.dsheirer.module.decode.ltrnet;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigLTRNet extends DecodeConfiguration public class DecodeConfigLTRNet extends DecodeConfiguration
{ {
@@ -46,4 +48,15 @@ public class DecodeConfigLTRNet extends DecodeConfiguration
{ {
mMessageDirection = direction; 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

@@ -17,10 +17,12 @@
******************************************************************************/ ******************************************************************************/
package io.github.dsheirer.module.decode.ltrstandard; package io.github.dsheirer.module.decode.ltrstandard;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigLTRStandard extends DecodeConfiguration public class DecodeConfigLTRStandard extends DecodeConfiguration
{ {
@@ -47,4 +49,15 @@ public class DecodeConfigLTRStandard extends DecodeConfiguration
{ {
mMessageDirection = direction; 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

@@ -17,9 +17,11 @@
******************************************************************************/ ******************************************************************************/
package io.github.dsheirer.module.decode.mpt1327; package io.github.dsheirer.module.decode.mpt1327;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigMPT1327 extends DecodeConfiguration public class DecodeConfigMPT1327 extends DecodeConfiguration
{ {
@@ -102,4 +104,15 @@ public class DecodeConfigMPT1327 extends DecodeConfiguration
{ {
mTrafficChannelPoolSize = size; 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,12 +17,16 @@
******************************************************************************/ ******************************************************************************/
package io.github.dsheirer.module.decode.nbfm; package io.github.dsheirer.module.decode.nbfm;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigNBFM extends DecodeConfiguration public class DecodeConfigNBFM extends DecodeConfiguration
{ {
private Bandwidth mBandwidth = Bandwidth.BW_12_5;
public DecodeConfigNBFM() public DecodeConfigNBFM()
{ {
} }
@@ -33,4 +37,53 @@ public class DecodeConfigNBFM extends DecodeConfiguration
return DecoderType.NBFM; 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 * sdr-trunk
* Copyright (C) 2014-2016 Dennis Sheirer * Copyright (C) 2014-2018 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* it under the terms of the GNU General Public License as published by * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* the Free Software Foundation, either version 3 of the License, or * later version.
* (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
* This program is distributed in the hope that it will be useful, * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * You should have received a copy of the GNU General Public License along with this program.
* GNU General Public License for more details. * 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.module.decode.nbfm; 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 io.github.dsheirer.module.decode.config.DecodeConfiguration;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel; import javax.swing.JLabel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class NBFMDecoderEditor extends ValidatingEditor<Channel> public class NBFMDecoderEditor extends ValidatingEditor<Channel>
{ {
private static final long serialVersionUID = 1L; 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 private JComboBox<DecodeConfigNBFM.Bandwidth> mComboBandwidth;
public void validate( Editor<Channel> editor ) throws EditorValidationException
{
//No validation
}
@Override public NBFMDecoderEditor()
public void save() {
{ init();
if( hasItem() && isModified() ) }
{
DecodeConfigNBFM nbfm = new DecodeConfigNBFM();
getItem().setDecodeConfiguration( nbfm );
}
setModified( false );
}
private void setControlsEnabled( boolean enabled ) private void init()
{ {
} setLayout(new MigLayout("insets 0 0 0 0,wrap 2", "[right][grow,fill]", ""));
@Override mComboBandwidth = new JComboBox<>();
public void setItem( Channel item ) mComboBandwidth.setModel(new DefaultComboBoxModel<DecodeConfigNBFM.Bandwidth>(
{ DecodeConfigNBFM.Bandwidth.values()));
super.setItem( item ); mComboBandwidth.setEnabled(false);
mComboBandwidth.addActionListener(new ActionListener()
if( hasItem() ) {
{ @Override
setControlsEnabled( true ); public void actionPerformed(ActionEvent e)
{
DecodeConfiguration config = getItem().getDecodeConfiguration(); setModified(true);
}
if( config instanceof DecodeConfigNBFM ) });
{
DecodeConfigNBFM nbfm = (DecodeConfigNBFM)config; add(new JLabel("Bandwidth:"));
setModified( false ); add(mComboBandwidth);
} }
else
{ @Override
setModified( true ); public void validate(Editor<Channel> editor) throws EditorValidationException
} {
} //No validation
else }
{
setControlsEnabled( false ); @Override
setModified( false ); 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

@@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigP25Phase1 extends DecodeConfiguration public class DecodeConfigP25Phase1 extends DecodeConfiguration
{ {
@@ -104,4 +105,14 @@ public class DecodeConfigP25Phase1 extends DecodeConfiguration
{ {
mTrafficChannelPoolSize = size; 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

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

View File

@@ -1,7 +1,6 @@
/* /*******************************************************************************
* ********************************************************************************************************************* * sdr-trunk
* sdrtrunk * Copyright (C) 2014-2018 Dennis Sheirer
* Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * 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 * 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. * You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/> * If not, see <http://www.gnu.org/licenses/>
* ********************************************************************************************************************* *
*/ ******************************************************************************/
package io.github.dsheirer.module.decode.passport; package io.github.dsheirer.module.decode.passport;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigPassport extends DecodeConfiguration public class DecodeConfigPassport extends DecodeConfiguration
{ {
@@ -31,4 +32,14 @@ public class DecodeConfigPassport extends DecodeConfiguration
{ {
return DecoderType.PASSPORT; 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

@@ -74,8 +74,7 @@ public class SourceManager
return mTunerModel; return mTunerModel;
} }
public Source getSource(SourceConfiguration config, int bandwidth, ChannelSpecification channelSpecification) public Source getSource(SourceConfiguration config, ChannelSpecification channelSpecification) throws SourceException
throws SourceException
{ {
Source retVal = null; Source retVal = null;
@@ -85,10 +84,10 @@ public class SourceManager
retVal = mMixerManager.getSource(config); retVal = mMixerManager.getSource(config);
break; break;
case TUNER: case TUNER:
retVal = mTunerModel.getSource((SourceConfigTuner) config, bandwidth, channelSpecification); retVal = mTunerModel.getSource((SourceConfigTuner) config, channelSpecification);
break; break;
case RECORDING: case RECORDING:
retVal = mRecordingSourceManager.getSource(config, bandwidth); retVal = mRecordingSourceManager.getSource(config, channelSpecification);
case NONE: case NONE:
default: default:
break; break;

View File

@@ -1,19 +1,17 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdr-trunk
* Copyright (C) 2014 Dennis Sheirer * Copyright (C) 2014-2018 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* it under the terms of the GNU General Public License as published by * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* the Free Software Foundation, either version 3 of the License, or * later version.
* (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
* This program is distributed in the hope that it will be useful, * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * You should have received a copy of the GNU General Public License along with this program.
* GNU General Public License for more details. * 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.source.recording; package io.github.dsheirer.source.recording;
@@ -22,6 +20,7 @@ import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.config.SourceConfigRecording; import io.github.dsheirer.source.config.SourceConfigRecording;
import io.github.dsheirer.source.config.SourceConfiguration; import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -33,64 +32,64 @@ import java.util.List;
public class RecordingSourceManager public class RecordingSourceManager
{ {
private final static Logger mLog = LoggerFactory.getLogger( RecordingSourceManager.class ); private final static Logger mLog = LoggerFactory.getLogger(RecordingSourceManager.class);
private ArrayList<Recording> mRecordings = new ArrayList<Recording>(); private ArrayList<Recording> mRecordings = new ArrayList<Recording>();
private SettingsManager mSettingsManager; private SettingsManager mSettingsManager;
public RecordingSourceManager( SettingsManager settingsManager ) public RecordingSourceManager(SettingsManager settingsManager)
{ {
mSettingsManager = settingsManager; mSettingsManager = settingsManager;
loadRecordings(); loadRecordings();
} }
/** /**
* Iterates current recordings to get a tuner channel source for the frequency * Iterates current recordings to get a tuner channel source for the frequency
* specified in the channel config's source config object * specified in the channel config's source config object
*/ */
public Source getSource( SourceConfiguration config, int bandwidth ) public Source getSource(SourceConfiguration config, ChannelSpecification channelSpecification)
throws SourceException throws SourceException
{ {
TunerChannelSource retVal = null; TunerChannelSource retVal = null;
if( config instanceof SourceConfigRecording ) if(config instanceof SourceConfigRecording)
{ {
SourceConfigRecording configRecording = (SourceConfigRecording)config; SourceConfigRecording configRecording = (SourceConfigRecording)config;
TunerChannel tunerChannel = configRecording.getTunerChannel();
tunerChannel.setBandwidth( bandwidth );
Recording recording = getRecordingFromAlias( TunerChannel tunerChannel = configRecording.getTunerChannel();
configRecording.getRecordingAlias() );
tunerChannel.setBandwidth(channelSpecification.getBandwidth());
if( recording != null )
{ Recording recording = getRecordingFromAlias(
retVal = recording.getChannel( tunerChannel ); configRecording.getRecordingAlias());
}
else if(recording != null)
{ {
throw new SourceException( "Recording with alias name [" + retVal = recording.getChannel(tunerChannel);
configRecording.getRecordingAlias() + "] is not currently available" ); }
} else
} {
throw new SourceException("Recording with alias name [" +
return retVal; configRecording.getRecordingAlias() + "] is not currently available");
}
}
return retVal;
} }
public Recording getRecordingFromAlias( String alias ) public Recording getRecordingFromAlias(String alias)
{ {
for( Recording recording: mRecordings ) for(Recording recording : mRecordings)
{ {
if( recording.getRecordingConfiguration() if(recording.getRecordingConfiguration()
.getAlias().contentEquals( alias ) ) .getAlias().contentEquals(alias))
{ {
return recording; return recording;
} }
} }
return null; return null;
} }
/** /**
@@ -98,47 +97,47 @@ public class RecordingSourceManager
*/ */
public List<Recording> getRecordings() public List<Recording> getRecordings()
{ {
Collections.sort( mRecordings ); Collections.sort(mRecordings);
return mRecordings;
}
public void addRecording( Recording recording )
{
mRecordings.add( recording );
}
public Recording addRecording( RecordingConfiguration config )
{
Recording recording = new Recording( mSettingsManager, config );
mRecordings.add( recording ); return mRecordings;
return recording;
} }
public void removeRecording( Recording recording ) public void addRecording(Recording recording)
{ {
/* Remove the config from the settings manager */ mRecordings.add(recording);
mSettingsManager.removeRecordingConfiguration( }
recording.getRecordingConfiguration() );
public Recording addRecording(RecordingConfiguration config)
mRecordings.remove( recording ); {
Recording recording = new Recording(mSettingsManager, config);
mRecordings.add(recording);
return recording;
}
public void removeRecording(Recording recording)
{
/* Remove the config from the settings manager */
mSettingsManager.removeRecordingConfiguration(
recording.getRecordingConfiguration());
mRecordings.remove(recording);
} }
private void loadRecordings() private void loadRecordings()
{ {
List<RecordingConfiguration> recordingConfigurations = List<RecordingConfiguration> recordingConfigurations =
mSettingsManager.getRecordingConfigurations(); mSettingsManager.getRecordingConfigurations();
mLog.info( "RecordingSourceManager - discovered [" + mLog.info("RecordingSourceManager - discovered [" +
recordingConfigurations.size() + "] recording configurations" ); recordingConfigurations.size() + "] recording configurations");
for( RecordingConfiguration config: recordingConfigurations ) for(RecordingConfiguration config : recordingConfigurations)
{ {
Recording recording = new Recording( mSettingsManager, config ); Recording recording = new Recording(mSettingsManager, config);
mRecordings.add( recording ); mRecordings.add(recording);
} }
} }
} }

View File

@@ -21,7 +21,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.TunerEvent.Event; import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager; import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
import io.github.dsheirer.source.tuner.manager.HeterodyneChannelSourceManager; import io.github.dsheirer.source.tuner.manager.PolyphaseChannelSourceManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -51,8 +51,8 @@ public abstract class Tuner implements ISourceEventProcessor
//Register to receive frequency and sample rate change notifications //Register to receive frequency and sample rate change notifications
mTunerController.addListener(this::process); mTunerController.addListener(this::process);
// mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController); mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController);
mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController); // mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController);
//Register to receive channel count change notifications //Register to receive channel count change notifications
mChannelSourceManager.addSourceEventListener(this::process); mChannelSourceManager.addSourceEventListener(this::process);

View File

@@ -26,12 +26,15 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceEventListenerToProcessorAdapter; import io.github.dsheirer.source.SourceEventListenerToProcessorAdapter;
import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.frequency.FrequencyController; import io.github.dsheirer.source.tuner.frequency.FrequencyController;
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable; import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.SortedSet;
public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener, public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener,
IReusableComplexBufferProvider, Listener<ReusableComplexBuffer> IReusableComplexBufferProvider, Listener<ReusableComplexBuffer>
{ {
@@ -361,4 +364,85 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
{ {
broadcast(complexBuffer); broadcast(complexBuffer);
} }
/**
* Indicates if the current center frequency and bandwidth is correct to source the tuner channel
*
* @param tunerChannel to test
* @return true if the current center frequency and bandwidth is correct for the channel
*/
public boolean isTunedFor(TunerChannel tunerChannel)
{
try
{
if(tunerChannel.getMinFrequency() < getMinTunedFrequency())
{
return false;
}
if(tunerChannel.getMaxFrequency() > getMaxTunedFrequency())
{
return false;
}
if(hasMiddleUnusableBandwidth())
{
long minAvoid = getFrequency() - getMiddleUnusableHalfBandwidth();
long maxAvoid = getFrequency() + getMiddleUnusableHalfBandwidth();
if(tunerChannel.overlaps(minAvoid, maxAvoid))
{
return false;
}
}
}
catch(SourceException se)
{
return false;
}
return true;
}
/**
* Indicates if the current center frequency and bandwidth is correct to source the tuner channel set
*
* @param tunerChannels to test
* @return true if the current center frequency and bandwidth is correct for the channel set
*/
public boolean isTunedFor(SortedSet<TunerChannel> tunerChannels)
{
try
{
if(tunerChannels.first().getMinFrequency() < getMinTunedFrequency())
{
return false;
}
if(tunerChannels.last().getMaxFrequency() > getMaxTunedFrequency())
{
return false;
}
if(hasMiddleUnusableBandwidth())
{
long minAvoid = getFrequency() - getMiddleUnusableHalfBandwidth();
long maxAvoid = getFrequency() + getMiddleUnusableHalfBandwidth();
for(TunerChannel tunerChannel: tunerChannels)
{
if(tunerChannel.overlaps(minAvoid, maxAvoid))
{
return false;
}
}
}
}
catch(SourceException se)
{
return false;
}
return true;
}
} }

View File

@@ -340,13 +340,13 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
* *
* Returns null if no tuner can source the channel * Returns null if no tuner can source the channel
*/ */
public Source getSource(SourceConfigTuner config, int bandwidth, ChannelSpecification channelSpecification) public Source getSource(SourceConfigTuner config, ChannelSpecification channelSpecification)
{ {
TunerChannelSource retVal = null; TunerChannelSource retVal = null;
TunerChannel tunerChannel = config.getTunerChannel(); TunerChannel tunerChannel = config.getTunerChannel();
tunerChannel.setBandwidth(bandwidth); tunerChannel.setBandwidth(channelSpecification.getBandwidth());
Iterator<Tuner> it = mTuners.iterator(); Iterator<Tuner> it = mTuners.iterator();

View File

@@ -19,6 +19,7 @@ import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.mixer.IOscillator; import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
@@ -74,6 +75,7 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene
channelSpecification.getStopFrequency()); channelSpecification.getStopFrequency());
mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD); mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
//Setup the frequency mixer to the current source frequency //Setup the frequency mixer to the current source frequency
mChannelSampleRate = sampleRate / (double)decimation; mChannelSampleRate = sampleRate / (double)decimation;
mTunerFrequency = tunerChannel.getFrequency(); mTunerFrequency = tunerChannel.getFrequency();
@@ -82,6 +84,18 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene
mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate); mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate);
} }
/**
* Registers an overflow listener to be notified in the internal buffer is in an
* overflow state.
*
* @param listener to receiver updates to the source overflow state
*/
@Override
public void setOverflowListener(IOverflowListener listener)
{
mBuffer.setOverflowListener(listener);
}
@Override @Override
public void dispose() public void dispose()
{ {

View File

@@ -21,18 +21,21 @@ package io.github.dsheirer.source.tuner.channel;
public class ChannelSpecification public class ChannelSpecification
{ {
private double mMinimumSampleRate; private double mMinimumSampleRate;
private int mBandwidth;
private double mPassFrequency; private double mPassFrequency;
private double mStopFrequency; private double mStopFrequency;
/** /**
* Constructs a channel specification instance. * Constructs a channel specification instance.
* @param minimumSampleRate requested for the channel * @param minimumSampleRate requested for the channel
* @param bandwidth
* @param passFrequency for the pass band of the channel * @param passFrequency for the pass band of the channel
* @param stopFrequency for the stop band of teh channel * @param stopFrequency for the stop band of teh channel
*/ */
public ChannelSpecification(double minimumSampleRate, double passFrequency, double stopFrequency) public ChannelSpecification(double minimumSampleRate, int bandwidth, double passFrequency, double stopFrequency)
{ {
mMinimumSampleRate = minimumSampleRate; mMinimumSampleRate = minimumSampleRate;
mBandwidth = bandwidth;
mPassFrequency = passFrequency; mPassFrequency = passFrequency;
mStopFrequency = stopFrequency; mStopFrequency = stopFrequency;
} }
@@ -45,6 +48,14 @@ public class ChannelSpecification
return mMinimumSampleRate; return mMinimumSampleRate;
} }
/**
* Bandwidth of the channel in Hertz
*/
public int getBandwidth()
{
return mBandwidth;
}
/** /**
* Pass band frequency for filtering the channel * Pass band frequency for filtering the channel
*/ */

View File

@@ -286,8 +286,26 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
{ {
if(!mStopped) if(!mStopped)
{ {
getHeartbeatManager().broadcast(); try
processSamples(); {
getHeartbeatManager().broadcast();
}
catch(Throwable t)
{
mLog.error("Error while sending heartbeat", t);
}
}
if(!mStopped)
{
try
{
processSamples();
}
catch(Throwable t)
{
mLog.error("Error while processing samples", t);
}
} }
if(mStopped) if(mStopped)
@@ -300,7 +318,15 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
mScheduledFuture = null; mScheduledFuture = null;
performDisposal(); try
{
getHeartbeatManager().broadcast();
performDisposal();
}
catch(Throwable t)
{
mLog.error("Error during final shutdown processing of samples", t);
}
} }
} }
} }

View File

@@ -54,48 +54,57 @@ public class CenterFrequencyCalculator
//Start by placing the highest frequency channel at the high end of the spectrum //Start by placing the highest frequency channel at the high end of the spectrum
candidateFrequency = maxChannelFrequency - (tunerController.getUsableBandwidth() / 2); candidateFrequency = maxChannelFrequency - (tunerController.getUsableBandwidth() / 2);
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists if(maxChannelFrequency - minChannelFrequency <= tunerController.getUsableHalfBandwidth())
if(tunerController.getMiddleUnusableHalfBandwidth() > 0)
{ {
boolean processingRequired = true; candidateFrequency = minChannelFrequency - tunerController.getMiddleUnusableHalfBandwidth();
isValidCandidateFrequency = true;
while(isValidCandidateFrequency && processingRequired) }
else
{
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
if(tunerController.getMiddleUnusableHalfBandwidth() > 0)
{ {
processingRequired = false; boolean processingRequired = true;
long minAvoid = candidateFrequency - tunerController.getMiddleUnusableHalfBandwidth(); while(isValidCandidateFrequency && processingRequired)
long maxAvoid = candidateFrequency + tunerController.getMiddleUnusableHalfBandwidth();
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
for(TunerChannel channel : channels)
{ {
if(channel.overlaps(minAvoid, maxAvoid)) processingRequired = false;
long minAvoid = candidateFrequency - tunerController.getMiddleUnusableHalfBandwidth();
long maxAvoid = candidateFrequency + tunerController.getMiddleUnusableHalfBandwidth();
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
for(TunerChannel channel : channels)
{ {
//Calculate a tuned frequency adjustment that places this overlapping channel just to the if(channel.overlaps(minAvoid, maxAvoid))
//left of the central DC spike avoid zone
long adjustment = channel.getMaxFrequency() - minAvoid + 1;
//If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
if(candidateFrequency + adjustment - (tunerController.getUsableBandwidth() / 2) <= minChannelFrequency)
{ {
candidateFrequency += adjustment; //Calculate a tuned frequency adjustment that places this overlapping channel just to the
processingRequired = true; //left of the central DC spike avoid zone
} long adjustment = channel.getMaxFrequency() - minAvoid + 1;
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
else
{
isValidCandidateFrequency = false;
}
//break out of the for/each loop, so that we can start over again with all of the channels //If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
break; if(candidateFrequency + adjustment - (tunerController.getUsableBandwidth() / 2) <= minChannelFrequency)
{
candidateFrequency += adjustment;
processingRequired = true;
}
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
else
{
isValidCandidateFrequency = false;
}
//break out of the for/each loop, so that we can start over again with all of the channels
break;
}
} }
} }
} }
} }
} }
if(isValidCandidateFrequency) if(isValidCandidateFrequency)

View File

@@ -108,23 +108,26 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
*/ */
private void updateTunerFrequency() private void updateTunerFrequency()
{ {
long centerFrequency = CenterFrequencyCalculator.getCenterFrequency(mTunerController, getTunerChannels()); if(!mTunerController.isTunedFor(getTunerChannels()))
if(centerFrequency == CenterFrequencyCalculator.INVALID_FREQUENCY)
{ {
mLog.error("Couldn't calculate center frequency for tuner and tuner channels"); long centerFrequency = CenterFrequencyCalculator.getCenterFrequency(mTunerController, getTunerChannels());
return;
}
if(centerFrequency != mTunerController.getFrequency()) if(centerFrequency == CenterFrequencyCalculator.INVALID_FREQUENCY)
{
try
{ {
mTunerController.setFrequency(centerFrequency); mLog.error("Couldn't calculate center frequency for tuner and tuner channels");
return;
} }
catch(SourceException se)
if(centerFrequency != mTunerController.getFrequency())
{ {
mLog.error("Couldn't update tuner center frequency to " + centerFrequency, se); try
{
mTunerController.setFrequency(centerFrequency);
}
catch(SourceException se)
{
mLog.error("Couldn't update tuner center frequency to " + centerFrequency, se);
}
} }
} }
} }

View File

@@ -393,11 +393,6 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
@Override @Override
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification) public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{ {
if(channelSpecification != null)
{
throw new IllegalArgumentException("Update polyphase source manager to support channel specifications");
}
if(isTunable(tunerChannel)) if(isTunable(tunerChannel))
{ {
//Get a new set of currently tuned channels //Get a new set of currently tuned channels
@@ -422,7 +417,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
} }
//If we're successful to here, allocate the channel //If we're successful to here, allocate the channel
return mPolyphaseChannelManager.getChannel(tunerChannel); return mPolyphaseChannelManager.getChannel(tunerChannel, channelSpecification);
} }
catch(SourceException se) catch(SourceException se)
{ {

View File

@@ -32,7 +32,7 @@ public class TestTunerController extends TunerController
public static final long MINIMUM_FREQUENCY = 1l; public static final long MINIMUM_FREQUENCY = 1l;
public static final long MAXIMUM_FREQUENCY = 1000000000l; public static final long MAXIMUM_FREQUENCY = 1000000000l;
public static final int SAMPLE_RATE = 50000; public static final int SAMPLE_RATE = 10000000;
public static final int DC_NOISE_BANDWIDTH = 0; public static final int DC_NOISE_BANDWIDTH = 0;
public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00; public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00;