Merges from master branch.

This commit is contained in:
Denny
2017-12-27 10:10:51 -05:00
parent 667c50174d
commit 236df8f3f4
262 changed files with 7701 additions and 49122 deletions

View File

@@ -32,8 +32,6 @@ import io.github.dsheirer.sample.real.RealBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
/**
* Provides packaging of demodulated audio sample buffers into audio packets for
* broadcast to registered audio packet listeners. Includes audio packet
@@ -117,7 +115,7 @@ public class AudioModule extends Module implements IAudioPacketProvider, IFilter
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
/* No start operations provided */
}

View File

@@ -140,7 +140,7 @@ public class StreamManager implements Listener<AudioPacket>
{
AudioRecorder recorder = BroadcastFactory.getAudioRecorder(getTemporaryRecordingPath(),
mBroadcastFormat);
recorder.start(ThreadPool.SCHEDULED);
recorder.start();
recorder.receive(audioPacket);
mStreamRecorders.put(channelMetadataID, recorder);
}

View File

@@ -1,118 +1,124 @@
/*******************************************************************************
* 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/>
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.buffer;
import java.util.Arrays;
/**
* Circular sample buffer - allocates a buffer and stores samples in a circular
* fashion, overwriting older samples with newly arrived samples. Initially
* fills buffer with 0-valued samples
*
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
* @author denny
* fashion, overwriting older samples with newly arrived samples.
*
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
*/
public class FloatCircularBuffer
{
float[] mBuffer;
int mBufferPointer = 0;
public FloatCircularBuffer( int size )
{
mBuffer = new float[ size ];
Arrays.fill( mBuffer, (float)0.0 );
}
public int getSize()
{
return mBuffer.length;
}
public float[] getBuffer()
{
return mBuffer;
}
float[] mBuffer;
int mBufferPointer = 0;
/**
* Puts the new value into the buffer and returns the oldest buffer value
* that it replaced
* @param newValue
* @return
*/
public float get( float newValue )
{
float oldestSample = mBuffer[ mBufferPointer ];
public FloatCircularBuffer(int size)
{
mBuffer = new float[size];
}
mBuffer[ mBufferPointer ] = newValue;
mBufferPointer++;
if( mBufferPointer >= mBuffer.length )
{
mBufferPointer = 0;
}
return oldestSample;
}
public int getSize()
{
return mBuffer.length;
}
/**
* Convenience method to put a new value in the buffer, while discarding
* the old value
*/
public void put( float newValue )
{
get( newValue );
}
public float[] getBuffer()
{
return mBuffer;
}
/**
* Returns an array of arraySize, loaded with sampleCount of the
* oldest samples, loaded into the array starting at index 0.
*
* This is useful for fetching an array filled half way with samples to
* use in FFT processing.
* @param arraySize - size of the array (must be larger than sampleCount)
* @param sampleCount - number of the oldest samples in the buffer to load
* @return
*/
public float[] get( int arraySize, int sampleCount )
{
float[] samples = new float[ arraySize ];
/**
* Puts the new value into the buffer and returns the oldest buffer value
* that it replaced
*
* @param newValue
* @return
*/
public float putAndGet(float newValue)
{
float oldestSample = mBuffer[mBufferPointer];
if( arraySize > sampleCount )
{
int tempCounter = mBufferPointer;
put(newValue);
for( int x = 0; x < sampleCount; x++ )
{
samples[ x ] = mBuffer[ tempCounter ];
tempCounter++;
if( tempCounter > mBuffer.length )
{
tempCounter = 0;
}
}
}
return samples;
}
return oldestSample;
}
/**
* Adds the new value to this buffer.
*/
public void put(float newValue)
{
mBuffer[mBufferPointer] = newValue;
mBufferPointer++;
if(mBufferPointer >= mBuffer.length)
{
mBufferPointer = 0;
}
}
/**
* Returns the sample at the specified index, where index 0 is the oldest sample and index (length - 1) is the
* newest sample.
* @param index in range of 0 to length-1
* @return value at indexed position
*/
public float get(int index)
{
if(index < mBuffer.length)
{
int pointer = mBufferPointer + index;
if(pointer >= mBuffer.length)
{
pointer -= mBuffer.length;
}
return mBuffer[pointer];
}
else
{
throw new ArrayIndexOutOfBoundsException("Index [" + index + "] is not valid for the buffer size of [" +
mBuffer.length + "]");
}
}
/**
* Returns the contents of the circular buffer unwrapped where index 0 is the oldest sample and index length-1
* is the newest sample.
*
* @return array of samples
*/
public float[] get()
{
float[] samples = new float[mBuffer.length];
System.arraycopy(mBuffer, mBufferPointer, samples, 0, mBuffer.length - mBufferPointer);
if(mBufferPointer != 0)
{
System.arraycopy(mBuffer, 0, samples, mBuffer.length - mBufferPointer, mBufferPointer);
}
return samples;
}
}

View File

@@ -25,8 +25,6 @@ import io.github.dsheirer.module.decode.DecoderType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
/**
* Basic decoder channel state - provides the minimum channel state functionality
* to support an always un-squelched audio decoder.
@@ -94,7 +92,7 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
broadcast(new AttributeChangeRequest<String>(Attribute.PRIMARY_ADDRESS_TO, NO_SQUELCH));
broadcast(new DecoderStateEvent(this, Event.ALWAYS_UNSQUELCH, State.IDLE));

View File

@@ -20,8 +20,6 @@ package io.github.dsheirer.channel.state;
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.channel.heartbeat.Heartbeat;
import io.github.dsheirer.channel.heartbeat.IHeartbeatListener;
import io.github.dsheirer.channel.metadata.Attribute;
import io.github.dsheirer.channel.metadata.AttributeChangeRequest;
import io.github.dsheirer.channel.metadata.IAttributeChangeRequestListener;
@@ -36,17 +34,17 @@ import io.github.dsheirer.module.decode.event.CallEvent;
import io.github.dsheirer.module.decode.event.ICallEventProvider;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
import static io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
import static io.github.dsheirer.source.SourceEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, IFrequencyChangeListener, IHeartbeatListener, ISquelchStateProvider,
IDecoderStateEventProvider, ISourceEventListener, IHeartbeatListener, ISquelchStateProvider,
IAttributeChangeRequestListener, IOverflowListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
@@ -64,7 +62,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent;
private FrequencyChangeListener mFrequencyChangeListener;
private SourceChangeListener mSourceChangeListener;
private boolean mSquelchLocked = false;
private boolean mSelected = false;
@@ -124,7 +122,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
@@ -160,14 +158,14 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
public Listener<SourceEvent> getSourceEventListener()
{
if(mFrequencyChangeListener == null)
if(mSourceChangeListener == null)
{
mFrequencyChangeListener = new FrequencyChangeListener();
mSourceChangeListener = new SourceChangeListener();
}
return mFrequencyChangeListener;
return mSourceChangeListener;
}
private boolean isStandardChannel()
@@ -520,14 +518,14 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
/**
* Listener to receive frequency change events and rebroadcast them to the decoder states
*/
public class FrequencyChangeListener implements Listener<FrequencyChangeEvent>
public class SourceChangeListener implements Listener<SourceEvent>
{
@Override
public void receive(FrequencyChangeEvent frequencyChangeEvent)
public void receive(SourceEvent sourceEvent)
{
if(frequencyChangeEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
if(sourceEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
{
long frequency = frequencyChangeEvent.getValue().longValue();
long frequency = sourceEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
}

View File

@@ -1,19 +1,16 @@
/*******************************************************************************
* sdrtrunk
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is 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.traffic;
@@ -29,13 +26,12 @@ import io.github.dsheirer.controller.channel.TrafficChannelEvent;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.event.CallEvent;
import io.github.dsheirer.module.decode.event.CallEvent.CallEventType;
import io.github.dsheirer.module.decode.event.ICallEventProvider;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannel.Type;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,7 +39,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
public class TrafficChannelManager extends Module implements ICallEventProvider, IDecoderStateEventListener
{
@@ -61,7 +56,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
private ChannelModel mChannelModel;
private DecodeConfiguration mDecodeConfiguration;
private EventLogConfiguration mEventLogConfiguration;
private RecordConfiguration mRecordConfiguration;
private String mSystem;
private String mSite;
@@ -75,7 +69,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
* @param channelModel containing channels currently in use
* @param decodeConfiguration - decoder configuration to use for each
* traffic channel allocation.
* @param eventLogConfiguration - logging configuration to use for traffic channels
* @param recordConfiguration - recording options for each traffic channel
* @param system label to use to describe the system
* @param site label to use to describe the site
@@ -85,7 +78,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
*/
public TrafficChannelManager(ChannelModel channelModel,
DecodeConfiguration decodeConfiguration,
EventLogConfiguration eventLogConfiguration,
RecordConfiguration recordConfiguration,
String system,
String site,
@@ -94,7 +86,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
{
mChannelModel = channelModel;
mDecodeConfiguration = decodeConfiguration;
mEventLogConfiguration = eventLogConfiguration;
mRecordConfiguration = recordConfiguration;
mSystem = system;
mSite = site;
@@ -150,8 +141,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
channel.setDecodeConfiguration(mDecodeConfiguration);
channel.setEventLogConfiguration(mEventLogConfiguration);
channel.setRecordConfiguration(mRecordConfiguration);
channel.setAliasListName(mAliasListName);
@@ -161,7 +150,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
mTrafficChannelPool.add(channel);
}
/* If we have a configured channel, update metadata */
/* If we have a configured channel, update metadata */
if(channel != null)
{
channel.setSourceConfiguration(new SourceConfigTuner(tunerChannel));
@@ -184,7 +173,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
{
CallEvent callEvent = event.getCallEvent();
/* Check for duplicate events and suppress */
/* Check for duplicate events and suppress */
synchronized(mTrafficChannelsInUse)
{
if(mTrafficChannelsInUse.containsKey(callEvent.getChannel()))
@@ -196,8 +185,8 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
if(frequency > 0)
{
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(Type.TRAFFIC,
frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(frequency,
mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
if(channel != null)
{
@@ -213,7 +202,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
}
else
{
callEvent.setCallEventType(CallEvent.CallEventType.CALL_DETECT);
callEvent.setCallEventType(CallEventType.CALL_DETECT);
String details = callEvent.getDetails();
@@ -230,7 +219,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
}
else
{
callEvent.setCallEventType(CallEvent.CallEventType.CALL_DETECT);
callEvent.setCallEventType(CallEventType.CALL_DETECT);
String details = callEvent.getDetails();
@@ -247,7 +236,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
}
else
{
callEvent.setCallEventType(CallEvent.CallEventType.CALL_DETECT);
callEvent.setCallEventType(CallEventType.CALL_DETECT);
String details = callEvent.getDetails();
@@ -337,7 +326,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
}
@@ -348,7 +337,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
{
List<String> channels = new ArrayList<>();
/* Copy the keyset so we don't get concurrent modification of the map */
/* Copy the keyset so we don't get concurrent modification of the map */
channels.addAll(mTrafficChannelsInUse.keySet());
for(String channel : channels)
@@ -399,4 +388,4 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
}
}
}
}
}

View File

@@ -31,18 +31,16 @@ import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigFactory;
import io.github.dsheirer.source.config.SourceConfigRecording;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannel.Type;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
@JacksonXmlRootElement(localName = "channel")
public class Channel extends Configuration implements Listener<FrequencyChangeEvent>
public class Channel extends Configuration implements Listener<SourceEvent>
{
// Standard channels are persisted and traffic channels are temporary
public enum ChannelType {STANDARD, TRAFFIC}
@@ -51,12 +49,9 @@ public class Channel extends Configuration implements Listener<FrequencyChangeEv
private static int UNIQUE_ID = 0;
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
private AuxDecodeConfiguration mAuxDecodeConfiguration =
new AuxDecodeConfiguration();
private SourceConfiguration mSourceConfiguration =
SourceConfigFactory.getDefaultSourceConfiguration();
private EventLogConfiguration mEventLogConfiguration =
new EventLogConfiguration();
private AuxDecodeConfiguration mAuxDecodeConfiguration = new AuxDecodeConfiguration();
private SourceConfiguration mSourceConfiguration = SourceConfigFactory.getDefaultSourceConfiguration();
private EventLogConfiguration mEventLogConfiguration = new EventLogConfiguration();
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
private String mAliasListName;
@@ -423,15 +418,14 @@ public class Channel extends Configuration implements Listener<FrequencyChangeEv
{
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
}
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
{
SourceConfigRecording config =
(SourceConfigRecording)mSourceConfiguration;
SourceConfigRecording config = (SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
}
}
@@ -457,9 +451,9 @@ public class Channel extends Configuration implements Listener<FrequencyChangeEv
* be used to visually show in the spectral display.
*/
@Override
public void receive(FrequencyChangeEvent event)
public void receive(SourceEvent event)
{
if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
if(event.getEvent() == SourceEvent.Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
{
mChannelFrequencyCorrection = event.getValue().intValue();
}

View File

@@ -26,18 +26,21 @@ 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;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesignerWithLagrange;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
/**
* Utility methods for designing various types of filters.
*/
public class FilterFactory
{
private final static Logger mLog =
LoggerFactory.getLogger(FilterFactory.class);
private final static Logger mLog = LoggerFactory.getLogger(FilterFactory.class);
private static final double PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE = -6.020599842071533; //decibel(0.5, 0.0)
/**
* Generates coefficients for a unity-gain, windowed low-pass filter
@@ -48,43 +51,49 @@ public class FilterFactory
* @param window - to apply against the coefficients
* @return
*/
public static float[] getSinc(int sampleRate,
long frequency,
int length,
Window.WindowType window)
public static float[] getSinc(int sampleRate, long frequency, int length, Window.WindowType window)
{
//Ensure we have an odd length
assert (length % 2 == 0);
int evenLength = length % 2 == 0 ? length : length + 1;
//Get unity response array (one element longer to align with IDFT size)
float[] frequencyResponse = getUnityResponseArray(sampleRate, frequency, length + 1);
float[] frequencyResponse = getUnityResponseArray(sampleRate, frequency, evenLength);
//Apply Inverse DFT against frequency response unity values, leaving the
//IDFT bin results in the frequency response array
FloatFFT_1D idft = new FloatFFT_1D(length + 1);
idft.realInverseFull(frequencyResponse, false);
FloatFFT_1D idft = new FloatFFT_1D(evenLength);
idft.realInverseFull(frequencyResponse, true);
//Transfer the IDFT results to the return array
float[] coefficients = new float[length];
int middleCoefficient = (int) (length / 2);
int middleCoefficient = (int)(length / 2);
//Bin 0 of the idft is our center coefficient
coefficients[middleCoefficient] = frequencyResponse[0];
//The remaining idft bins from 1 to (middle - 1) are the mirror image
//coefficients
for(int x = 1; x < middleCoefficient; x++)
if(length % 2 == 0) //Even length
{
coefficients[middleCoefficient + x] = frequencyResponse[2 * x];
coefficients[middleCoefficient - x] = frequencyResponse[2 * x];
//The remaining idft bins from 1 to (middle - 1) are the mirror image
//coefficients
for(int x = 0; x < middleCoefficient; x++)
{
coefficients[middleCoefficient + x] = frequencyResponse[2 * x];
coefficients[middleCoefficient - x] = frequencyResponse[2 * x];
}
}
else
{
//Bin 0 of the idft is our center coefficient
coefficients[middleCoefficient] = frequencyResponse[0];
//The remaining idft bins from 1 to (middle - 1) are the mirror image
//coefficients
for(int x = 1; x < middleCoefficient; x++)
{
coefficients[middleCoefficient + x] = frequencyResponse[2 * x];
coefficients[middleCoefficient - x] = frequencyResponse[2 * x];
}
}
//Apply the window against the coefficients
coefficients = Window.apply(window, coefficients);
//Normalize to unity (1) gain
coefficients = normalize(coefficients);
return coefficients;
}
@@ -95,7 +104,7 @@ public class FilterFactory
* @param coefficients
* @return
*/
private static float[] normalize(float[] coefficients)
public static float[] normalize(float[] coefficients)
{
float accumulator = 0;
@@ -128,17 +137,15 @@ public class FilterFactory
* @param length
* @return
*/
public static float[] getUnityResponseArray(int sampleRate,
long frequency,
int length)
public static float[] getUnityResponseArray(int sampleRate, long frequency, int length)
{
float[] unityArray = new float[length * 2];
int binCount = (int) ((Math.round(
(float) frequency / (float) sampleRate * (float) length)));
if(length % 2 == 0) //even length
{
int binCount = (int)((Math.round((float)frequency / (float)sampleRate * (float)length)));
for(int x = 0; x < binCount; x++)
{
unityArray[x] = 1.0f;
@@ -147,9 +154,11 @@ public class FilterFactory
}
else //odd length
{
int binCount = (int)((Math.round((float)frequency / (float)sampleRate * (float)length + 0.5)));
unityArray[0] = 1.0f;
for(int x = 1; x <= binCount; x++)
for(int x = 1; x < binCount; x++)
{
unityArray[x] = 1.0f;
unityArray[length - x] = 1.0f;
@@ -159,25 +168,6 @@ public class FilterFactory
return unityArray;
}
public static float[] getSine(float sampleRate, float frequency, int length)
{
float[] retVal = new float[length];
float radianFrequency = (float) (2.0d * Math.PI * (frequency / sampleRate));
int middle = (int) (length / 2);
for(int x = 0; x < middle; x++)
{
float val = (float) Math.sin(radianFrequency * x);
retVal[middle + x] = val;
retVal[middle - x] = -val;
}
return retVal;
}
/**
* Applies a repeating sequence of 1, -1 to the coefficients to invert
* the frequency response of the filter. Used in converting a low-pass
@@ -211,25 +201,9 @@ public class FilterFactory
* @param windowType - window to apply against the generated coefficients
* @return
*/
public static float[] getLowPass(int sampleRate,
long cutoff,
int filterLength,
Window.WindowType windowType)
public static float[] getLowPass(int sampleRate, long cutoff, int filterLength, Window.WindowType windowType)
{
if(filterLength % 2 == 0) //even length
{
float[] values = getSinc(sampleRate, cutoff, filterLength + 2, windowType);
//throw away the 0 index and the last index
return Arrays.copyOfRange(values, 1, values.length - 2);
}
else
{
float[] values = getSinc(sampleRate, cutoff, filterLength + 1, windowType);
//throw away the 0 index
return Arrays.copyOfRange(values, 1, values.length);
}
return getSinc(sampleRate, cutoff, filterLength, windowType);
}
/**
@@ -246,26 +220,17 @@ public class FilterFactory
* - passFrequency < stopFrequency
* - stopFrequency <= sampleRate/2
*/
public static float[] getLowPass(int sampleRate,
int passFrequency,
int stopFrequency,
int attenuation,
Window.WindowType windowType,
boolean forceOddLength)
public static float[] getLowPass(int sampleRate, int passFrequency, int stopFrequency, int attenuation,
Window.WindowType windowType, boolean forceOddLength)
{
if(stopFrequency < passFrequency || stopFrequency > (sampleRate / 2))
{
throw new IllegalArgumentException("FilterFactory - low pass "
+ "filter pass frequency [" + passFrequency + "] must be "
+ "less than the stop frequency [" + stopFrequency + "] "
+ "and must be less than half [" + (int) (sampleRate / 2) +
"] of the sample rate [" + sampleRate + "]");
throw new IllegalArgumentException("FilterFactory - low pass filter pass frequency [" + passFrequency +
"] must be less than the stop frequency [" + stopFrequency + "] and must be less than half [" +
(int)(sampleRate / 2) + "] of the sample rate [" + sampleRate + "]");
}
int tapCount = getTapCount(sampleRate,
passFrequency,
stopFrequency,
attenuation);
int tapCount = getTapCount(sampleRate, passFrequency, stopFrequency, attenuation);
if(forceOddLength)
{
@@ -289,34 +254,21 @@ public class FilterFactory
* @param windowType - window to apply against the generated coefficients
* @return
*/
public static float[] getHighPass(int sampleRate,
long cutoff,
int filterLength,
Window.WindowType windowType)
public static float[] getHighPass(int sampleRate, long cutoff, int filterLength, Window.WindowType windowType)
{
//Convert the high frequency cutoff to its low frequency cutoff
//equivalent, so that when we generate the low pass filter, prior to
//inversion, its at the correct frequency
long convertedCutoff = sampleRate / 2 - cutoff;
return invert(getSinc(sampleRate,
convertedCutoff,
filterLength,
windowType));
return invert(getSinc(sampleRate, convertedCutoff, filterLength, windowType));
}
public static float[] getHighPass(int sampleRate,
long stopFrequency,
long passFrequency,
int attenuation,
Window.WindowType windowType,
boolean forceOddLength)
public static float[] getHighPass(int sampleRate, long stopFrequency, long passFrequency, int attenuation,
Window.WindowType windowType, boolean forceOddLength)
{
/* reverse the stop and pass frequency to get the low pass variant */
int tapCount = getTapCount(sampleRate,
stopFrequency,
passFrequency,
attenuation);
int tapCount = getTapCount(sampleRate, stopFrequency, passFrequency, attenuation);
if(forceOddLength)
{
@@ -360,11 +312,10 @@ public class FilterFactory
}
/**
* Determines the number of fir filter taps required to produce the
* specified frequency response with passband ripple near .1dB.
* Determines the number of fir filter taps required to produce the specified frequency response with passband
* ripple near 0.1dB.
*
* Implements the algorithm from Understanding Digital Signal Processing, 3e
* , Lyons, section 5.10.5.
* Implements the algorithm from Understanding Digital Signal Processing, 3e, Lyons, section 5.10.5.
*
* @param sampleRate in hertz
* @param pass pass frequency in hertz
@@ -372,21 +323,17 @@ public class FilterFactory
* @param attenuation in dB
* @return
*/
public static int getTapCount(int sampleRate,
long pass,
long stop,
int attenuation)
public static int getTapCount(int sampleRate, long pass, long stop, int attenuation)
{
double frequency = ((double) stop - (double) pass) / (double) sampleRate;
double frequency = ((double)stop - (double)pass) / (double)sampleRate;
return (int) (Math.round((double) attenuation / (22.0d * frequency)));
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)
public static ComplexPrimeCICDecimate getDecimationFilter(int sampleRate, int decimatedRate, int order,
int passFrequency, int attenuation, Window.WindowType windowType)
{
int decimationRate = (int) (sampleRate / decimatedRate);
int decimationRate = (int)(sampleRate / decimatedRate);
return new ComplexPrimeCICDecimate(decimationRate, order,
passFrequency, attenuation, windowType);
@@ -401,10 +348,7 @@ public class FilterFactory
* polyphase filter decimation chain
* @throws - AssertionException if sample rate is not a multiple of 48 kHz
*/
public static int[] getPolyphaseDecimationRates(int sampleRate,
int decimatedRate,
long passFrequency,
long stopFrequency)
public static int[] getPolyphaseDecimationRates(int sampleRate, int decimatedRate, long passFrequency, long stopFrequency)
{
int[] rates;
@@ -414,7 +358,7 @@ public class FilterFactory
+ "integer multiple of sample rate");
}
int decimation = (int) (sampleRate / decimatedRate);
int decimation = (int)(sampleRate / decimatedRate);
//Decimation rates below 20 will use single stage polyphase filter
if(decimation < 20)
@@ -432,11 +376,6 @@ public class FilterFactory
int stage1 = findClosest(optimalStage1, factors);
// mLog.info( "Decimation rate [" + decimation +
// "] stage1 optimal [" + optimalStage1 +
// "] stage1 actual [" + stage1 +
// "]");
if(stage1 == decimation || stage1 == 1)
{
rates = new int[1];
@@ -447,7 +386,7 @@ public class FilterFactory
{
rates = new int[2];
int stage2 = (int) (decimation / stage1);
int stage2 = (int)(decimation / stage1);
if(stage1 > stage2)
{
@@ -465,8 +404,7 @@ public class FilterFactory
}
/**
* Finds the factor that is closest to the desired factor, from an ordered
* list of factors.
* Finds the factor that is closest to the desired factor, from an ordered list of factors.
*/
private static int findClosest(int desiredFactor, Set<Integer> factors)
{
@@ -488,9 +426,8 @@ public class FilterFactory
}
/**
* Determines the factors that make up an integer. Uses a brute force
* method to iterate all integers from 1 to value, determining which factors
* are evenly divisible into the value.
* Determines the factors that make up an integer. Uses a brute force method to iterate all integers from 1 to
* value, determining which factors are evenly divisible into the value.
*
* @param value - integer decimation value
* @return - ordered set of factors for value
@@ -498,11 +435,11 @@ public class FilterFactory
private static Set<Integer> getFactors(int value)
{
Set<Integer> factors = new TreeSet<Integer>();
/* Brute force */
for(int x = 1; x <= value; x++)
{
int remainder = (int) (value / x);
int remainder = (int)(value / x);
if(remainder * x == value)
{
@@ -514,14 +451,10 @@ public class FilterFactory
}
/**
* Determines the optimal decimation rate for stage 1 of a two-stage
* poly-phase decimation filter chain, to produce a final sample rate of
* 48 kHz using a pass bandwidth of 25 kHz.
* Determines the optimal decimation rate for stage 1 of a two-stage poly-phase decimation filter chain, to produce
* a final sample rate of 48 kHz using a pass bandwidth of 25 kHz. Use for total decimation rates of 20 or higher.
*
* Use for total decimation rates of 20 or higher.
*
* Implements the algorithm described in Lyons, Understanding Digital Signal
* Processing, 3e, section 10.2.1, page 511.
* Implements the algorithm described in Lyons, Understanding Digital Signal Processing, 3e, section 10.2.1, page 511.
*
* @param sampleRate
* @param decimation
@@ -529,42 +462,29 @@ public class FilterFactory
* @return optimum integer decimation rate for the first stage decimation
* filter
*/
public static int getOptimalStageOneRate(int sampleRate,
int decimation,
long passFrequency,
long stopFrequency)
public static int getOptimalStageOneRate(int sampleRate, int decimation, long passFrequency, long stopFrequency)
{
double ratio = getBandwidthRatio(passFrequency, stopFrequency);
double numerator = 1.0d - (
Math.sqrt((double) decimation * ratio / (2.0d - ratio)));
double numerator = 1.0d - (Math.sqrt((double)decimation * ratio / (2.0d - ratio)));
double denominator = 2.0d - (ratio * (decimation + 1.0d));
int retVal = (int) (2.0d * decimation * (numerator / denominator));
// mLog.info( "Optimal Stage 1 Decimation - rate [" + sampleRate +
// "] pass [" + passFrequency +
// "] bw ratio [" + ratio +
// "] optimal [" + retVal +
// "]" );
int retVal = (int)(2.0d * decimation * (numerator / denominator));
return retVal;
}
/**
* Determines the F ratio as described in Lyons, Understanding Digital
* Signal Processing, 3e, section 10.2.1, page 511
*
* Used in conjunction with the optimal stage one decimation rate method
* above.
* Determines the F ratio as described in Lyons, Understanding Digital Signal Processing, 3e, section 10.2.1, page 511
* Used in conjunction with the optimal stage one decimation rate method above.
*/
private static double getBandwidthRatio(long passFrequency, long stopFrequency)
{
assert (passFrequency < stopFrequency);
return ((double) (stopFrequency - passFrequency) /
(double) stopFrequency);
return ((double)(stopFrequency - passFrequency) /
(double)stopFrequency);
}
/**
@@ -575,14 +495,11 @@ public class FilterFactory
* @param outputSampleRate
* @return
*/
public static float[] getCICCleanupFilter(int outputSampleRate,
int passFrequency,
int attenuation,
Window.WindowType window)
public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, Window.WindowType window)
{
int taps = getTapCount(outputSampleRate, passFrequency, passFrequency + 1500,
attenuation);
/* Make tap count odd */
if(taps % 2 == 0)
{
@@ -599,7 +516,7 @@ public class FilterFactory
//Transfer the IDFT results to the odd length return array
float[] coefficients = new float[taps];
int middleCoefficient = (int) (taps / 2);
int middleCoefficient = (int)(taps / 2);
//Bin 0 of the idft is our center coefficient
coefficients[middleCoefficient] = frequencyResponse[0];
@@ -620,26 +537,22 @@ public class FilterFactory
return coefficients;
}
public static float[] getCICResponseArray(int sampleRate,
int frequency,
int length)
public static float[] getCICResponseArray(int sampleRate, int frequency, int length)
{
float[] cicArray = new float[length * 2];
int binCount = (int) ((Math.round(
(double) frequency / (double) sampleRate * 2.0d * (double) length)));
int binCount = (int)((Math.round(
(double)frequency / (double)sampleRate * 2.0d * (double)length)));
cicArray[0] = 1.0f;
float unityResponse = (float) (Math.sin(1.0d / (double) length) /
(1.0d / (double) length));
float unityResponse = (float)(Math.sin(1.0d / (double)length) /
(1.0d / (double)length));
for(int x = 1; x <= binCount; x++)
{
/* Calculate unity response plus amplification for droop */
float compensated = 1.0f + (unityResponse -
(float) (Math.sin((double) x / (double) length) /
((double) x / (double) length)));
/* Calculate unity response plus amplification for droop */
float compensated = 1.0f + (unityResponse - (float)(Math.sin((double)x / (double)length) / ((double)x / (double)length)));
cicArray[x] = compensated;
cicArray[length - x] = compensated;
@@ -649,28 +562,29 @@ public class FilterFactory
}
/**
* Creates root raised cosine filter coefficients with a tap count equal
* to the symbols x samplesPerSymbol + 1.
* Creates root raised cosine filter coefficients with a tap count equal to the symbols x samplesPerSymbol + 1.
*
* Symbol count should be an even integer.
*
* Ported to java from gnuradio/filter/firdes.cc
*
* For 40db attenuation, calculate the number of symbols based on the
* following formula:
* For 40db attenuation, calculate the number of symbols based on the following formula:
* Symbols = -44 * alpha + 33
*
* Symbols = -44 * alpha + 33
* Polyphase Channelizer notes:
* -Set samples Per Symbol at 2 (or more) * number of channels
* -Set symbolCount to sufficient size to produce required attenuation
* -Set symbol rate in hertz
* -Set alpha = (desired channel bandwidth / (symbol rate * samples per symbol) - 1.0
* -Channel bandwidth must be larger than symbol rate * samples per symbol, in order for there to be a positive alpha
*
* @param samplesPerSymbol - number of samples per symbol
* @param symbols - number of symbols - must be even
* @param alpha - roll-off factor
* @param symbolCount - number of symbol intervals for the filter. This directly impacts the filter size
* @param alpha - roll-off factor.
* @return - filter coefficients
*/
public static float[] getRootRaisedCosine(int samplesPerSymbol,
int symbols,
float alpha)
public static float[] getRootRaisedCosine(int samplesPerSymbol, int symbolCount, float alpha)
{
int taps = samplesPerSymbol * symbols + 1;
int taps = samplesPerSymbol * symbolCount;
float scale = 0;
@@ -678,10 +592,10 @@ public class FilterFactory
for(int x = 0; x < taps; x++)
{
float index = (float) x - ((float) taps / 2.0f);
float index = (float)x - ((float)taps / 2.0f);
float x1 = (float) Math.PI * index / (float) samplesPerSymbol;
float x2 = 4.0f * alpha * index / (float) samplesPerSymbol;
float x1 = (float)Math.PI * index / (float)samplesPerSymbol;
float x2 = 4.0f * alpha * index / (float)samplesPerSymbol;
float x3 = x2 * x2 - 1.0f;
float numerator, denominator;
@@ -690,17 +604,15 @@ public class FilterFactory
{
if(x != taps / 2)
{
numerator = (float) Math.cos((1.0 + alpha) * x1) +
(float) Math.sin((1.0f - alpha) * x1) /
(4.0f * alpha * index / (float) samplesPerSymbol);
numerator = (float)Math.cos((1.0 + alpha) * x1) +
(float)Math.sin((1.0f - alpha) * x1) / (4.0f * alpha * index / (float)samplesPerSymbol);
}
else
{
numerator = (float) Math.cos((1.0f + alpha) * x1) +
(1.0f - alpha) * (float) Math.PI / (4.0f * alpha);
numerator = (float)Math.cos((1.0f + alpha) * x1) + (1.0f - alpha) * (float)Math.PI / (4.0f * alpha);
}
denominator = x3 * (float) Math.PI;
denominator = x3 * (float)Math.PI;
}
else
{
@@ -713,14 +625,11 @@ public class FilterFactory
x3 = (1.0f - alpha) * x1;
x2 = (1.0f + alpha) * x1;
numerator = (float) (Math.sin(x2) * (1.0f + alpha) * Math.PI -
Math.cos(x3) * ((1.0 - alpha) * Math.PI *
(double) samplesPerSymbol) / (4 * alpha * index) +
Math.sin(x3) * (double) samplesPerSymbol *
(double) samplesPerSymbol / (4.0 * alpha * index * index));
numerator = (float)(Math.sin(x2) * (1.0f + alpha) * Math.PI - Math.cos(x3) * ((1.0 - alpha) * Math.PI *
(double)samplesPerSymbol) / (4.0 * alpha * index) + Math.sin(x3) * (double)samplesPerSymbol *
(double)samplesPerSymbol / (4.0 * alpha * index * index));
denominator = (float) (-32.0 * Math.PI * alpha * alpha * index /
(double) samplesPerSymbol);
denominator = (float)(-32.0 * Math.PI * alpha * alpha * index / (double)samplesPerSymbol);
}
coefficients[x] = 4.0f * alpha * numerator / denominator;
@@ -740,10 +649,10 @@ public class FilterFactory
}
/**
* Creates a filter from the filter specification
* Creates a filter from the filter specification using the remez exchange design algorithm
*
* @param specification
* @return
* @return filter coefficients
* @throws FilterDesignException if the filter cannot be designed
*/
public static float[] getTaps(FIRFilterSpecification specification) throws FilterDesignException
@@ -757,4 +666,248 @@ public class FilterFactory
return null;
}
/**
* Evaluates the filter to determine the magnitude response (dB) at the specified frequency.
*
* @param filter to evaluate
* @param frequency to calculate (0 <> 0.5)
* @return magnitude frequency response in decibels
*/
public static double evaluate(float[] filter, double frequency)
{
double real = 0.0;
double imag = 0.0;
for(int x = 0; x < filter.length; x++)
{
real += filter[x] * Math.cos(Math.PI * frequency * (double)x);
imag += filter[x] * Math.sin(Math.PI * frequency * (double)x);
}
return decibel(real, imag);
}
/**
* Calculates the decibel magnitude of the real and imaginary complex sample
* @param real (x-axis) value
* @param imag (y-axis) value
* @return magnitude in decibels
*/
public static double decibel(double real, double imag)
{
return (float)(10.0 * Math.log10(Math.pow(real, 2.0) + Math.pow(imag, 2.0)));
}
public static float[] getRemezChannelizer(int channelBandwidth, int channels, int tapsPerChannel, double alpha,
double passRipple, double stopRipple) throws FilterDesignException
{
FIRFilterSpecification specification = FIRFilterSpecification.channelizerBuilder()
.sampleRate(channels * channelBandwidth * 4)
.channels(channels)
.channelBandwidth(channelBandwidth)
.tapsPerChannel(tapsPerChannel)
.alpha(alpha)
.passRipple(passRipple)
.stopRipple(stopRipple)
.build();
RemezFIRFilterDesignerWithLagrange designer = new RemezFIRFilterDesignerWithLagrange(specification);
float[] taps = designer.getImpulseResponse();
double bandEdgeFrequency = (double)channelBandwidth / (double)(channels * channelBandwidth * 2);
double response = decibel(designer.getFrequencyResponse(Math.cos(bandEdgeFrequency * Math.PI)), 0.0);
mLog.debug("Frequency Response at 1.0: " + response);
response = decibel(designer.getFrequencyResponse(Math.cos(bandEdgeFrequency * Math.PI * 2.0)), 0.0);
mLog.debug("Frequency Response at 2.0: " + response);
return taps;
}
/**
* Creates a windowed-sync (Nyquist) M/2 prototype FIR filter for use with a Polyphase Channelizer/Synthesizer.
* The filter is designed for x2 oversampling of each channel and the filter cutoff frequency is incrementally
* adjusted to achieve a -6.02 dB attenuation at the channel band edge to enable perfect reconstruction of adjacent
* channels.
*
* The odd length sync filter that meets the design objectives will be increased in length by pre-padding a zero
* coefficient to produce a filter with a length that is an integral of the channel count.
*
* Note: the tapsPerChannel value will be iteratively increased (up to 10x) if a filter cannot be designed to meet
* the channel band edge attenuation objective.
*
* Note: a channelizer filter designed using a Blackman-Harris-7 window and 18-21 taps per channel creates a filter
* with attenuation in range of -60 to -80 dB at the edge of the 2x sampled channel.
*
* @param channelBandwidth - full bandwidth of the channel in hertz
* @param channels - number of channels
* @param tapsPerChannel - desired filter tap count per channel (may be increased to meet objective)
* @param windowType - to use when designing the windowed-sync filter
* @param logResults - to log a debug summary of the filter characteristics
*
* @return filter coefficients
*
* @throws FilterDesignException if a filter cannot be designed with a band edge attenuation of -6.02 dB
*/
public static float[] getSincChannelizer(double channelBandwidth, int channels, int tapsPerChannel,
Window.WindowType windowType, boolean logResults) throws FilterDesignException
{
int currentTapsPerChannel = tapsPerChannel;
int sampleRate = (int)(channelBandwidth * (double)channels * 2.0);
int filterLength = (channels * currentTapsPerChannel) - 1;
double cutoffFrequency = channelBandwidth / (double)sampleRate;
double bandEdge = cutoffFrequency;
double increment = cutoffFrequency * 0.1;
//Get an initial filter and band edge frequency response using the channel bandwidth as a cutoff
float[] taps = FilterFactory.getSinc(cutoffFrequency, filterLength, windowType);
double response = FilterFactory.evaluate(taps, bandEdge);
//Set cutoff adjustment threshold - we'll test cutoff frequencies to around 1 hertz resolution
double incrementThreshold = 1.0 / (double)sampleRate;
//Iteratively evaluate filters to find the filter with the highest cutoff frequency that meets the objective
while(increment > incrementThreshold)
{
//If the current cutoff meets the objective, test if a higher cutoff also meets the objective
if(matchesObjective(response, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE) &&
(cutoffFrequency + increment <= bandEdge))
{
float[] higherCutoffTaps = FilterFactory.getSinc(cutoffFrequency + increment, filterLength, windowType);
double higherCutoffResponse = FilterFactory.evaluate(higherCutoffTaps, bandEdge);
if(matchesObjective(higherCutoffResponse, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE))
{
cutoffFrequency += increment;
taps = higherCutoffTaps;
response = higherCutoffResponse;
}
else
{
increment /= 2.0;
}
}
//If the current cutoff meets the objective, decrease the increment to test for smaller resolution cutoffs
else if(matchesObjective(response, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE))
{
increment /= 2.0;
}
//Decrease the cutoff frequency - we can't meet the objective
else
{
cutoffFrequency -= increment;
if(cutoffFrequency <= 0)
{
if(logResults)
{
mLog.debug("Warning: cannot design channelizer filter with tap count [" + currentTapsPerChannel +
"] increasing tap count and starting over");
}
currentTapsPerChannel++;
//After we've attempted increasing the taps per channel 10x, give up
if(currentTapsPerChannel > (tapsPerChannel + 10))
{
throw new FilterDesignException("Couldn't design filter with taps per channel count in the " +
"range of " + tapsPerChannel + " - " + (tapsPerChannel + 10));
}
filterLength = channels * currentTapsPerChannel - 1;
cutoffFrequency = ((double)channelBandwidth / (double)sampleRate);
increment = cutoffFrequency * 0.1;
}
taps = FilterFactory.getSinc(cutoffFrequency, filterLength, windowType);
response = FilterFactory.evaluate(taps, bandEdge);
}
}
//This will probably never happen, but ...
if(!matchesObjective(response, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE))
{
throw new FilterDesignException("Cannot design filter to specifications");
}
//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);
if(logResults)
{
mLog.debug("Polyphase Channelizer Filter Design Summary");
mLog.debug("-----------------------------------------------------");
mLog.debug("Input Sample Rate: " + sampleRate);
mLog.debug("Channel Bandwidth: " + channelBandwidth);
mLog.debug("Channels: " + channels);
mLog.debug("Window Type: " + windowType.name());
mLog.debug("Taps Per Channel - Requested:" + tapsPerChannel + " Actual:" + ((double)extendedTaps.length / (double)channels));
mLog.debug("Filter Length: " + (extendedTaps.length));
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency));
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));
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25));
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50));
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75));
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00));
}
return extendedTaps;
}
/**
* Creates a windowed-sync (Nyquist) filter.
*
* @param cutoff frequency (0.0 <> 0.5)
* @param length of the filter - must be odd-length
* @param windowType to use in designing the filter
* @return filter coefficients.
* @throws FilterDesignException if the requested length is not odd
*/
public static float[] getSinc(double cutoff, int length, Window.WindowType windowType) throws FilterDesignException
{
if(length % 2 == 0)
{
throw new FilterDesignException("Sinc filters must be odd-length");
}
float[] coefficients = new float[length];
int half = length / 2;
double[] window = Window.getWindow(windowType, length);
double scalor = 2.0 * cutoff;
double piScalor = Math.PI * scalor;
coefficients[half] = (float)(1.0 * scalor);
for(int x = 1; x <= half; x++)
{
double a = piScalor * x;
double coefficient = scalor * Math.sin(a) / a;
coefficient *= window[half + x];
coefficients[half + x] = (float)coefficient;
coefficients[half - x] = (float)coefficient;
}
return coefficients;
}
/**
* Compares two doubles for equals and avoids any rounding error that are present.
* @param a value to compare
* @param objective value to compare
* @return true if they are equivalent within the margin of error
*/
private static boolean matchesObjective(double a, double objective)
{
return Math.abs(a - objective) < 0.0000000001;
}
}

View File

@@ -1,234 +1,439 @@
/*******************************************************************************
* 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/>
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
/**
* Window creator
*
* Blackman, Hamming and Hanning windows
* based on windowing methods described at:
* http://www.mstarlabs.com/dsp/goertzel/goertzel.html
* and
* Understanding Digital Signal Processing 3e, Lyons, p.91
* and
* Digital Filters for Everyone, Allred, p. 132
*
* Window factory and corresponding utility methods.
*/
public class Window
{
public static double[] getWindow( WindowType type, int length )
private final static Logger mLog = LoggerFactory.getLogger(Window.class);
private static final double TWO_PI = Math.PI * 2.0d;
private static final double FOUR_PI = Math.PI * 4.0d;
private static final double SIX_PI = Math.PI * 6.0d;
private static final double EIGHT_PI = Math.PI * 8.0d;
/**
* Creates a window of the specified type and length.
*
* @param type of window
* @param length of window
* @return window of the specified type and length
*/
public static double[] getWindow(WindowType type, int length)
{
switch( type )
{
case BLACKMAN:
return getBlackmanWindow( length );
case COSINE:
return getCosineWindow( length );
case HAMMING:
return getHammingWindow( length );
case HANNING:
return getHanningWindow( length );
case NONE:
default:
return getRectangularWindow( length );
}
switch(type)
{
case BLACKMAN:
return getBlackman(length);
case BLACKMAN_HARRIS_4:
return getBlackmanHarris4(length);
case BLACKMAN_HARRIS_7:
return getBlackmanHarris7(length);
case BLACKMAN_NUTALL:
return getBlackmanNutall(length);
case COSINE:
return getCosine(length);
case FLAT_TOP:
return getFlatTop(length);
case HAMMING:
return getHamming(length);
case HANN:
return getHann(length);
case NUTALL:
return getNutall(length);
case NONE:
default:
return getRectangular(length);
}
}
public static double[] getRectangularWindow( int length )
/**
* Creates an all-pass or rectangular window.
*
* @param length of the window
* @return rectangular window
*/
public static double[] getRectangular(int length)
{
double[] coefficients = new double[ length ];
double[] coefficients = new double[length];
Arrays.fill( coefficients, 1.0d );
return coefficients;
}
public static double[] getCosineWindow( int length )
{
double[] coefficients = new double[ length ];
Arrays.fill(coefficients, 1.0d);
if( length % 2 == 0 ) //Even length
{
int half = (int)( ( length - 1 ) / 2 );
for( int x = -half; x < length / 2 + 1; x++ )
{
coefficients[ x + half ] = Math.cos(
( (double)x * Math.PI ) / ( (double)length + 1.0d ) );
}
}
else //Odd length
{
int half = (int) length / 2;
for( int x = -half; x < half + 1; x++ )
{
coefficients[ x + half ] = Math.cos(
( (double)x * Math.PI ) / ( (double)length + 1.0d ) );
}
}
return coefficients;
return coefficients;
}
public static double[] getBlackmanWindow( int length )
/**
* Creates a cosine window of the specified length.
*
* @param length of the window
* @return cosine window
*/
public static double[] getCosine(int length)
{
double[] coefficients = new double[ length ];
for( int x = 0; x < length; x++ )
{
coefficients[ x ] = .426591D -
( .496561D * Math.cos( ( Math.PI * 2.0D * (double)x ) / (double)( length - 1 ) ) +
( .076848D * Math.cos( ( Math.PI * 4.0D * (double)x ) / (double)( length - 1 ) ) ) );
}
double[] coefficients = new double[length];
return coefficients;
}
public static double[] getHammingWindow( int length )
{
double[] coefficients = new double[ length ];
if( length % 2 == 0 ) //Even length
{
for( int x = 0; x < length; x++ )
{
coefficients[ x ] = .54D - ( .46D *
Math.cos( ( Math.PI * ( 2.0D * (double)x + 1.0d ) ) / (double)( length - 1 ) ) );
}
}
else //Odd length
{
for( int x = 0; x < length; x++ )
{
coefficients[ x ] = .54D - ( .46D *
Math.cos( ( 2.0D * Math.PI * (double)x ) / (double)( length - 1 ) ) );
}
}
if(length % 2 == 0) //Even length
{
int half = (int)((length - 1) / 2);
return coefficients;
for(int x = -half; x < length / 2 + 1; x++)
{
coefficients[x + half] = Math.cos(
((double)x * Math.PI) / ((double)length + 1.0d));
}
}
else //Odd length
{
int half = (int)length / 2;
for(int x = -half; x < half + 1; x++)
{
coefficients[x + half] = Math.cos(
((double)x * Math.PI) / ((double)length + 1.0d));
}
}
return coefficients;
}
public static double[] getHanningWindow( int length )
/**
* Creates a 3-term Blackman window.
*
* @param length of the window
* @return Blackman window
*/
public static double[] getBlackman(int length)
{
double[] coefficients = new double[ length ];
double[] coefficients = new double[length];
if( length % 2 == 0 ) //Even length
{
for( int x = 0; x < length; x++ )
{
coefficients[ x ] = .5D - ( .5D *
Math.cos( ( Math.PI * ( 2.0D * (double)x + 1 ) ) / (double)( length - 1 ) ) );
}
}
else //Odd length
{
for( int x = 0; x < length; x++ )
{
coefficients[ x ] = .5D - ( .5D *
Math.cos( ( 2.0D * Math.PI * (double)x ) / (double)( length - 1 ) ) );
}
}
double denominator = length - 1;
double a0 = 0.426590713672;
double a1 = 0.496560619089;
double a2 = 0.0768486672399;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * Math.cos((TWO_PI * (double)x) / denominator)) +
(a2 * Math.cos((FOUR_PI * (double)x) / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Nutall window
*
* @param length of the window
* @return Nutall window
*/
public static double[] getNutall(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.355768;
double a1 = 0.487396;
double a2 = 0.144232;
double a3 = 0.012604;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * Math.cos(TWO_PI * (double)x / denominator)) +
(a2 * Math.cos(FOUR_PI * (double)x / denominator)) -
(a3 * Math.cos(SIX_PI * (double)x / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Nutall window
*
* @param length of the window
* @return Blackman-Nutall window
*/
public static double[] getBlackmanNutall(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.3635819;
double a1 = 0.4891775;
double a2 = 0.1365995;
double a3 = 0.0106411;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * Math.cos(TWO_PI * (double)x / denominator)) +
(a2 * Math.cos(FOUR_PI * (double)x / denominator)) -
(a3 * Math.cos(SIX_PI * (double)x / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static double[] getBlackmanHarris4(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.35875;
double a1 = 0.48829;
double a2 = 0.14128;
double a3 = 0.01168;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * Math.cos(TWO_PI * (double)x / denominator)) +
(a2 * Math.cos(FOUR_PI * (double)x / denominator)) -
(a3 * Math.cos(SIX_PI * (double)x / denominator));
}
return coefficients;
}
/**
* Creates a 7-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static double[] getBlackmanHarris7(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.27105140069342;
double a1 = 0.43329793923448;
double a2 = 0.21812299954311;
double a3 = 0.06592544638803;
double a4 = 0.01081174209837;
double a5 = 0.00077658482522;
double a6 = 0.00001388721735;
return coefficients;
for(int x = 0; x < length; x++)
{
double w = TWO_PI * (double)x / denominator;
coefficients[x] = a0 -
(a1 * Math.cos(w)) +
(a2 * Math.cos(2.0 * w)) -
(a3 * Math.cos(3.0 * w)) +
(a4 * Math.cos(4.0 * w)) -
(a5 * Math.cos(5.0 * w)) +
(a6 * Math.cos(6.0 * w));
}
return coefficients;
}
/**
* Creates a Hamming window
*
* @param length of the window
* @return Hamming window
*/
public static double[] getHamming(int length)
{
double[] coefficients = new double[length];
double a0 = 0.54;
double a1 = 0.46;
double denominator = length;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 - (a1 * Math.cos(TWO_PI * (double)x / denominator));
}
return coefficients;
}
/**
* Creates a Hann window
*
* @param length of the window
* @return Hann window
*/
public static double[] getHann(int length)
{
double[] coefficients = new double[length];
double a0 = 0.5;
double a1 = 0.5;
double denominator = length;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 - (a1 * Math.cos(TWO_PI * (double)x / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static double[] getFlatTop(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.215578948;
double a1 = 0.41663158;
double a2 = 0.277263158;
double a3 = 0.083578947;
double a4 = 0.006947368;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * Math.cos(TWO_PI * (double)x / denominator)) +
(a2 * Math.cos(FOUR_PI * (double)x / denominator)) -
(a3 * Math.cos(SIX_PI * (double)x / denominator)) +
(a4 * Math.cos(EIGHT_PI * (double)x / denominator));
}
return coefficients;
}
private static double getKaiserBeta(double attenuation)
{
if(attenuation > 50.0)
{
return 0.1102 * (attenuation - 8.7);
}
else if(attenuation >= 21.0)
{
return (0.5842 * Math.pow(attenuation - 21.0, 0.4)) + (0.07886 * (attenuation - 21.0));
}
else
{
return 0.0;
}
}
/**
* Apply the window against an array of float-type samples
*/
public static float[] apply( double[] coefficients, float[] samples )
public static float[] apply(double[] coefficients, float[] samples)
{
for( int x = 0; x < coefficients.length; x++ )
{
samples[ x ] = (float)( samples[ x ] * coefficients[ x ] );
}
return samples;
}
public static float[] apply( WindowType type, float[] samples )
{
double[] coefficients = getWindow( type, samples.length );
return apply( coefficients, samples );
}
/**
* Apply the window against an array of float-type samples
*/
public static double[] apply( double[] coefficients, double[] samples )
{
for( int x = 0; x < coefficients.length; x++ )
{
samples[ x ] = samples[ x ] * coefficients[ x ];
}
return samples;
for(int x = 0; x < coefficients.length; x++)
{
samples[x] = (float)(samples[x] * coefficients[x]);
}
return samples;
}
public static double[] apply( WindowType type, double[] samples )
/**
* Apply the window type against the float array of samples
*
* @param type of window to use
* @param samples to be windowed
* @return windowed samples
*/
public static float[] apply(WindowType type, float[] samples)
{
double[] coefficients = getWindow( type, samples.length );
return apply( coefficients, samples );
double[] coefficients = getWindow(type, samples.length);
return apply(coefficients, samples);
}
/**
* Apply the window against an array of double-type samples
*/
public static double[] apply(double[] coefficients, double[] samples)
{
for(int x = 0; x < coefficients.length; x++)
{
samples[x] = samples[x] * coefficients[x];
}
return samples;
}
/**
* Apply the window type against the double array of samples
*
* @param type of window to use
* @param samples to be windowed
* @return windowed samples
*/
public static double[] apply(WindowType type, double[] samples)
{
double[] coefficients = getWindow(type, samples.length);
return apply(coefficients, samples);
}
/**
* Window types
*/
public enum WindowType
{
BLACKMAN, COSINE, HAMMING, HANNING, NONE;
}
/**
* Utility to log the double arrays
*/
public static String arrayToString( double[] array, boolean breaks )
{
StringBuilder sb = new StringBuilder();
for( int x = 0; x < array.length; x++ )
{
sb.append( x + ":" + array[ x ] );
BLACKMAN("Blackman"),
BLACKMAN_HARRIS_4("Blackman-Harris 4"),
BLACKMAN_HARRIS_7("Blackman-Harris 7"),
BLACKMAN_NUTALL("Blackman-Nutall"),
COSINE("Cosine"),
FLAT_TOP("Flat Top"),
HAMMING("Hamming"),
HANN("Hann"),
NUTALL("Nutall"),
NONE("None");
if( breaks )
{
sb.append( "\n" );
}
else
{
if( x % 8 == 7 )
{
sb.append( "\n" );
}
else
{
sb.append( "\t" );
}
}
}
return sb.toString();
}
private String mLabel;
WindowType(String label)
{
mLabel = label;
}
public String toString()
{
return mLabel;
}
}
}

View File

@@ -0,0 +1,144 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<ComplexBuffer>, ISourceEventListener
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class);
private Broadcaster<SourceEvent> mSourceChangeBroadcaster = new Broadcaster();
private List<PolyphaseChannelSource> mChannels = new CopyOnWriteArrayList<>();
private double mSampleRate;
private int mChannelCount;
private double mChannelSampleRate;
/**
* Complex sample polyphase channelizer
*
* @param channelCount
*/
public AbstractComplexPolyphaseChannelizer(double sampleRate, int channelCount)
{
mChannelCount = channelCount;
mSampleRate = sampleRate;
mChannelSampleRate = (double)mSampleRate / (double)mChannelCount;
}
/**
* Input sample rate for this channelizer
* @return sample rate in hertz
*/
public double getSampleRate()
{
return mSampleRate;
}
/**
* Sets the input sample rate for for this channelizer
* @param sampleRate in hertz
*/
public void setSampleRate(double sampleRate)
{
mSampleRate = sampleRate;
mChannelSampleRate = mSampleRate / (double)mChannelCount;
}
/**
* Number of channels being processed by this channelizer.
*/
public int getChannelCount()
{
return mChannelCount;
}
/**
* Output channel sample rate
* @return sample rate in hertz
*/
public double getChannelSampleRate()
{
return mChannelSampleRate;
}
/**
* Dispatches the processed channel samples to any registered polyphase channel outputs.
*
* @param channels containing an array of I/Q samples per channel
*/
protected void dispatch(float[] channels)
{
for(PolyphaseChannelSource channel : mChannels)
{
channel.receiveChannelResults(channels);
}
}
/**
* Adds the polyphase channel source to receive processed output channel samples
*
* @param polyphaseChannelSource
*/
public void addChannel(PolyphaseChannelSource polyphaseChannelSource)
{
if(polyphaseChannelSource != null && !mChannels.contains(polyphaseChannelSource))
{
mChannels.add(polyphaseChannelSource);
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
}
}
/**
* Removes the polyphase channel source from receiving output channel samples.
*
* @param polyphaseChannelSource
*/
public void removeChannel(PolyphaseChannelSource polyphaseChannelSource)
{
if(polyphaseChannelSource != null && mChannels.contains(polyphaseChannelSource))
{
mChannels.remove(polyphaseChannelSource);
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
}
}
/**
* Number of polyphase channels registered to receive sample streams
*/
public int getRegisteredChannelCount()
{
return mChannels.size();
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
return mSourceChangeBroadcaster;
}
}

View File

@@ -0,0 +1,586 @@
/*******************************************************************************
* *********************************************************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
* *********************************************************************************************************************
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class ChannelCalculator
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
/**
* Policy to determine desired corrective action for methods that calculate an index for a frequency or a frequency
* from an index where the frequency may exist either at the overlapping boundary of two indexes, or in the middle
* of the wrap-around (N/2) index.
*/
private enum IndexBoundaryPolicy
{
ADJUST_POSITIVE,
ADJUST_NEGATIVE
}
private double mSampleRate;
private int mChannelCount;
private double mCenterFrequency;
private double mChannelBandwidth;
private double mOversampling = 1.0;
/**
* Calculates channel index(es) from a polyphase channelizer output to source a DDC polyphase channel source
* with a specified channel center frequency and bandwidth.
*
* Polyphase channel indexes are not linear/consecutive owing to the use of the FFT to rotate channels to their
* center frequencies following filtering in the polyphase filter bank. As such, this calculator provides methods
* to determine the min/center/max frequency of each channel that is adjusted for the current center frequency
* value and to determine the channel index(es) that need to be targeted to produce a requested output channel
* with a specific frequency and bandwdith.
*
* @param sampleRate in hertz for the input sample rate from the sample source (ie tuner)
* @param channelCount number of channels in the polyphase filter bank
* @param centerFrequency center frequency for the input sample stream
* @param oversampling set to 1.0 for no oversample, or greater than 1.0 for oversampling
*/
public ChannelCalculator(double sampleRate, int channelCount, double centerFrequency, double oversampling)
{
mSampleRate = sampleRate;
mChannelCount = channelCount;
mCenterFrequency = centerFrequency;
mOversampling = oversampling;
updateChannelBandwidth();;
}
/**
* Calculates the channel bandwidth from the sample rate and channel count.
*/
private void updateChannelBandwidth()
{
mChannelBandwidth = (double)mSampleRate / (double)mChannelCount;
}
/**
* Center frequency for the channelizer
*/
public double getCenterFrequency()
{
return mCenterFrequency;
}
/**
* Updates the center frequency for the channelizer
*/
public void setCenterFrequency(double frequency)
{
mCenterFrequency = frequency;
}
/**
* Number of channels being channelized by the polyphase channelizer
*/
public int getChannelCount()
{
return mChannelCount;
}
/**
* Updates the number of channels being channelized by the polyphase channelizer
*/
public void setChannelCount(int channelCount)
{
mChannelCount = channelCount;
updateChannelBandwidth();
}
/**
* Identifies the index of the wrap-around channel meaning that the lower half of the channel exists at the upper
* end of the spectrum and the upper half of the channel exists at the lower end of the spectrum
*
* @return wrap-around channel index
*/
public int getWrapAroundIndex()
{
return mChannelCount / 2;
}
/**
* PolyphaseChannelManager input sample rate
* @return input sample rate in hertz
*/
public double getSampleRate()
{
return mSampleRate;
}
/**
* Updates the channel count using the sample rate argument to calculate the number of channels from the
* already established channel bandwidth.
*
* @param sampleRate to use for channel count
*/
public void setSampleRate(double sampleRate)
{
setChannelCount((int)(sampleRate / getChannelBandwidth()));
}
/**
* Channel bandwidth. Note: this value may be smaller than the channel sample rate when oversampling
* is being used.
*/
public double getChannelBandwidth()
{
return mChannelBandwidth;
}
/**
* Half of the channel bandwidth.
*/
public double getHalfChannelBandwidth()
{
return mChannelBandwidth / 2.0;
}
/**
* Oversampling rate produced by the channelizer.
*
* @return 1.0 for no oversampling, or a value greater than 1.0 when oversampling is applied.
*/
public double getOversampling()
{
return mOversampling;
}
/**
* Sets the oversampling rate being applied by the polyphase channelizer
* @param oversampling rate where 1.0 indicates no oversampling, and a value greater than 1.0 indicates
* that the polyphase channelizer is oversampling each channel.
*/
public void setOversampling(double oversampling)
{
mOversampling = oversampling;
}
/**
* Channel sample rate. This value is the product of the filtered channel bandwidth times the
* oversampling rate.
*/
public double getChannelSampleRate()
{
return (double)getChannelBandwidth() * getOversampling();
}
/**
* PolyphaseChannelManager input minimum frequency
* @return minimum frequency in hertz
*/
public long getMinimumFrequency()
{
return (long)(getCenterFrequency() - (getSampleRate() / 2.0));
}
/**
* PolyphaseChannelManager input maximum frequency
* @return maximum frequency in hertz
*/
public long getMaximumFrequency()
{
return (long)(getCenterFrequency() + (getSampleRate() / 2.0));
}
/**
* Identifies the channelizer channel index(es) required to produce a tuner channel with the specified center
* frequency and channel bandwidth.
*
* @param tunerChannel specifying the desired channel frequency and bandwidth
*
* @return an array of one or more polyphase channelizer index(es) to target in order to produce an output channel
* for the desired frequency and bandwidth.
*
* @throws IllegalArgumentException if the tuner channel's minimum or maximum frequency is not contained
* within the channelizer's minimum or maximum frequency range
*/
public List<Integer> getChannelIndexes(TunerChannel tunerChannel) throws IllegalArgumentException
{
if(tunerChannel.getMinFrequency() < getMinimumFrequency() ||
tunerChannel.getMaxFrequency() > getMaximumFrequency())
{
throw new IllegalArgumentException("Requested channel cannot be provided by this " +
"channelizer. Requested channel [" + tunerChannel.getMinFrequency() + " - " +
tunerChannel.getMaxFrequency() + "] exceeds current channelizer frequency range [" +
getMinimumFrequency() + " - " + getMaximumFrequency() + "]");
}
int minIndex = getIndexForFrequency(tunerChannel.getMinFrequency(), IndexBoundaryPolicy.ADJUST_POSITIVE);
int maxIndex = getIndexForFrequency(tunerChannel.getMaxFrequency(), IndexBoundaryPolicy.ADJUST_NEGATIVE);
//Detect the unsourceable situation where the wrap around index would be included in both the minimum index
//and the maximum index, meaning that the tuner channel bandwidth is nearly the same as the input sample rate.
if(minIndex == getWrapAroundIndex() && maxIndex == getWrapAroundIndex())
{
throw new IllegalArgumentException("Requested tuner channel cannot be provided. Requested bandwidth is " +
"within two channel bandwidths of the sample rate.");
}
List<Integer> indexes = new ArrayList<>();
indexes.add(minIndex);
if(minIndex != maxIndex)
{
//Ensure our min/max indexes are within the correct bounds 0 <> N-1 to safeguard the while loop below
if(minIndex < 0 || minIndex >= getChannelCount() || maxIndex < 0 || maxIndex >= getChannelCount())
{
throw new IllegalArgumentException("Something went wrong while calculating the min and max polyphase " +
"channel indexes for tuner channel: " + tunerChannel);
}
int pointer = minIndex + 1;
if(pointer >= getChannelCount())
{
pointer -= getChannelCount();
}
while(pointer != maxIndex)
{
indexes.add(pointer);
pointer++;
if(pointer >= getChannelCount())
{
pointer -= getChannelCount();
}
}
indexes.add(maxIndex);
}
return indexes;
}
/**
* Calculates the index for the specified frequency. Certain frequencies can exist as both the maximum frequency
* of one channel and the minimum frequency of an adjacent channel. The index boundary policy identifies your
* choice to select the higher frequency channel index (ADJUST_POSITIVE) or the lower frequency channel
* index (ADJUST_NEGATIVE).
*
* @param frequency to determine channel index for
* @param indexBoundaryPolicy to determine the behavior when the frequency overlaps two channel boundaries
* @return index for the frequency
*/
public int getIndexForFrequency(long frequency, IndexBoundaryPolicy indexBoundaryPolicy)
{
double offset = frequency - getCenterFrequency();
if(Math.abs(offset) < getHalfChannelBandwidth())
{
return 0; //Center index
}
if(offset > 0)
{
offset += getHalfChannelBandwidth();
}
else
{
offset -= getHalfChannelBandwidth();
}
int indexOffset = (int)(offset / getChannelBandwidth());
if(indexOffset < 0)
{
indexOffset += getChannelCount();
}
if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_POSITIVE &&
isOverlapFrequency(frequency, indexOffset, indexOffset + 1))
{
indexOffset = normalize(indexOffset + 1);
}
else if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_NEGATIVE &&
isOverlapFrequency(frequency, indexOffset - 1, indexOffset))
{
indexOffset = normalize(indexOffset - 1);
}
return indexOffset;
}
/**
* Indicates if the frequency is an overlapping frequency between channel index 1 and channel index 2 where the
* maximum frequency of index 1 is the same as the minimum frequency of index 2 and both of these frequencies are
* the same as the frequency argument.
*
* Note: both index 1 and index 2 will be normalized to the range of 0 <> N-1 prior to determining overlap
*
* @param frequency to test for overlap between index 1 and 2
* @param index1 which is the lower channel index located next to index 2.
* @param index2 which is the higher channel index located next to index 1.
* @return true if frequency = index1 maximum frequency = index2 minimum frequency
*/
public boolean isOverlapFrequency(long frequency, int index1, int index2)
{
index1 = normalize(index1);
index2 = normalize(index2);
//Check that these two indexes are side by side
int delta = normalize(index2 - index1);
if(delta != 1)
{
return false;
}
long index1MaximumFrequency = (long)getIndexMaximumFrequency(index1, IndexBoundaryPolicy.ADJUST_POSITIVE);
long index2MinimumFrequency = (long)getIndexMinimumFrequency(index2, IndexBoundaryPolicy.ADJUST_NEGATIVE);
if(index1 == getWrapAroundIndex())
{
index1MaximumFrequency = (long)getIndexMaximumFrequency(index1, IndexBoundaryPolicy.ADJUST_NEGATIVE);
}
if(index2 == getWrapAroundIndex())
{
index2MinimumFrequency = (long)getIndexMinimumFrequency(index2, IndexBoundaryPolicy.ADJUST_POSITIVE);
}
return frequency == index1MaximumFrequency && frequency == index2MinimumFrequency;
}
/**
* Normalizes the index to the range of 0 <> N-1 channel indexes</>
*/
private int normalize(int index)
{
while(index < 0)
{
index += getChannelCount();
}
while(index >= getChannelCount())
{
index -= getChannelCount();
}
return index;
}
/**
* Calculates the center frequency for the channel identified by the index in the channel results array.
*
* @param index to determine the channel center frequency
* @return center frequency for the indexed channel
*/
public double getIndexCenterFrequency(int index, IndexBoundaryPolicy indexBoundaryPolicy)
{
if(index < 0 || index >= getChannelCount())
{
throw new IllegalArgumentException("Illegal channel index");
}
int wrapAroundIndex = getWrapAroundIndex();
if(index == wrapAroundIndex)
{
if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_POSITIVE)
{
return getCenterFrequency() + (index * getChannelBandwidth());
}
else
{
return getCenterFrequency() - (index * getChannelBandwidth());
}
}
else if(index < wrapAroundIndex)
{
return getCenterFrequency() + (index * getChannelBandwidth());
}
else
{
return getCenterFrequency() - ((getChannelCount() - index) * getChannelBandwidth());
}
}
/**
* Calculates the minimum frequency band edge for the channel identified by the index in the channel results
* array.
* @param index to calculate the minimum band edge frequency for the channel.
* @param indexBoundaryPolicy for the wrap around index to indicate if this method should return the minimum
* frequency of the upper spectrum half of the wrap around channel (ADJUST_POSITIVE) or the minimum frequency of the
* lower spectrum half of the wrap around channel (ADJUST_NEGATIVE)
* @return frequency of the minimum band edge
*/
public double getIndexMinimumFrequency(int index, IndexBoundaryPolicy indexBoundaryPolicy)
{
if(index < 0 || index >= getChannelCount())
{
throw new IllegalArgumentException("Can't determine minimum frequency - illegal channel index [" +
index + "]");
}
int wrapAroundIndex = getWrapAroundIndex();
if(index == wrapAroundIndex)
{
if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_POSITIVE)
{
return getCenterFrequency() + ((double)index * getChannelBandwidth()) - getHalfChannelBandwidth();
}
else
{
return getIndexCenterFrequency(index, indexBoundaryPolicy);
}
}
else if(index <= wrapAroundIndex)
{
return getCenterFrequency() + ((double)index * getChannelBandwidth()) - getHalfChannelBandwidth();
}
else
{
return getCenterFrequency() - ((double)(getChannelCount() - index) * getChannelBandwidth()) -
getHalfChannelBandwidth();
}
}
/**
* Calculates the maximum frequency band edge for the channel identified by the index in the channel results
* array.
* @param index to calculate the maximum band edge frequency for the channel.
* @param indexBoundaryPolicy for the wrap around index to indicate if this method should return the maximum
* frequency of the upper spectrum half of the wrap around channel (ADJUST_POSITIVE) or the maximum frequency of the
* lower spectrum half of the wrap around channel (ADJUST_NEGATIVE)
* @return frequency of the maximum band edge
*/
public double getIndexMaximumFrequency(int index, IndexBoundaryPolicy indexBoundaryPolicy)
{
if(index < 0 || index >= getChannelCount())
{
throw new IllegalArgumentException("Can't determine maximum frequency - illegal channel index [" +
index + "]");
}
int wrapAroundIndex = getWrapAroundIndex();
if(index == wrapAroundIndex)
{
if(indexBoundaryPolicy == IndexBoundaryPolicy.ADJUST_POSITIVE)
{
return getIndexCenterFrequency(index, indexBoundaryPolicy);
}
else
{
return getCenterFrequency() - ((double)index * getChannelBandwidth() - getHalfChannelBandwidth());
}
}
else if(index <= wrapAroundIndex)
{
return getCenterFrequency() + ((double)index * getChannelBandwidth()) + getHalfChannelBandwidth();
}
else
{
return getCenterFrequency() - ((double)(getChannelCount() - index) * getChannelBandwidth()) +
getHalfChannelBandwidth();
}
}
/**
* Calculates the center frequency for a channel that will be synthesized from the channel indexes.
* @param indexes that are contiguous in the spectrum
* @return center frequency in hertz
*/
public long getCenterFrequencyForIndexes(List<Integer> indexes)
{
if(indexes.isEmpty())
{
throw new IllegalArgumentException("Indexes cannot be empty");
}
int middleIndex = indexes.get(indexes.size() / 2);
if(indexes.size() % 2 == 0)
{
//Even number of indexes
return (long)getIndexMinimumFrequency(middleIndex, IndexBoundaryPolicy.ADJUST_POSITIVE);
}
else
{
//Odd number of indexes
return (long)getIndexCenterFrequency(middleIndex, IndexBoundaryPolicy.ADJUST_POSITIVE);
}
}
/**
* String representation of the channel calculator settings
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Channels:").append(getChannelCount());
sb.append(" Sample Rate:").append(getSampleRate());
sb.append(" Channel Bandwidth:").append(getChannelBandwidth());
sb.append(" Channel Rate:").append(getChannelSampleRate());
sb.append(" Min:").append(getMinimumFrequency());
sb.append(" Max:").append(getMaximumFrequency());
return sb.toString();
}
public static void main(String[] args)
{
mLog.debug("Starting ....");
int channelBandwidth = 12500;
int sampleRate = 100000; //Airspy
int channelCount = sampleRate / channelBandwidth;
double centerFrequency = 455000000;
ChannelCalculator calculator = new ChannelCalculator(sampleRate, channelCount, centerFrequency, 2.0);
mLog.debug(calculator.toString());
DecimalFormat decimalFormat = new DecimalFormat("000,000,000.0");
int wrapAroundIndex = calculator.getWrapAroundIndex();
IndexBoundaryPolicy indexBoundaryPolicy = IndexBoundaryPolicy.ADJUST_NEGATIVE;
for(int index = wrapAroundIndex; index < calculator.getChannelCount(); index++)
{
mLog.debug("Index:" + index + " Min:" + decimalFormat.format(calculator.getIndexMinimumFrequency(index, indexBoundaryPolicy)) +
" Center:" + decimalFormat.format(calculator.getIndexCenterFrequency(index, indexBoundaryPolicy)) +
" Max:" + decimalFormat.format(calculator.getIndexMaximumFrequency(index, indexBoundaryPolicy)));
}
indexBoundaryPolicy = IndexBoundaryPolicy.ADJUST_POSITIVE;
for(int index = 0; index <= wrapAroundIndex; index++)
{
mLog.debug("Index:" + index + " Min:" + decimalFormat.format(calculator.getIndexMinimumFrequency(index, indexBoundaryPolicy)) +
" Center:" + decimalFormat.format(calculator.getIndexCenterFrequency(index, indexBoundaryPolicy)) +
" Max:" + decimalFormat.format(calculator.getIndexMaximumFrequency(index, indexBoundaryPolicy)));
}
mLog.debug("Finished");
}
}

View File

@@ -0,0 +1,344 @@
/*******************************************************************************
* *********************************************************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
* *********************************************************************************************************************
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import io.github.dsheirer.source.tuner.test.TestTuner;
import io.github.dsheirer.spectrum.DFTProcessor;
import io.github.dsheirer.spectrum.DFTSize;
import io.github.dsheirer.spectrum.SpectrumPanel;
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.EventQueue;
public class ChannelizerViewer extends JFrame
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer.class);
private static final int CHANNEL_BANDWIDTH = 12500;
private static final int CHANNEL_FFT_FRAME_RATE = 20;
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
private JPanel mPrimaryPanel;
private JPanel mControlPanel;
private PrimarySpectrumPanel mPrimarySpectrumPanel;
private ChannelArrayPanel mChannelPanel;
private int mChannelCount;
private int mChannelsPerRow;
private long mToneFrequency;
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
private DFTSize mChannelPanelDFTSize = DFTSize.FFT01024;
private TestTuner mTestTuner;
/**
* GUI Test utility for researching polyphase channelizers.
*
* @param channelsPerRow
*/
public ChannelizerViewer(int channelsPerRow)
{
mTestTuner = new TestTuner();
mChannelCount = (int)(mTestTuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH);
mChannelsPerRow = channelsPerRow;
mToneFrequency = mTestTuner.getTunerController().getFrequency() + 19200;
mTestTuner.getTunerController().setToneFrequency(mToneFrequency);
init();
}
private void init()
{
setTitle("TunerChannelizer Viewer");
setSize(1000, 800);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
setLocationRelativeTo(null);
add(getPrimaryPanel());
}
private JPanel getPrimaryPanel()
{
if(mPrimaryPanel == null)
{
mPrimaryPanel = new JPanel();
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][][grow,fill]"));
mPrimaryPanel.add(getSpectrumPanel(), "wrap");
mPrimaryPanel.add(getControlPanel(), "wrap");
mPrimaryPanel.add(getChannelArrayPanel());
}
return mPrimaryPanel;
}
private PrimarySpectrumPanel getSpectrumPanel()
{
if(mPrimarySpectrumPanel == null)
{
mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager,
mTestTuner.getTunerController().getSampleRate());
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
mTestTuner.getSourceManager().addComplexBufferListener(mPrimarySpectrumPanel);
}
return mPrimarySpectrumPanel;
}
private JPanel getControlPanel()
{
if(mControlPanel == null)
{
mControlPanel = new JPanel();
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
mControlPanel.add(new JLabel("Tone:"), "align left");
int maximumFrequencyOffset = (int)(mTestTuner.getTunerController().getSampleRate() / 2);
long minimumFrequency = mTestTuner.getTunerController().getFrequency() - maximumFrequencyOffset;
long maximumFrequency = mTestTuner.getTunerController().getFrequency() + maximumFrequencyOffset;
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, minimumFrequency, maximumFrequency,
100 );
model.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int toneFrequency = model.getNumber().intValue();
mTestTuner.getTunerController().setToneFrequency(toneFrequency);
}
});
JSpinner spinner = new JSpinner(model);
mControlPanel.add(spinner);
mControlPanel.add(new JLabel("Hz"));
mControlPanel.add(new JLabel("Sample Rate: " + mTestTuner.getTunerController().getSampleRate() + " Hz"), "push,align right");
mControlPanel.add(new JLabel("Max: " + (mTestTuner.getTunerController().getSampleRate() / 2) + " Hz"), "push,align right");
mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right");
}
return mControlPanel;
}
private ChannelArrayPanel getChannelArrayPanel()
{
if(mChannelPanel == null)
{
mChannelPanel = new ChannelArrayPanel();
}
return mChannelPanel;
}
public class ChannelArrayPanel extends JPanel
{
public ChannelArrayPanel()
{
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
if(bufferSize % 2 == 1)
{
bufferSize++;
}
init();
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill"));
double spectralBandwidth = mTestTuner.getTunerController().getSampleRate();
double halfSpectralBandwidth = spectralBandwidth / 2.0;
int channelToLog = -1;
for(int x = 0; x < mChannelCount; x++)
{
//place the channels left and right of 10.0 MHz
long frequency = (long)(mTestTuner.getTunerController().getFrequency() +
((x * CHANNEL_BANDWIDTH) - halfSpectralBandwidth) + (CHANNEL_BANDWIDTH / 2));
mLog.debug("Channel " + x + " Frequency: " + frequency);
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH, (x == channelToLog));
channelPanel.setDFTSize(mChannelPanelDFTSize);
if(x % mChannelsPerRow == mChannelsPerRow - 1)
{
add(channelPanel, "grow,push,wrap 2px");
}
else
{
add(channelPanel, "grow,push");
}
}
}
}
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
{
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
private SpectrumPanel mSpectrumPanel;
public PrimarySpectrumPanel(SettingsManager settingsManager, double sampleRate)
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
mSpectrumPanel = new SpectrumPanel(settingsManager);
add(mSpectrumPanel);
mDFTProcessor.addConverter(mComplexDecibelConverter);
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
mComplexDecibelConverter.addListener(mSpectrumPanel);
}
public void setDFTSize(DFTSize dftSize)
{
mDFTProcessor.setDFTSize(dftSize);
}
@Override
public void receive(ComplexBuffer complexBuffer)
{
mDFTProcessor.receive(complexBuffer);
}
@Override
public void process(SourceEvent event) throws SourceException
{
mLog.debug("Source Event! Add handler support for this to channelizer viewer");
}
}
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
{
private TunerChannelSource mSource;
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
private SpectrumPanel mSpectrumPanel;
private long mFrequency;
public ChannelPanel(SettingsManager settingsManager, double sampleRate, long frequency, int bandwidth, boolean enableLogging)
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
mSpectrumPanel = new SpectrumPanel(settingsManager);
add(mSpectrumPanel);
mDFTProcessor.addConverter(mComplexDecibelConverter);
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
mComplexDecibelConverter.addListener(mSpectrumPanel);
mFrequency = frequency;
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getSourceManager().getSource(tunerChannel);
if(mSource != null)
{
if(enableLogging)
{
mSource.setListener(new Listener<ComplexBuffer>()
{
@Override
public void receive(ComplexBuffer complexBuffer)
{
mLog.debug("Sending samples to DFT processor: " + complexBuffer.getSamples().length);
mDFTProcessor.receive(complexBuffer);
}
});
}
else
{
mSource.setListener(mDFTProcessor);
}
mSource.start();
}
else
{
mLog.error("Couldn't get a source from the tuner for frequency: " + frequency);
}
}
public TunerChannelSource getSource()
{
return mSource;
}
public void setDFTSize(DFTSize dftSize)
{
mDFTProcessor.setDFTSize(dftSize);
}
@Override
public void receive(ComplexBuffer complexBuffer)
{
mLog.debug("Got a buffer for the channel dft processor");
mDFTProcessor.receive(complexBuffer);
}
@Override
public void process(SourceEvent event) throws SourceException
{
mLog.debug("Source Event! Add handler support for this to channelizer viewer");
}
}
public static void main(String[] args)
{
int channelsPerRow = 4;
final ChannelizerViewer frame = new ChannelizerViewer(channelsPerRow);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
frame.setVisible(true);
}
});
}
}

View File

@@ -0,0 +1,171 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannelizer
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizer.class);
private RealFIRFilter[] mInphaseFilters;
private RealFIRFilter[] mQuadratureFilters;
private int mFilterPointer = 0;
private float[] mFilteredSamples;
private FloatFFT_1D mFFT;
/**
* Maximally Decimated Polyphase Filter Bank (MDPFB) channelizer that divides the input frequency band into
* equal bandwidth channels.
*
* @param coefficients of a prototype low-pass filter designed for the inbound sample rate with a cutoff frequency
* equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more
* filter outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed
* as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency
*
* @param channelCount - number of channels to output.
* @param channelSampleRate - channel sample rate (after decimation)
*/
public ComplexPolyphaseChannelizer(float[] coefficients, int channelCount, int channelSampleRate)
{
super(channelCount, channelSampleRate);
initFilters(coefficients);
}
@Override
public void receive(ComplexBuffer complexBuffer)
{
float[] samples = complexBuffer.getSamples();
for(int x = 0; x < samples.length; x += 2)
{
filter(samples[x], samples[x + 1]);
}
}
protected void filter(float inphase, float quadrature)
{
int index = (getChannelCount() - mFilterPointer - 1) * 2;
mFilteredSamples[index] = mInphaseFilters[mFilterPointer].filter(inphase);
mFilteredSamples[index + 1] = mQuadratureFilters[mFilterPointer].filter(quadrature);
if(mFilterPointer == 0)
{
calculate();
mFilterPointer = getChannelCount() - 1;
}
else
{
mFilterPointer--;
}
}
private void calculate()
{
float[] samples = new float[getChannelCount() * 2];
System.arraycopy(mFilteredSamples, 0, samples, 0, mFilteredSamples.length);
//FFT is executed in-place where the output overwrites the input
mFFT.complexForward(samples);
dispatch(samples);
}
/**
* Distributes the filter taps to each of the polyphase filters
*
* @param taps
*/
private void initFilters(float[] taps)
{
int tapCount = (int)Math.ceil((double)taps.length / (double)getChannelCount());
float[][] coefficients = new float[getChannelCount()][tapCount];
for(int tap = 0; tap < tapCount; tap++)
{
for(int filter = 0; filter < getChannelCount(); filter++)
{
int index = tap * getChannelCount() + filter;
if(index < taps.length)
{
coefficients[filter][tap] = taps[index];
}
}
}
mInphaseFilters = new RealFIRFilter[getChannelCount()];
mQuadratureFilters = new RealFIRFilter[getChannelCount()];
for(int filter = 0; filter < getChannelCount(); filter++)
{
mInphaseFilters[filter] = new RealFIRFilter(coefficients[filter], 1.0f);
mQuadratureFilters[filter] = new RealFIRFilter(coefficients[filter], 1.0f);
}
mFilteredSamples = new float[getChannelCount() * 2];
mFFT = new FloatFFT_1D(getChannelCount());
mFilterPointer = getChannelCount() -1;
}
public static void main(String[] args)
{
mLog.debug("Starting ...");
int symbolRate = 4800;
int samplesPerSymbol = 2;
int channels = 800;
int channelBandwidth = 12500;
int sampleRate = channels * channelBandwidth;
float[] taps = FilterFactory.getLowPass( 10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7 );
int channelCenter = (int)(12500 * 1.5);
StringBuilder sb = new StringBuilder();
sb.append("\nPolyphase TunerChannelizer\n");
sb.append("Sample Rate:" + sampleRate + " Channels:" + channels + " Channel Rate:" + channelBandwidth + "\n");
sb.append("Tap Count:" + taps.length + "\n");
sb.append("Channel:" + channelCenter);
mLog.debug(sb.toString());
ComplexPolyphaseChannelizer channelizer = new ComplexPolyphaseChannelizer(taps, 8, channelBandwidth);
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
for(int x = 0; x < 2000; x++)
{
channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature());
oscillator.rotate();
}
mLog.debug("Finished!");
}
}

View File

@@ -0,0 +1,349 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChannelizer
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 12500;
private float[][] mSamples;
private float[][] mCoefficients;
private int[][] mSampleIndexMap;
private int mColumnPointer;
private int mRowPointer;
private int mBlockSize;
private FloatFFT_1D mFFT;
/**
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer that divides the input frequency band into
* equal bandwidth channels that are each oversampled by 2x for output.
*
* @param taps of a low-pass filter designed for the inbound sample rate with a cutoff frequency
* equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more
* filter outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed
* as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency
*
* @param sampleRate of the incoming sample stream
* @param channelCount - number of filters/channels to output. Since this filter bank oversamples each filter
* output, this number must be even (divisible by the 2x oversample rate).
*/
public ComplexPolyphaseChannelizerM2(float[] taps, int sampleRate, int channelCount)
{
super(sampleRate, channelCount);
if(channelCount % 2 != 0)
{
throw new IllegalArgumentException("Channel count must be an even multiple of the oversample rate (2)");
}
init(taps);
}
/**
* Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer that divides the input frequency band into
* equal bandwidth channels that are each oversampled by 2x for output.
*
* Note: this constructor uses a default minimum channel bandwidth of 12.5 kHz. The number of channels is calculate
* from the sample rate and this default minimum channel bandwidth. The resulting per-channel bandwidth will be
* between 12.5 and 25.0 kHz with the output per-channel sample rate of 2x the bandwidth.
*
* @param sampleRate to be channelized.
* @param tapsPerFilter to use when designing the filter
*/
public ComplexPolyphaseChannelizerM2(double sampleRate, int tapsPerFilter) throws FilterDesignException
{
super(sampleRate, (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH));
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
tapsPerFilter, WindowType.BLACKMAN_HARRIS_7, true);
init(filterTaps);
}
/**
* Updates this channelizer to use the new sample rate. This method creates a new filter suitable for the
* sample rate and reinitializes all internal data structures to prepare for processing the new sample rate.
* @param sampleRate in hertz
*/
@Override
public void setSampleRate(double sampleRate)
{
try
{
super.setSampleRate(sampleRate);
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
15, WindowType.BLACKMAN_HARRIS_7, true);
init(filterTaps);
}
catch(FilterDesignException fde)
{
throw new IllegalArgumentException("Cannot create a channelizer filter for the specified sample rate [" +
sampleRate + "]");
}
}
/**
* Receives the complex sample buffer and processes the results through the channelizer.
*/
@Override
public void receive(ComplexBuffer complexBuffer)
{
float[] samples = complexBuffer.getSamples();
int samplesPointer = 0;
while(samplesPointer < samples.length)
{
int lengthToCopy = mBlockSize - (mRowPointer % mBlockSize);
if((samplesPointer + lengthToCopy) > samples.length)
{
lengthToCopy = samples.length - samplesPointer;
}
System.arraycopy(samples, samplesPointer, mSamples[mColumnPointer], mRowPointer, lengthToCopy);
samplesPointer += lengthToCopy;
mRowPointer += lengthToCopy;
if(mRowPointer % mBlockSize == 0)
{
process();
}
}
}
/**
* Processes the sample buffer for each new block of sample data that is loaded and distributes the results to any
* registered channel listeners.
*/
private void process()
{
int sampleRowCount = getChannelCount() * 2;
float[] processed = new float[sampleRowCount];
//Top of block processing
if(mRowPointer >= sampleRowCount)
{
int[] sampleIndexMap = mSampleIndexMap[mColumnPointer];
for(int channel = 0; channel < getChannelCount(); channel++)
{
int outputIndex = 2 * (getChannelCount() - channel - 1);
int sampleIndex = 2 * channel;
float[] filter = mCoefficients[channel];
processed[outputIndex] = filter((sampleIndex), mCoefficients[channel], sampleIndexMap);
processed[outputIndex + 1] = filter((sampleIndex + 1), mCoefficients[channel], sampleIndexMap);
}
mRowPointer = 0;
mColumnPointer--;
if(mColumnPointer < 0)
{
mColumnPointer = mSamples.length - 1;
}
}
//Mid-point processing
else
{
int[] sampleIndexMap = mSampleIndexMap[mColumnPointer];
int half = getChannelCount() / 2;
for(int channel = 0; channel < half; channel++)
{
int outputIndex = 2 * (getChannelCount() - channel - 1);
int sampleIndex = 2 * channel;
int filterIndex = channel + half;
processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap);
processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap);
}
//The second half of the sample buffer was loaded using the previous column pointer - adjust for that now
sampleIndexMap = mSampleIndexMap[(mColumnPointer == (mSamples.length - 1) ? 0 : mColumnPointer + 1)];
for(int channel = half; channel < getChannelCount(); channel++)
{
int outputIndex = 2 * (getChannelCount() - channel - 1);
int sampleIndex = 2 * channel;
int filterIndex = channel - half;
processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap);
processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap);
}
}
//Rotate each of the channels to the correct phase using the IFFT
mFFT.complexInverse(processed, true);
dispatch(processed);
}
/**
* Calculates the filtered output for polyphase filter arm.
*
* @param sampleRow index pointing to a row in the mSamples array to filter
* @param coefficients to use in filtering the samples
* @param sampleIndexMap to translate the column index offset based on the current sample column pointer
* @return filtered sample for the row
*/
private float filter(int sampleRow, float[] coefficients, int[] sampleIndexMap)
{
float accumulator = 0.0f;
for(int column = 0; column < mSamples.length; column++)
{
accumulator += mSamples[sampleIndexMap[column]][sampleRow] * coefficients[column];
}
return accumulator;
}
/**
* Initializes the channelizer structures. Distributes the prototype filter coefficients to each of the polyphase
* filters, sets up the sample buffer array and pointers and initializes the FFT.
*
* These structures resemble those described by Fred Harris in Multirate Signal Processing for Communications
* Systems, p230-233. However, instead of swapping the input sample buffers and swapping the output sample array
* on every other processing block, we simply swap the filter coefficient banks and leave the input sample buffer
* and output array indexes aligned. However, the entire structure (sample buffer, coefficients and output array)
* is inverted (row zero index is M-1 and M-1 is zero index) to facilitate easy loading of data into the input
* sample buffer array using Java's efficient System.arrayCopy() utility.
*
* Note: the filter coefficients array is organized (row,column) for ease of accessing each filter arm row during
* convolution. The samples array is organized (column,row) for ease/efficiency in loading each row of data. Also,
* the input sample array implements a circular buffer where the mCoefficientsMap provides a lookup to each sample
* index based on the current value of the mColumnPointer.
*
* @param coefficients of the prototype filter for this channelizer
*/
private void init(float[] coefficients)
{
int columnCount = (int)Math.ceil((double)coefficients.length / (double)getChannelCount());
int filterRowCount = getChannelCount();
int sampleRowCount = getChannelCount() * 2;
//Sample and filter coefficient arrays are specifically setup as column/row and row/column
mSamples = new float[columnCount][sampleRowCount];
mCoefficients = new float[filterRowCount][columnCount];
mColumnPointer = columnCount - 1;
mBlockSize = getChannelCount();
mRowPointer = mBlockSize;
//Setup the FFT
mFFT = new FloatFFT_1D(getChannelCount());
//Setup the polyphase filters
//Setup the index map for the sample array circular buffer
initIndexMap(columnCount);
int coefficientIndex = 0;
//Load filter columns left-2-right (samples are loaded in reverse column order, right-2-left, for easy convolution)
for(int columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
//Load filter rows bottom to top, opposite of sample row loading and sample output ordering
for(int rowIndex = filterRowCount - 1; rowIndex >= 0; rowIndex--)
{
mCoefficients[rowIndex][columnIndex] = coefficients[coefficientIndex++];
}
}
}
/**
* Generates a circular buffer index map to support lookup of the translated index based on the current column
* pointer and the desired sample index.
*/
private void initIndexMap(int size)
{
mSampleIndexMap = new int[size][size];
for(int row = 0; row < size; row++)
{
for(int column = 0;column < size;column++)
{
int offset = row + column;
mSampleIndexMap[row][column] = ((offset < size) ? offset : offset - size);
}
}
}
public static void main(String[] args)
{
mLog.debug("Starting ...");
int channelCount = 4;
int channelBandwidth = 12500;
int sampleRate = channelCount * channelBandwidth;
int tapsPerFilter = 14;
int channelCenter = (int)(12500 * 1.5);
float[] taps = new float[16];
for(int x = 0; x < taps.length; x++)
{
taps[x] = x;
}
float[] samples = new float[32];
for(int x = 0; x < samples.length; x++)
{
samples[x] = x;
}
try
{
// ComplexPolyphaseChannelizerM2 channelizer = new ComplexPolyphaseChannelizerM2(sampleRate, tapsPerFilter);
ComplexPolyphaseChannelizerM2 channelizer = new ComplexPolyphaseChannelizerM2(taps,sampleRate, channelCount);
// Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
//
// ComplexBuffer complexBuffer = oscillator.generateComplexBuffer(1000);
ComplexBuffer complexBuffer = new ComplexBuffer(samples);
channelizer.receive(complexBuffer);
}
catch(Exception e)
{
mLog.error("Error", e);
}
mLog.debug("Finished!");
}
}

View File

@@ -0,0 +1,363 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.channelizer.output.OneChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.IComplexBufferProvider;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class PolyphaseChannelManager implements Listener<SourceEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final int CHANNEL_BANDWIDTH = 12500;
private static final double CHANNEL_OVERSAMPLING = 2.0;
private static final int CHANNEL_SAMPLE_RATE = (int)(CHANNEL_BANDWIDTH * CHANNEL_OVERSAMPLING);
private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 17;
private IComplexBufferProvider mComplexBufferProvider;
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
private ChannelCalculator mChannelCalculator;
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener();
private ScheduledBufferProcessor mBufferProcessor;
/**
* Polyphase Channel Manager is a DDC channel manager and complex buffer queue/processor for a tuner. This class
* provides DDC polyphase channel sources and wraps a polyphase channelizer processing sample buffers produced by
* the tuner and distributing channelized sample buffers to each allocated DDC polyphase channel source. This
* class is responsible for monitoring the tuner for changes in center frequency and/or sample rate and updating
* active DDC polyphase channel sources accordingly. This class also monitors source event requests and
* notifications received from active DDC polyphase channel sources to adjust sample streams as required.
*
* Note: add this channel manager as a source event listener to the complex buffer provider to ensure this manager
* adapts to changes in source frequency and sample rate.
*
* @param complexBufferProvider that supports register/deregister of this channel manager
* @param frequency of the provided complex buffer samples
* @param sampleRate of the provided complex buffer samples
*/
public PolyphaseChannelManager(IComplexBufferProvider complexBufferProvider, long frequency, int sampleRate)
{
if(complexBufferProvider == null)
{
throw new IllegalArgumentException("Complex buffer provider argument cannot be null");
}
mComplexBufferProvider = complexBufferProvider;
//Ensure that sample rate is a multiple of channel sample rate since polyphase channelizer is M2
if(sampleRate % (CHANNEL_SAMPLE_RATE) != 0)
{
throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be an even multiple of " +
CHANNEL_SAMPLE_RATE + " Hz");
}
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
mBufferProcessor = new ScheduledBufferProcessor(500, 100, 50, 50);
updateChannelizer(sampleRate, null);
}
/**
* Provides a Digital Drop Channel (DDC) for the specified tuner channel or returns null if the channel can't be
* sourced due to the current center frequency and/or sample rate.
* @param tunerChannel specifying center frequency and bandwidth.
* @return source or null.
*/
public Source getChannel(TunerChannel tunerChannel)
{
PolyphaseChannelSource channelSource = null;
try
{
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
if(outputProcessor != null)
{
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
CHANNEL_SAMPLE_RATE);
mChannelSources.add(channelSource);
}
}
catch(IllegalArgumentException iae)
{
mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" +
mChannelCalculator.getCenterFrequency() + "] and sample rate [" +
(mChannelCalculator.getChannelCount() * mChannelCalculator.getChannelBandwidth()) + "]");
}
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.
* @param indexes to target by the output processor
* @return output processor compatible with the number of indexes to monitor
*/
private IPolyphaseChannelOutputProcessor getOutputProcessor(List<Integer> indexes)
{
switch(indexes.size())
{
case 1:
return new OneChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
case 2:
return new TwoChannelOutputProcessor(CHANNEL_SAMPLE_RATE, indexes);
default:
//TODO: create output processor for greater than 2 input channels
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
return null;
}
}
/**
* Starts/adds the channel source to receive channelized sample buffers, registering with the tuner to receive
* sample buffers when this is the first channel.
*
* @param channelSource to start
*/
private void startChannelSource(PolyphaseChannelSource channelSource)
{
synchronized(mBufferProcessor)
{
mPolyphaseChannelizer.addChannel(channelSource);
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
{
mComplexBufferProvider.addComplexBufferListener(mBufferProcessor);
mBufferProcessor.start();
}
}
}
/**
* Stops/removes the channel source from receiving channelized sample buffers and deregisters from the tuner
* when this is the last channel being sourced.
*
* @param channelSource to stop
*/
private void stopChannelSource(PolyphaseChannelSource channelSource)
{
synchronized(mBufferProcessor)
{
mChannelSources.remove(channelSource);
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
{
mComplexBufferProvider.removeComplexBufferListener(mBufferProcessor);
mBufferProcessor.stop();
}
}
}
/**
* Implements the Listener<SourceEvent> interface for receiving notifications of frequency and sample rate
* changes from the tuner.
*/
@Override
public void receive(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
//Update the index calculator and each of the output processors to update each channel frequency
mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
updateOutputProcessors(sourceEvent);
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
//Update the channelizer (and the output processors) for the new sample rate
updateChannelizer(sourceEvent.getValue().intValue(), sourceEvent);
break;
}
}
/**
* Creates or updates the channelizer to process the incoming sample rate and updates any channel processors.
*
* @param sampleRate to use for the channelizer
* @param sourceEvent containing a notification of sample rate change (optional - can be null). This source
* event will be passed to all output processors to ensure the change is fully promulgated before restarting the
* buffer processor if it was already running.
*/
private void updateChannelizer(int sampleRate, SourceEvent sourceEvent)
{
mChannelCalculator.setSampleRate(sampleRate);
//Lock on the buffer processor to block any channel create/start/stop requests while we configure
synchronized(mBufferProcessor)
{
//Get current processor state so we can restart it after we update the channelizer
boolean bufferProcessorWasRunning = mBufferProcessor.isRunning();
//Stop will be ignored if it is not currently running
mBufferProcessor.stop();
try
{
if(mPolyphaseChannelizer == null)
{
mPolyphaseChannelizer = new ComplexPolyphaseChannelizerM2(sampleRate,
POLYPHASE_FILTER_TAPS_PER_CHANNEL);
mBufferProcessor.setListener(mPolyphaseChannelizer);
}
else
{
mPolyphaseChannelizer.setSampleRate(sampleRate);
}
}
catch(IllegalArgumentException iae)
{
mLog.error("Could not create polyphase channelizer for sample rate [" + sampleRate + "]", iae);
}
catch(FilterDesignException fde)
{
mLog.error("Could not create filter for polyphase channelizer for sample rate [" + sampleRate + "]", fde);
}
if(sourceEvent != null && sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
{
updateOutputProcessors(sourceEvent);
}
if(bufferProcessorWasRunning)
{
mBufferProcessor.start();
}
}
}
/**
* Updates each of the output processors for any changes in the tuner's center frequency or sample rate, which
* would cause the output processors to change the polyphase channelizer results channel(s) that the processor is
* consuming
*
* @param sourceEvent that requires an update to the output processors
*/
private void updateOutputProcessors(SourceEvent sourceEvent)
{
for(PolyphaseChannelSource channelSource: mChannelSources)
{
updateOutputProcessor(channelSource);
//Notify the channel source that the frequency or sample rate has changed so that it can reset
//any frequency correction value.
if(sourceEvent != null)
{
try
{
channelSource.process(sourceEvent);
}
catch(SourceException se)
{
mLog.error("Error while notifying polyphase channel source of a source event", se);
}
}
}
}
/**
* Updates the polyphase channel source's output processor due to a change in the center frequency or sample
* rate for the source providing sample buffers to the polyphase channelizer, or whenever the DDC channel's
* center tuned frequency changes.
*
* @param channelSource that requires an update to its output processor
*/
private void updateOutputProcessor(PolyphaseChannelSource channelSource)
{
try
{
List<Integer> indexes = mChannelCalculator.getChannelIndexes(channelSource.getTunerChannel());
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(indexes);
//If the indexes size is the same then update the current processor, otherwise create a new one
if(channelSource.getPolyphaseChannelOutputProcessor().getInputChannelCount() == indexes.size())
{
channelSource.getPolyphaseChannelOutputProcessor().setPolyphaseChannelIndices(indexes);
channelSource.setFrequency(centerFrequency);
}
else
{
channelSource.setPolyphaseChannelOutputProcessor(getOutputProcessor(indexes), centerFrequency);
}
}
catch(IllegalArgumentException iae)
{
mLog.error("Error updating polyphase channel source - can't determine output channel indexes for " +
"updated tuner center frequency and sample rate. Stopping channel source", iae);
stopChannelSource(channelSource);
}
}
/**
* Internal class for handling requests for start/stop sample stream from polyphase channel sources
*/
private class ChannelSourceEventListener implements Listener<SourceEvent>
{
@Override
public void receive(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
case REQUEST_START_SAMPLE_STREAM:
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
{
startChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
}
else
{
mLog.error("Request to start sample stream for unrecognized source: " +
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
}
break;
case REQUEST_STOP_SAMPLE_STREAM:
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
{
stopChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
}
else
{
mLog.error("Request to stop sample stream for unrecognized source: " +
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
}
break;
default:
mLog.error("Received unrecognized source event from polyphase channel source [" +
sourceEvent.getEvent() + "]");
}
}
}
}

View File

@@ -0,0 +1,222 @@
/*******************************************************************************
* *********************************************************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
* *********************************************************************************************************************
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.ComplexToComplexBufferAssembler;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PolyphaseChannelSource extends TunerChannelSource
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private ComplexToComplexBufferAssembler mComplexBufferAssembler = new ComplexToComplexBufferAssembler(2500);
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private double mChannelSampleRate;
private long mCenterFrequency;
private long mFrequencyCorrection;
/**
* Polyphase channelizer tuner channel source implementation. Adapts the channel array output samples from the
* polyphase channelizer into a single channel, or a channel synthesized from two adjacent channels that is
* frequency translated and decimated to a single channel.
*
* @param tunerChannel describing the desired channel frequency and bandwidth/minimum sample rate
* @param outputProcessor - to process polyphase channelizer channel results into a channel stream
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
* @param channelSampleRate for the downstream sample output
*/
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate)
{
super(producerSourceEventListener, tunerChannel);
mPolyphaseChannelOutputProcessor = outputProcessor;
mChannelSampleRate = channelSampleRate;
}
/**
* Registers the listener to receive complex sample buffers from this channel source
*/
@Override
public void setListener(final Listener<ComplexBuffer> listener)
{
mComplexBufferAssembler.setListener(listener);
}
/**
* Removes the listener from receiving complex sample buffers from this channel source
*/
@Override
public void removeListener(Listener<ComplexBuffer> listener)
{
mComplexBufferAssembler.setListener(null);
}
/**
* Channel output processor used by this channel source to convert polyphase channel results into a specific
* channel complex buffer output stream.
*/
public IPolyphaseChannelOutputProcessor getPolyphaseChannelOutputProcessor()
{
return mPolyphaseChannelOutputProcessor;
}
/**
* Sets/updates the output processor for this channel source, replacing the existing output processor.
*
* @param outputProcessor to use
* @param frequency center for the channels processed by the output processor
*/
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)
{
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
mPolyphaseChannelOutputProcessor = outputProcessor;
//Remove the buffer overflow listener and clear any previous buffer overflow state
existingProcessor.setSourceOverflowListener(null);
broadcastOverflowState(false);
//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(mComplexBufferAssembler);
}
//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.
mCenterFrequency = frequency;
mFrequencyCorrection = 0;
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mFrequencyCorrection));
}
}
/**
* Primary method for receiving channel results output from a polyphase channelizer. The results array will be
* queued for processing to extract the target channel samples, process them for frequency correction and/or
* channel aggregation, and dispatch the results to the downstream sample listener/consumer.
*
* @param channelResultsBuffer containing the polyphase channelizer output for a single complex sample period.
*/
public void receiveChannelResults(float[] channelResultsBuffer)
{
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
}
/**
* Downstream channel sample rate
*
* @return sample rate in Hertz
*/
@Override
public double getSampleRate()
{
return mChannelSampleRate;
}
/**
* Sets the downstream channel sample rate
* @param sampleRate in hertz
*/
@Override
protected void setSampleRate(double sampleRate)
{
mChannelSampleRate = sampleRate;
}
@Override
public void dispose()
{
mLog.info("Polyphase channel source received request to dispose");
}
@Override
public long getFrequencyCorrection()
{
return mFrequencyCorrection;
}
/**
* Adjusts the frequency correction value that is being applied to the channelized output stream by the
* polyphase channel output processor.
*
* @param value to apply for frequency correction in hertz
*/
protected void setFrequencyCorrection(long value)
{
mFrequencyCorrection = value;
updateFrequencyOffset();
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mFrequencyCorrection));
}
/**
* Sets the center frequency for the incoming sample stream channel results and resets frequency correction to zero.
* @param frequency in hertz
*/
@Override
protected void setFrequency(long frequency)
{
mCenterFrequency = frequency;
setFrequencyCorrection(0);
}
@Override
protected void processSamples()
{
//Lock on the output processor so that it can't be changed out in the middle of processing
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.processChannelResults(mComplexBufferAssembler);
}
}
/**
* Calculates the frequency offset required to mix the incoming signal to the desired output baseband
*/
private long getFrequencyOffset()
{
return getTunerChannel().getFrequency() - mCenterFrequency + mFrequencyCorrection;
}
/**
* Updates the frequency offset being applied by the output processor. Calculates the offset using the center
* frequency of the incoming polyphase channel results and the desired output channel frequency adjusted by any
* requested frequency correction value.
*/
private void updateFrequencyOffset()
{
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
}
}

View File

@@ -0,0 +1,168 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class ScheduledBufferProcessor<E> implements Listener<E>
{
private final static Logger mLog = LoggerFactory.getLogger(ScheduledBufferProcessor.class);
private OverflowableTransferQueue<E> mQueue;
private Listener<E> mListener;
private ScheduledFuture<?> mScheduledFuture;
private long mDistributionInterval;
private int mMaxBuffersPerInterval;
private AtomicBoolean mRunning = new AtomicBoolean();
/**
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
* for periodically distributing internally queued elements to the registered listener. This processor provides
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
* processing workload.
*
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
*
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
* @param distributionInterval in milliseconds specifying how often the internal scheduled thread pool buffer
* distributor should run
* @param maxBuffersPerInterval maximum number of buffers to pull from the internal queue during each distribution
* interval
*/
public ScheduledBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
{
mQueue = new OverflowableTransferQueue<>(maximumSize, resetThreshold);
mQueue.setOverflowListener(new IOverflowListener()
{
@Override
public void sourceOverflow(boolean overflow)
{
mLog.error("Overflow state changed - overflow:" + overflow);
}
});
mDistributionInterval = distributionInterval;
mMaxBuffersPerInterval = maxBuffersPerInterval;
}
/**
* Sets the listener to receive notifications of buffer overflow and/or reset. Note: this method can also be
* invoked with a null argument to clear the previously registered overflow listener.
*
* @param listener to receive overflow/reset notifications.
*/
public void setOverflowListener(IOverflowListener listener)
{
mQueue.setOverflowListener(listener);
}
/**
* Sets or changes the listener to receive buffers from this processor.
* @param listener to receive buffers
*/
public void setListener(Listener<E> listener)
{
mListener = listener;
}
/**
* Primary input method for adding buffers to this processor. Note: incoming buffers will be ignored if this
* processor is in a stopped state. You must invoke start() to allow incoming buffers and initiate buffer
* processing.
*
* @param e to enqueue for distribution to a registered listener
*/
@Override
public void receive(E e)
{
mQueue.offer(e);
}
/**
* Starts this buffer processor and allows queuing of incoming buffers.
*/
public void start()
{
if(mRunning.compareAndSet(false, true))
{
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0, mDistributionInterval, TimeUnit.MILLISECONDS);
}
}
/**
* Stops this buffer processor, stops queuing of incoming buffers and clears the internal buffer queue.
*/
public void stop()
{
if(mRunning.compareAndSet(true, false))
{
if(mScheduledFuture != null)
{
mScheduledFuture.cancel(true);
mScheduledFuture = null;
}
mQueue.clear();
}
}
/**
* Indicates if this processor is currently running
*/
public boolean isRunning()
{
return mRunning.get();
}
/**
* Processor to service the buffer queue and distribute the buffers to the registered listener
*/
class Processor implements Runnable
{
private List<E> mBuffers = new ArrayList<>();
@Override
public void run()
{
mQueue.drainTo(mBuffers, mMaxBuffersPerInterval);
if(mListener != null)
{
for(E buffer: mBuffers)
{
mListener.receive(buffer);
}
}
mBuffers.clear();
}
}
}

View File

@@ -0,0 +1,170 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.Source;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
private OverflowableTransferQueue<float[]> mChannelResultsQueue;
private List<float[]> mChannelResultsToProcess = new ArrayList<>();
private int mMaxResultsToProcess;
private int mInputChannelCount;
//TODO: swap this out and use the LowPhaseNoiseOscillator
private Oscillator mFrequencyCorrectionMixer;
private boolean mFrequencyCorrectionEnabled;
/**
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
* oscillator support to apply frequency correction to the channel sample stream as requested by sample consumer.
*
* @param inputChannelCount is the number of input channels for this output processor
* @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)
{
mInputChannelCount = inputChannelCount;
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
mMaxResultsToProcess = (int)(sampleRate / 10) * 2; //process at 100 millis interval, twice the expected inflow rate
mChannelResultsQueue = new OverflowableTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
}
/**
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
* @param frequencyOffset in hertz
*/
@Override
public void setFrequencyOffset(long frequencyOffset)
{
mFrequencyCorrectionMixer.setFrequency(frequencyOffset);
mFrequencyCorrectionEnabled = (frequencyOffset != 0);
}
@Override
public void receiveChannelResults(float[] channelResults)
{
mChannelResultsQueue.offer(channelResults);
}
/**
* Processes all enqueued polyphase channelizer results until the internal queue is empty
* @param listener to receive the processed channel results
*/
@Override
public void processChannelResults(ComplexSampleListener listener)
{
try
{
int count = mChannelResultsQueue.drainTo(mChannelResultsToProcess, mMaxResultsToProcess);
if(count > 0)
{
process(mChannelResultsToProcess, listener);
}
}
catch(Throwable throwable)
{
mLog.error("Error while processing polyphase channel samples", throwable);
}
mChannelResultsToProcess.clear();
}
/**
* Sub-class implementation to process one polyphase channelizer result array.
* @param channelResults to process
* @param listener to receive the channel complex sample
*/
public abstract void process(List<float[]> channelResults, ComplexSampleListener listener);
/**
* Sets the overflow listener to monitor the internal channelizer channel results queue overflow state
*/
public void setSourceOverflowListener(Source source)
{
mChannelResultsQueue.setSourceOverflowListener(source);
}
/**
* Removes the overflow listener from monitoring the internal channelizer channel results queue overflow state
*/
public void removeOverflowListener(IOverflowListener listener)
{
mChannelResultsQueue.setOverflowListener(null);
}
@Override
public int getInputChannelCount()
{
return mInputChannelCount;
}
/**
* Provides frequency correction of the inphase component or simply returns the inphase argument value when no
* frequency correction value has been specified.
*
* @param inphase value of the sample to translate/correct
* @param quadrature value of the sample to translate/correct
* @return corrected inphase value
*/
protected float getFrequencyCorrectedInphase(float inphase, float quadrature)
{
return Complex.multiplyInphase(inphase, quadrature, mFrequencyCorrectionMixer.inphase(),
mFrequencyCorrectionMixer.quadrature());
}
/**
* Provides frequency correction of the quadrature component or simply returns the quadrature argument value when no
* frequency correction value has been applied,
*
* @param inphase value of the sample to translate/correct
* @param quadrature value of the sample to translate/correct
* @return translated inphase value
*/
protected float getFrequencyCorrectedQuadrature(float inphase, float quadrature)
{
return Complex.multiplyQuadrature(inphase, quadrature, mFrequencyCorrectionMixer.inphase(),
mFrequencyCorrectionMixer.quadrature());
}
/**
* Indicates if this channel has a frequency correction offset
*/
public boolean hasFrequencyCorrection()
{
return mFrequencyCorrectionEnabled;
}
}

View File

@@ -0,0 +1,66 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import io.github.dsheirer.source.Source;
import java.util.List;
public interface IPolyphaseChannelOutputProcessor
{
/**
* Receive and enqueue output results from the polyphase analysis channelizer
* @param channelResults to enqueue
*/
void receiveChannelResults(float[] channelResults);
/**
* Process the channel output channel results queue and deliver the output to the listener
*
* @param listener to receive the processed channel results
*/
void processChannelResults(ComplexSampleListener listener);
/**
* Sets the desired frequency offset from center. The samples will be mixed with an oscillator set to this offset
* frequency to produce an output where the desired signal is centered in the passband.
*
* @param frequency in hertz
*/
void setFrequencyOffset(long frequency);
/**
* Indicates the number of input channels processed by this output processor
* @return
*/
int getInputChannelCount();
/**
* Updates the input polyphase channel index(es) used by this output processor
* @param indexes
*/
void setPolyphaseChannelIndices(List<Integer> indexes);
/**
* Sets the listener to receive buffer overflow notifications
* @param source to receive overflow notifications
*/
void setSourceOverflowListener(Source source);
}

View File

@@ -0,0 +1,96 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class OneChannelOutputProcessor extends ChannelOutputProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(OneChannelOutputProcessor.class);
private int mChannelOffset;
/**
* Processor to extract a single channel from a polyphase channelizer and produce an output I/Q complex sample
* from each polyphase channelizer output results array.
*
* @param sampleRate of the output sample stream.
* @param channelIndexes containing a single channel index.
*/
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes)
{
super(1, sampleRate);
setPolyphaseChannelIndices(channelIndexes);
mLog.debug("Sample Rate:" + sampleRate + " PolyPhase Channel Indexes:" + channelIndexes);
}
/**
* Updates this processor to extract a single, specified channel index.
* @param indexes containing a single channel index value.
* @throws IllegalArgumentException if the list of indexes does not contain a single channel index.
*/
public void setPolyphaseChannelIndices(List<Integer> indexes)
{
if(indexes.size() != 1)
{
throw new IllegalArgumentException("Single channel output processor requires a single index to " +
"process - provided indexes " + indexes.toString());
}
//Set the channelized output results offset to twice the channel index to account for each channel having
//an I/Q pair
mChannelOffset = indexes.get(0) * 2;
}
/**
* Extract the channel from the channel results array, apply frequency translation, and deliver the
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
*
* @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param listener to receive the extracted, frequency-translated channel results
*/
@Override
public void process(List<float[]> channelResults, ComplexSampleListener listener)
{
for(float[] channels: channelResults)
{
if(channels.length < mChannelOffset + 1)
{
throw new IllegalArgumentException("Polyphase channelizer output channels array is not large enough to " +
"cover this single channel output processor with channel offset [" + mChannelOffset + "]");
}
float i = channels[mChannelOffset];
float q = channels[mChannelOffset + 1];
if(hasFrequencyCorrection())
{
listener.receive(getFrequencyCorrectedInphase(i, q), getFrequencyCorrectedQuadrature(i, q));
}
else
{
listener.receive(i, q);
}
}
}
}

View File

@@ -0,0 +1,91 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private int mChannelOffset1;
private int mChannelOffset2;
/**
* Processor to extract two channels from a polyphase channelizer, synthesize/recombine the channels, apply
* frequency translation and frequency correction, down-sample by a factor of two and output an I/Q complex sample.
*
* @param sampleRate of the output sample stream.
* @param channelIndexes containing two channel indices.
*/
public TwoChannelOutputProcessor(int sampleRate, List<Integer> channelIndexes)
{
//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 * 2);
setPolyphaseChannelIndices(channelIndexes);
}
/**
* Updates this processor to extract the two specified channel indexes.
*
* @param indexes containing a single channel index value.
* @throws IllegalArgumentException if the list of indexes does not contain a single channel index.
*/
public void setPolyphaseChannelIndices(List<Integer> indexes)
{
if(indexes.size() != 2)
{
throw new IllegalArgumentException("Double channel output processor requires two indexes to " +
"process - provided indexes " + indexes.toString());
}
//Set the channelized output results offsets to twice the channel index to account for each channel having
//an I/Q pair
mChannelOffset1 = indexes.get(0) * 2;
mChannelOffset2 = indexes.get(1) * 2;
}
/**
* Extract the channel from the channel results array, apply frequency translation, and deliver the
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
*
* @param channelResults to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param listener to receive the extracted, frequency-translated channel results
*/
@Override
public void process(List<float[]> channelResults, ComplexSampleListener listener)
{
mLog.debug("I got channel results - doing nothing !!");
// if(channels.length < mChannelOffset + 1)
// {
// throw new IllegalArgumentException("Polyphase channelizer output channels array is not large enough to " +
// "cover this single channel output processor with channel offset [" + mChannelOffset + "]");
// }
//
// float i = channels[mChannelOffset];
// float q = channels[mChannelOffset + 1];
// listener.receive(getFrequencyCorrectedInphase(i, q), getFrequencyCorrectedQuadrature(i, q));
}
}

View File

@@ -0,0 +1,51 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.correction;
import io.github.dsheirer.sample.complex.ComplexBuffer;
public class IQCorrectionFilter
{
private float mRatio = 0.00001f;
private float mAverageInphase = 0.0f;
private float mAverageQuadrature = 0.0f;
public IQCorrectionFilter(float ratio)
{
mRatio = ratio;
}
public IQCorrectionFilter()
{
}
public void filter(ComplexBuffer buffer)
{
float[] samples = buffer.getSamples();
for(int x = 0; x < samples.length; x += 2)
{
mAverageInphase = mAverageInphase + (mRatio * (samples[x] - mAverageInphase));
samples[x] -= mAverageInphase;
mAverageQuadrature = mAverageQuadrature + (mRatio * (samples[x + 1] - mAverageQuadrature));
samples[x + 1] -= mAverageQuadrature;
}
}
}

View File

@@ -0,0 +1,37 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.fir.remez;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
public class PolyphaseChannelizerDesigner extends FIRFilterSpecification
{
/**
* Constructs a Polyphase TunerChannelizer Remez filter specification.
*
* Constructor is package private -- use the PolyphaseChannelizerFilterFactory to create a filter
*
* @param order of the filter
* @param gridDensity to use when calculating the coefficients
*/
PolyphaseChannelizerDesigner(int order, int gridDensity)
{
super(FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL, order, gridDensity);
}
}

View File

@@ -0,0 +1,97 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.fir.remez;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PolyphaseChannelizerFilterFactory
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelizerFilterFactory.class);
private static final double OBJECTIVE_BAND_EDGE_COEFFICIENT_AMPLITUDE = Math.sqrt(2.0) / 2.0; //.707xxx
public static float[] getFilter(int sampleRate, int channelBandwidth, double alpha)
{
FIRLinearPhaseFilterType type = FIRLinearPhaseFilterType.TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL;
int passBandStart = 0;
int gridDensity = 60;
int rolloffFrequency = (int)(alpha * (double)channelBandwidth);
int passBandStop = channelBandwidth - rolloffFrequency;
//Set the stop band to start at 40% into the adjacent channel, since we're oversampling by 2.
int stopBandStart = channelBandwidth + rolloffFrequency;
double passRipple = 0.01d;
double transitionRipple = 0.02d;
double stopRipple = 0.001d; //Approximately 90 dB of attenuation
int order = FIRFilterSpecification.estimateFilterOrder( sampleRate, passBandStop, stopBandStart,
passRipple, stopRipple );
// order = 1001;
// Ensure odd order since we're designing a Type 2 filter
if(order % 2 == 0)
{
order++;
}
mLog.info("Filter Order: " + order);
FIRFilterSpecification specification = new PolyphaseChannelizerDesigner(order, gridDensity);
FIRFilterSpecification.FrequencyBand passBand = new FIRFilterSpecification.FrequencyBand(sampleRate, passBandStart,
passBandStop, 1.0, passRipple, 1.0);
//Use the filter order as the weighting for the pass band edge frequency
// double weight = order;
FIRFilterSpecification.FrequencyBand transitionBand = new FIRFilterSpecification.FrequencyBand(sampleRate,
channelBandwidth, channelBandwidth, OBJECTIVE_BAND_EDGE_COEFFICIENT_AMPLITUDE, transitionRipple, 5.0);
FIRFilterSpecification.FrequencyBand stopBand = new FIRFilterSpecification.FrequencyBand(sampleRate,
stopBandStart, (int)(sampleRate / 2), 0.0, stopRipple);
specification.addFrequencyBand(passBand);
specification.addFrequencyBand(transitionBand);
specification.addFrequencyBand(stopBand);
double bandEdgeFrequency = Math.cos(Math.PI * (double)channelBandwidth / (double)(sampleRate / 2));
float[] filter = null;
try
{
RemezFIRFilterDesignerWithLagrange designer = new RemezFIRFilterDesignerWithLagrange(specification);
filter = designer.getImpulseResponse();
double bandEdgeAmplitude = designer.getFrequencyResponse(bandEdgeFrequency);
mLog.debug("Coefficient Amplitude at Band Edge is: " + bandEdgeAmplitude + " for frequency: " + passBandStop);
}
catch(Exception e)
{
mLog.error("Error designing filter", e);
}
return filter;
}
}

View File

@@ -211,15 +211,12 @@ public class RemezFIRFilterDesigner
for( int k = 0; k < mExtremalIndices.size() - 1; k++ )
{
double cosineDelta = cosineOfFrequency -
mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( k ) ];
double cosineDelta = cosineOfFrequency - mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( k ) ];
//If this frequency is or is close to one of the polynomial points, use c[k] for that point
//If this frequency is close to one of the polynomial points, use the polynomial point for the response
if( Math.abs( cosineDelta ) < 1.0e-7 )
{
numerator = mIdealFrequencyResponse[ k ];
denominator = 1.0;
break;
return mIdealFrequencyResponse[ k ];
}
else
{

View File

@@ -0,0 +1,710 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.fir.remez;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class RemezFIRFilterDesigner2
{
private final static Logger mLog = LoggerFactory.getLogger(RemezFIRFilterDesigner2.class);
private static final double CONVERGENCE_THRESHOLD = 0.0001;
public static final int MAXIMUM_ITERATION_COUNT = 40;
public static final double TWO_PI = 2.0 * Math.PI;
public static final double FREQUENCY_PROXIMITY_THRESHOLD = 1.0e-7;
private FIRFilterSpecification mSpecification;
private Grid mGrid;
private List<Integer> mExtremalIndices;
private BigDecimal[] mD;
private double[] mGridErrors;
private double[] mGridFrequencyResponse;
private double[] mIdealFrequencyResponse;
private double mDelta;
private boolean mConverged;
/**
* Linear FIR filter designer using the Parks-McClelland Remez exchange algorithm.
*
* Construct a filter specification using the builder methods in the FIRFilterSpecification
* class and construct an instance of this class with the specification. Check the isValid()
* method after construction to see if the filter design was successful. If successful, use the
* getFrequencyResponse() and getImpulseResponse() methods to access the filter taps as the
* specified length.
*
* If the filter cannot be designed from the specification the isValid() method will return
* false and any attempts to access the getFrequencyResponse() or getImpulseResponse() methods
* will throw a FilterDesignException.
*
* @param specification that defines a Type 1-4 linear phase FIR Filter
*/
public RemezFIRFilterDesigner2(FIRFilterSpecification specification)
{
mSpecification = specification;
design();
}
/**
* Designs the filter according to the specification
*/
private void design()
{
mLog.debug("Designing filter ...");
mGrid = new Grid(mSpecification);
mExtremalIndices = getInitialExtremalIndices();
int iterationCount = 0;
try
{
do
{
mLog.debug("Calculating grid frequency response");
calculateGridFrequencyResponse();
mLog.debug("Calculating grid error");
calculateGridError();
mLog.debug("Finding extremal indices");
findExtremalIndices();
mLog.debug("Checking Convergence");
checkConvergence();
iterationCount++;
mLog.debug("Iteration: " + iterationCount);
}
while(!mConverged && iterationCount < MAXIMUM_ITERATION_COUNT);
}
catch(FilterDesignException fde)
{
mLog.error("Filter design error - couldn't find extremal indices at count [" +
iterationCount + "] try changing filter order up/down from [" +
mSpecification.getOrder() + "]");
mConverged = false;
}
if(mConverged)
{
//Update frequency response using the final set of extremal indices
calculateGridFrequencyResponse();
}
}
/**
* Indicates if the filter was successfully created from the filter specification. Check this
* method before accessing frequency response or filter response, otherwise a FilterDesignException
* is thrown.
*
* @return true if the filter was successfully designed.
*/
public boolean isValid()
{
return mConverged;
}
/**
* Frequency response of the designed filter with double precision
*
* @return half ( length / 2 ) of filter frequency response sampled at 0.0 - PI radians
* @throws FilterDesignException if the specified filter cannot be designed
*/
public double[] getFrequencyResponse() throws FilterDesignException
{
if(!mConverged)
{
throw new FilterDesignException("Can't create filter from specification - failed "
+ "to converge");
}
//Resample the final polynomial at the desired filter length
double[] resampled = resample();
//Apply Parks/McClelland frequency response corrections according to the filter type
return correctFrequencyResponse(resampled);
}
/**
* Impulse response of the designed filter with floating point precision.
*
* @return filter impulse response
* @throws FilterDesignException if the specified filter cannot be designed
*/
public float[] getImpulseResponse() throws FilterDesignException
{
return convertToFloatArray(getImpulseResponseDoubles());
}
/**
* Impulse response of the designed filter with double precision
*/
public double[] getImpulseResponseDoubles() throws FilterDesignException
{
double[] frequencyResponse = getFrequencyResponse();
int length = mSpecification.getFilterLength();
double[] impulseResponse = new double[length];
double M;
switch(mSpecification.getFilterType())
{
case TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL:
M = ((double)length - 1.0) / 2.0;
for(int n = 0; n < length; n++)
{
double accumulator = frequencyResponse[0];
double frequency = TWO_PI * (n - M) / length;
for(int k = 1; k <= M; k++)
{
accumulator += 2.0 * frequencyResponse[k] * Math.cos(frequency * (double)k);
}
impulseResponse[n] = accumulator / (double)length;
}
break;
case TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL:
double offset = (double)(length - 1) / 2.0;
for(int n = 0; n < length; n++)
{
double accumulator = frequencyResponse[0];
double frequency = TWO_PI * ((double)n - offset) / (double)length;
for(int k = 1; k < frequencyResponse.length; k++)
{
accumulator += 2.0 * frequencyResponse[k] * Math.cos(frequency * (double)k);
}
impulseResponse[n] = accumulator / (double)length;
}
break;
}
return impulseResponse;
}
/**
* Calculates the frequency response of this filter at the specified cosine of the frequency
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 116a to
* determine the actual response of the specified frequency cosine using Lagrange
* interpolation of the polynomial formed at the extremal index set.
*
* @param cosineOfFrequency to calculate the frequency response
* @return actual frequency response for the specified frequency with the current set of
* extreaml indices
*/
public double getFrequencyResponse(double cosineOfFrequency)
{
BigDecimal numerator = BigDecimal.ZERO;
BigDecimal denominator = BigDecimal.ZERO;
for(int k = 0; k < mExtremalIndices.size() - 1; k++)
{
double cosineDelta = cosineOfFrequency - mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)];
//If this frequency is close to one of the polynomial points, use the point as the response value
if(Math.abs(cosineDelta) < FREQUENCY_PROXIMITY_THRESHOLD)
{
return mIdealFrequencyResponse[k];
}
else
{
BigDecimal dkOverCosineDelta = mD[k].divide(BigDecimal.valueOf(cosineDelta), 25, RoundingMode.HALF_UP);
numerator = numerator.add(dkOverCosineDelta.multiply(BigDecimal.valueOf(mIdealFrequencyResponse[k])));
denominator = denominator.add(dkOverCosineDelta);
}
}
if(denominator.doubleValue() == 0.0)
{
int a = 0;
}
return numerator.divide(denominator, 25, RoundingMode.HALF_UP).doubleValue();
}
/**
* Calculated Delta value
*
* @return delta value
*/
public double getDelta()
{
return mDelta;
}
/**
* Creates initial array of extremal frequency indices spaced at intervals of the grid frequency
* set for all frequencies where a frequency band weighting is specified.
*/
private List<Integer> getInitialExtremalIndices()
{
int count = mSpecification.getExtremaCount();
int density = mSpecification.getGridDensity();
List<Integer> extremalIndices = new ArrayList<>();
for(int i = 0; i < count; i++)
{
extremalIndices.add(i * density);
}
return extremalIndices;
}
/**
* Calculates the actual frequency response of a dense frequency grid using the polynomial
* represented by the extremal indices.
*/
private void calculateGridFrequencyResponse()
{
mLog.debug("\tCalculating B");
BigDecimal[] b = calculateB();
mLog.debug("\tCalculating Delta");
calculateDelta(b);
mLog.debug("\tCalculating C");
calculateC();
mLog.debug("\tCalculating D");
calculateD(b);
mLog.debug("\tUpdating Grid Frequency Response");
updateGridFrequencyResponse();
}
/**
* Updates the frequency response of frequencies specified in the dense frequency grid
*/
private void updateGridFrequencyResponse()
{
mGridFrequencyResponse = new double[mGrid.getSize()];
double[] gridFrequencyCosines = mGrid.getCosineFrequencyGrid();
for(int i = 0; i < mGridFrequencyResponse.length; i++)
{
//TODO: parallelize this part!!!!
mGridFrequencyResponse[i] = getFrequencyResponse(gridFrequencyCosines[i]);
if(i % 1000 == 0)
{
mLog.debug("\t\tUpdating Frequency Response - index:" + i + " of " +
mGridFrequencyResponse.length + " Value:" + mGridFrequencyResponse[i]);
}
}
}
/**
* Calculates the value of b across the set of extremal indices.
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 115
*
* @return the array of b values of length L+2
*/
private BigDecimal[] calculateB()
{
int length = mExtremalIndices.size();
BigDecimal[] b = new BigDecimal[length];
for(int k = 0; k < length; k++)
{
b[k] = BigDecimal.ONE;
double xk = mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)];
for(int i = 0; i < length; i++)
{
if(i != k)
{
double xi = mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(i)];
double denominator = xk - xi;
if(Math.abs(denominator) < 0.00001)
{
denominator = 0.00001;
}
b[k] = b[k].multiply(BigDecimal.valueOf(1.0 / denominator));
}
}
}
return b;
}
/**
* Calculates the value of delta that represents the maximum ripple for the current set of
* extremal indices (L+2)
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 114
*
* @param b calculated for the grid from equation 115
*/
private void calculateDelta(BigDecimal[] b)
{
BigDecimal numerator = BigDecimal.ZERO;
BigDecimal denominator = BigDecimal.ZERO;
double sign = 1.0;
for(int k = 0; k < b.length; k++)
{
if(k < mExtremalIndices.size())
{
int extremalIndex = mExtremalIndices.get(k);
numerator = numerator.add(b[k].multiply(BigDecimal.valueOf(mGrid.getDesiredResponse()[extremalIndex])));
denominator = denominator.add(b[k].multiply(BigDecimal.valueOf(sign / mGrid.getWeight()[extremalIndex])));
sign = -sign;
}
else
{
mLog.error("Something went wrong -- the length of b exceeds the set of extremal indices");
}
}
mDelta = numerator.divide(denominator, 1023, BigDecimal.ROUND_HALF_DOWN).doubleValue();
}
/**
* Calculates the ideal frequency response (C) of the current extremal index set. This reference
* is used to determine the level of error in the set of actual frequency responses calculated
* from this set of extremal indices to determine convergence.
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 116b
*/
private void calculateC()
{
int length = mSpecification.getExtremaCount() - 1;
mIdealFrequencyResponse = new double[length];
double sign = 1.0;
for(int k = 0; k < length; k++)
{
if(k < mExtremalIndices.size())
{
int index = mExtremalIndices.get(k);
mIdealFrequencyResponse[k] = mGrid.getDesiredResponse()[index] -
(sign * mDelta / mGrid.getWeight()[index]);
sign = -sign;
}
}
}
/**
* Calculates the set of D values for the current extremal index set.
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 116c
*/
private void calculateD(BigDecimal[] b)
{
int length = mExtremalIndices.size() - 1;
mD = new BigDecimal[length];
for(int k = 0; k < length; k++)
{
//Note: extremalCosines array is one index longer than d array, so we use length as
//a pointer to the final (L+2) extremalCosine index
mD[k] = b[k].multiply(BigDecimal.valueOf((mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)] -
mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(length)])));
}
}
/**
* Calculates the weighted error between the specification desired frequency response and the
* actual frequency response of the current extremal index set across the frequencies in the
* dense frequency grid.
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 112
*/
public void calculateGridError()
{
int length = mGridFrequencyResponse.length;
mGridErrors = new double[length];
for(int i = 0; i < length; i++)
{
mGridErrors[i] = mGrid.getWeight()[i] *
(mGrid.getDesiredResponse()[i] - mGridFrequencyResponse[i]);
}
}
/**
* Identifies the indexes of the extremal error values using the Alternation theorem
*
* @throws FilterDesignException if the full set of extremal indices can't be identified
*/
private void findExtremalIndices() throws FilterDesignException
{
mExtremalIndices.clear();
//Check for extremum at error index 0
if(((mGridErrors[0] > 0.0 && mGridErrors[0] > mGridErrors[1]) ||
(mGridErrors[0] < 0.0 && mGridErrors[0] < mGridErrors[1])) &&
isGTEDelta(mGridErrors[0]))
{
mExtremalIndices.add(0);
}
//Check for extrema in middle error indices )
for(int x = 1; x < mGridErrors.length - 1; x++)
{
if(((mGridErrors[x] > 0.0 && (mGridErrors[x - 1] <= mGridErrors[x] && mGridErrors[x] > mGridErrors[x + 1])) ||
(mGridErrors[x] < 0.0 && (mGridErrors[x - 1] >= mGridErrors[x] && mGridErrors[x] < mGridErrors[x + 1]))) &&
isGTEDelta(mGridErrors[x]))
{
mExtremalIndices.add(x);
}
}
//Check for extremum at final error index
int last = mGridErrors.length - 1;
if(((mGridErrors[last] > 0.0 && (mGridErrors[last] > mGridErrors[last - 1])) ||
(mGridErrors[last] < 0.0 && (mGridErrors[last] < mGridErrors[last - 1]))) &&
isGTEDelta(mGridErrors[last]))
{
mExtremalIndices.add(last);
}
//Ensure we have the minimum of extremals before we continue
if(mExtremalIndices.size() < mSpecification.getExtremaCount())
{
throw new FilterDesignException("Couldn't find the minimum extremal frequencies in "
+ "error set");
}
//Enforce alternation theory -- only one extremal for each transition about the zero axis
List<Integer> indicesToRemove = new ArrayList<>();
Iterator<Integer> it = mExtremalIndices.iterator();
Integer current = it.next();
Integer next;
boolean positiveAxis = mGridErrors[current] > 0.0;
while(it.hasNext())
{
next = it.next();
//Check for consecutive (redundant) indices on same side of zero axis - retain largest
if(!(positiveAxis ^ (mGridErrors[next] > 0.0)))
{
if(Math.abs(mGridErrors[next]) <= Math.abs(mGridErrors[current]))
{
//Remove next error index that is less than current index and on
it.remove();
next = current;
}
else
{
//Since we can't remove the current index with the iterator, queue for later removal
indicesToRemove.add(current);
}
}
else
{
positiveAxis = !positiveAxis;
}
current = next;
}
//Remove redundant extremal indices that couldn't be removed via the iterator
mExtremalIndices.removeAll(indicesToRemove);
//Truncate the list to one larger than needed by removing excess tailing indices
while(mExtremalIndices.size() > mSpecification.getExtremaCount())
{
mExtremalIndices.remove(mExtremalIndices.size() - 1);
}
//If we have one too many indices, delete the smaller of the first or last
if(mExtremalIndices.size() > mSpecification.getExtremaCount())
{
int lastIndex = mExtremalIndices.size() - 1;
if(Math.abs(mGridErrors[mExtremalIndices.get(0)]) >
Math.abs(mGridErrors[mExtremalIndices.get(lastIndex)]))
{
mExtremalIndices.remove(lastIndex);
}
else
{
mExtremalIndices.remove(0);
}
}
//Detect if we have too few indices
if(mExtremalIndices.size() < mSpecification.getExtremaCount())
{
throw new FilterDesignException("Couldn't find the minimum extremal frequencies in "
+ "error set");
}
}
/**
* Indicates if the absolute value of the argument is greater than or equal to the delta
* value with accuracy to 14 digits of precision. This avoids rounding errors at 15 digits or
* precision or greater.
*
* @param value to evaluate
* @return true if the absolute value is greater than or equal to the absolute delta value
*/
private boolean isGTEDelta(double value)
{
return Math.abs(value) - Math.abs(mDelta) > -1.0e-5;
}
/**
* Checks for convergence of the frequency response of the current set of extremal indices to
* the filter specification by comparing the maximum absolute error value against the delta
* value to determine if these two values are within the convergence threshold.
*/
public void checkConvergence()
{
double maximum = Math.abs(mGridErrors[mExtremalIndices.get(0)]);
for(int i = 1; i < mExtremalIndices.size(); i++)
{
double current = Math.abs(mGridErrors[mExtremalIndices.get(i)]);
if(current > maximum)
{
maximum = current;
}
}
double convergence = maximum - Math.abs(mDelta);
mConverged = convergence < CONVERGENCE_THRESHOLD;
}
/**
* Coverts/casts the double array to a float array
*/
private static float[] convertToFloatArray(double[] samples)
{
float[] converted = new float[samples.length];
for(int x = 0; x < samples.length; x++)
{
converted[x] = (float)samples[x];
}
return converted;
}
/**
* Resamples the designed filter at half of the filter length at evenly spaced intervals of the
* frequency spectrum from 0 to PI radians.
*
* @return frequency response of the filter sampled the desired filter length and order
*/
private double[] resample()
{
int length = mSpecification.getFilterLength();
if(length % 2 == 0)
{
length--;
}
double half = (double)length / 2.0;
double[] resampled = new double[(int)Math.ceil(half)];
for(int x = 0; x < resampled.length; x++)
{
resampled[x] = getFrequencyResponse(Math.cos(Math.PI * (double)x / half));
}
return resampled;
}
/**
* Applies Parks/McClelland filter type correction to the final frequency response array
*
* @param frequencyResponse of the resampled filter design from 0 <> ~ PI radians
* @return corrected frequency response
*/
private double[] correctFrequencyResponse(double[] frequencyResponse)
{
double filterLength = mSpecification.getFilterLength();
switch(mSpecification.getFilterType())
{
case TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL:
//No correction needed
break;
case TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL:
//No correction needed
break;
case TYPE_3_ODD_LENGTH_EVEN_ORDER_ANTI_SYMMETRICAL:
for(int x = 0; x < frequencyResponse.length; x++)
{
double frequencyRadians = Math.PI * (((double)x) / filterLength);
frequencyResponse[x] *= Math.sin(TWO_PI * frequencyRadians);
}
break;
case TYPE_4_EVEN_LENGTH_ODD_ORDER_ANTI_SYMMETRICAL:
for(int x = 0; x < frequencyResponse.length; x++)
{
double frequencyRadians = Math.PI * (((double)x) / filterLength);
frequencyResponse[x] *= Math.sin(Math.PI * frequencyRadians);
}
break;
default:
break;
}
return frequencyResponse;
}
}

View File

@@ -0,0 +1,686 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.fir.remez;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import org.apache.commons.math3.analysis.polynomials.PolynomialFunctionLagrangeForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class RemezFIRFilterDesignerWithLagrange
{
private final static Logger mLog = LoggerFactory.getLogger(RemezFIRFilterDesignerWithLagrange.class);
private static final double CONVERGENCE_THRESHOLD = 0.0001;
public static final int MAXIMUM_ITERATION_COUNT = 40;
public static final double TWO_PI = 2.0 * Math.PI;
private static final DecimalFormat DECIMAL_FORMATTER = new DecimalFormat("0.00000000");
private FIRFilterSpecification mSpecification;
private Grid mGrid;
private List<Integer> mExtremalIndices;
private double[] mD;
private double[] mGridErrors;
private double[] mGridFrequencyResponse;
private double[] mIdealFrequencyResponse;
private PolynomialFunctionLagrangeForm mLagrangeInterpolator;
private double mDelta;
private boolean mConverged;
/**
* Linear FIR filter designer using the Parks-McClelland Remez exchange algorithm.
*
* Construct a filter specification using the builder methods in the FIRFilterSpecification
* class and construct an instance of this class with the specification. Check the isValid()
* method after construction to see if the filter design was successful. If successful, use the
* getFrequencyResponse() and getImpulseResponse() methods to access the filter taps as the
* specified length.
*
* If the filter cannot be designed from the specification the isValid() method will return
* false and any attempts to access the getFrequencyResponse() or getImpulseResponse() methods
* will throw a FilterDesignException.
*
* @param specification that defines a Type 1-4 linear phase FIR Filter
*/
public RemezFIRFilterDesignerWithLagrange(FIRFilterSpecification specification) throws FilterDesignException
{
mSpecification = specification;
design();
}
/**
* Designs the filter according to the specification
*/
private void design() throws FilterDesignException
{
mGrid = new Grid(mSpecification);
mExtremalIndices = getInitialExtremalIndices();
int iterationCount = 0;
do
{
calculateGridFrequencyResponse();
calculateGridError();
findExtremalIndices();
checkConvergence();
iterationCount++;
}
while(!mConverged && iterationCount < MAXIMUM_ITERATION_COUNT);
if(mConverged)
{
//Update frequency response using the final set of extremal indices
calculateGridFrequencyResponse();
}
else
{
throw new FilterDesignException("Unable to design filter after [" + iterationCount + "] iterations");
}
}
/**
* Indicates if the filter was successfully created from the filter specification. Check this
* method before accessing frequency response or filter response, otherwise a FilterDesignException
* is thrown.
*
* @return true if the filter was successfully designed.
*/
public boolean isValid()
{
return mConverged;
}
/**
* Frequency response of the designed filter with double precision
*
* @return half ( length / 2 ) of filter frequency response sampled at 0.0 - PI radians
* @throws FilterDesignException if the specified filter cannot be designed
*/
public double[] getFrequencyResponse() throws FilterDesignException
{
if(!mConverged)
{
throw new FilterDesignException("Can't create filter from specification - failed "
+ "to converge ");
}
//Resample the final polynomial at the desired filter length
double[] resampled = resample();
//Apply Parks/McClelland frequency response corrections according to the filter type
return correctFrequencyResponse(resampled);
}
/**
* Impulse response of the designed filter with floating point precision.
*
* @return filter impulse response
* @throws FilterDesignException if the specified filter cannot be designed
*/
public float[] getImpulseResponse() throws FilterDesignException
{
return convertToFloatArray(getImpulseResponseDoubles());
}
/**
* Impulse response of the designed filter with double precision
*/
public double[] getImpulseResponseDoubles() throws FilterDesignException
{
double[] frequencyResponse = getFrequencyResponse();
int length = mSpecification.getFilterLength();
double[] impulseResponse = new double[length];
double M;
switch(mSpecification.getFilterType())
{
case TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL:
M = ((double)length - 1.0) / 2.0;
for(int n = 0; n < length; n++)
{
double accumulator = frequencyResponse[0];
double frequency = TWO_PI * (n - M) / length;
for(int k = 1; k <= M; k++)
{
accumulator += 2.0 * frequencyResponse[k] * Math.cos(frequency * (double)k);
}
impulseResponse[n] = accumulator / (double)length;
}
break;
case TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL:
double offset = (double)(length - 1) / 2.0;
for(int n = 0; n < length; n++)
{
double accumulator = frequencyResponse[0];
double frequency = TWO_PI * ((double)n - offset) / (double)length;
for(int k = 1; k < frequencyResponse.length; k++)
{
accumulator += 2.0 * frequencyResponse[k] * Math.cos(frequency * (double)k);
}
impulseResponse[n] = accumulator / (double)length;
}
break;
}
return impulseResponse;
}
/**
* Calculates the frequency response of this filter at the specified cosine of the frequency
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 116a to
* determine the actual response of the specified frequency cosine using Lagrange
* interpolation of the polynomial formed at the extremal index set.
*
* @param cosineOfFrequency to calculate the frequency response
* @return actual frequency response for the specified frequency with the current set of
* extreaml indices
*/
public double getFrequencyResponse(double cosineOfFrequency)
{
if(mLagrangeInterpolator == null)
{
throw new IllegalStateException("Lagrange interpolator is not setup - can't calculate frequency response");
}
return mLagrangeInterpolator.value(cosineOfFrequency);
// double numerator = 0.0;
// double denominator = 0.0;
//
// for( int k = 0; k < mExtremalIndices.size() - 1; k++ )
// {
// double cosineDelta = cosineOfFrequency - mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( k ) ];
//
// //If this frequency is close to one of the polynomial points, use the polynomial point for the response
// if( Math.abs( cosineDelta ) < 1.0e-7 )
// {
// return mIdealFrequencyResponse[ k ];
// }
// else
// {
// double dkOverCosineDelta = mD[ k ] / cosineDelta;
//
// numerator += dkOverCosineDelta * mIdealFrequencyResponse[ k ];
// denominator += dkOverCosineDelta;
// }
// }
//
// return numerator / denominator;
}
/**
* Calculated Delta value
*
* @return delta value
*/
public double getDelta()
{
return mDelta;
}
/**
* Creates initial array of extremal frequency indices spaced at intervals of the grid frequency
* set for all frequencies where a frequency band weighting is specified.
*/
private List<Integer> getInitialExtremalIndices()
{
int count = mSpecification.getExtremaCount();
int density = mSpecification.getGridDensity();
List<Integer> extremalIndices = new ArrayList<>();
for(int i = 0; i < count; i++)
{
extremalIndices.add(i * density);
}
return extremalIndices;
}
/**
* Calculates the actual frequency response of a dense frequency grid using the polynomial
* represented by the extremal indices.
*/
private void calculateGridFrequencyResponse()
{
BigDecimal[] b = calculateB();
calculateDelta(b);
calculateC();
//Setup the lagrange polynomial so that we can determine the frequency response at the grid frequencies
//where mExtremalIndices are the polynomial points, the subset of cosine frequency grid identified by the
//extremal indices is the x value set and mIdealFrequencyResponse is the y axis value set
double[] xAxis = new double[mIdealFrequencyResponse.length];
for(int x = 0; x < mExtremalIndices.size() - 1; x++)
{
int index = mExtremalIndices.get(x);
xAxis[x] = mGrid.getCosineFrequencyGrid()[index];
}
mLagrangeInterpolator = new PolynomialFunctionLagrangeForm(xAxis, mIdealFrequencyResponse);
updateGridFrequencyResponse();
}
/**
* Updates the frequency response of frequencies specified in the dense frequency grid
*/
private void updateGridFrequencyResponse()
{
mGridFrequencyResponse = new double[mGrid.getSize()];
double[] gridFrequencyCosines = mGrid.getCosineFrequencyGrid();
for(int i = 0; i < mGridFrequencyResponse.length; i++)
{
mGridFrequencyResponse[i] = getFrequencyResponse(gridFrequencyCosines[i]);
}
}
/**
* Calculates the value of b across the set of extremal indices.
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 115
*
* @return the array of b values of length L+2
*/
private BigDecimal[] calculateB()
{
int length = mExtremalIndices.size();
BigDecimal[] b = new BigDecimal[length];
for(int k = 0; k < length; k++)
{
b[k] = BigDecimal.ONE;
double xk = mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)];
for(int i = 0; i < length; i++)
{
if(i != k)
{
double xi = mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(i)];
double denominator = xk - xi;
if(Math.abs(denominator) < 0.00001)
{
denominator = 0.00001;
}
b[k] = b[k].multiply(BigDecimal.valueOf(1.0 / denominator));
// b[ k ] *= 1.0 / denominator;
}
}
}
return b;
}
/**
* Calculates the value of delta that represents the maximum ripple for the current set of
* extremal indices (L+2)
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 114
*
* @param b calculated for the grid from equation 115
*/
private void calculateDelta(BigDecimal[] b)
{
BigDecimal numerator = BigDecimal.ZERO;
BigDecimal denominator = BigDecimal.ZERO;
double sign = 1.0;
for(int k = 0; k < b.length; k++)
{
if(k < mExtremalIndices.size())
{
int extremalIndex = mExtremalIndices.get(k);
numerator = numerator.add(b[k].multiply(BigDecimal.valueOf(mGrid.getDesiredResponse()[extremalIndex])));
denominator = denominator.add(b[k].multiply(BigDecimal.valueOf(sign / mGrid.getWeight()[extremalIndex])));
sign = -sign;
}
else
{
mLog.error("Something went wrong -- the length of b exceeds the set of extremal indices");
}
}
//Set the scale to the maximum scale we could see for a primitive decimal
mDelta = numerator.divide(denominator, 1023, BigDecimal.ROUND_HALF_DOWN).doubleValue();
}
/**
* Calculates the ideal frequency response (C) of the current extremal index set. This reference
* is used to determine the level of error in the set of actual frequency responses calculated
* from this set of extremal indices to determine convergence.
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 116b
*/
private void calculateC()
{
int length = mSpecification.getExtremaCount() - 1;
mIdealFrequencyResponse = new double[length];
double sign = 1.0;
for(int k = 0; k < length; k++)
{
if(k < mExtremalIndices.size())
{
int index = mExtremalIndices.get(k);
mIdealFrequencyResponse[k] = mGrid.getDesiredResponse()[index] -
(sign * mDelta / mGrid.getWeight()[index]);
sign = -sign;
}
}
}
/**
* Calculates the weighted error between the specification desired frequency response and the
* actual frequency response of the current extremal index set across the frequencies in the
* dense frequency grid.
*
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 112
*/
public void calculateGridError()
{
int length = mGridFrequencyResponse.length;
mGridErrors = new double[length];
for(int i = 0; i < length; i++)
{
mGridErrors[i] = mGrid.getWeight()[i] * (mGrid.getDesiredResponse()[i] - mGridFrequencyResponse[i]);
}
}
/**
* Identifies the indexes of the extremal error values using the Alternation theorem
*
* @throws FilterDesignException if the full set of extremal indices can't be identified
*/
private void findExtremalIndices() throws FilterDesignException
{
mExtremalIndices.clear();
//Check for extremum at error index 0
if(((mGridErrors[0] > 0.0 && mGridErrors[0] > mGridErrors[1]) ||
(mGridErrors[0] < 0.0 && mGridErrors[0] < mGridErrors[1])) &&
isGTEDelta(mGridErrors[0]))
{
mExtremalIndices.add(0);
}
//Check for extrema in middle error indices )
for(int x = 1; x < mGridErrors.length - 1; x++)
{
if(((mGridErrors[x] > 0.0 && (mGridErrors[x - 1] <= mGridErrors[x] && mGridErrors[x] > mGridErrors[x + 1])) ||
(mGridErrors[x] < 0.0 && (mGridErrors[x - 1] >= mGridErrors[x] && mGridErrors[x] < mGridErrors[x + 1]))) &&
isGTEDelta(mGridErrors[x]))
{
mExtremalIndices.add(x);
}
}
//Check for extremum at final error index
int last = mGridErrors.length - 1;
if(((mGridErrors[last] > 0.0 && (mGridErrors[last] > mGridErrors[last - 1])) ||
(mGridErrors[last] < 0.0 && (mGridErrors[last] < mGridErrors[last - 1]))) &&
isGTEDelta(mGridErrors[last]))
{
mExtremalIndices.add(last);
}
//Ensure we have the minimum of extremals before we continue
if(mExtremalIndices.size() < mSpecification.getExtremaCount())
{
throw new FilterDesignException("Couldn't find the minimum extremal frequencies in "
+ "error set before checking for alternation theory - found:" + mExtremalIndices.size() +
" required:" + mSpecification.getExtremaCount());
}
//Enforce alternation theory -- only one extremal for each transition about the zero axis
List<Integer> indicesToRemove = new ArrayList<>();
Iterator<Integer> it = mExtremalIndices.iterator();
Integer current = it.next();
Integer next;
boolean positiveAxis = mGridErrors[current] > 0.0;
while(it.hasNext())
{
next = it.next();
//Check for consecutive (redundant) indices on same side of zero axis - retain largest
if(!(positiveAxis ^ (mGridErrors[next] > 0.0)))
{
if(Math.abs(mGridErrors[next]) <= Math.abs(mGridErrors[current]))
{
//Remove next error index that is less than current index and on
it.remove();
next = current;
}
else
{
//Since we can't remove the current index with the iterator, queue for later removal
indicesToRemove.add(current);
}
}
else
{
positiveAxis = !positiveAxis;
}
current = next;
}
//Remove redundant extremal indices that couldn't be removed via the iterator
mExtremalIndices.removeAll(indicesToRemove);
//Truncate the list to one larger than needed by removing excess tailing indices
while(mExtremalIndices.size() > mSpecification.getExtremaCount())
{
mExtremalIndices.remove(mExtremalIndices.size() - 1);
}
//If we have one too many indices, delete the smaller of the first or last
if(mExtremalIndices.size() > mSpecification.getExtremaCount())
{
int lastIndex = mExtremalIndices.size() - 1;
if(Math.abs(mGridErrors[mExtremalIndices.get(0)]) >
Math.abs(mGridErrors[mExtremalIndices.get(lastIndex)]))
{
mExtremalIndices.remove(lastIndex);
}
else
{
mExtremalIndices.remove(0);
}
}
//Detect if we have too few indices
if(mExtremalIndices.size() < mSpecification.getExtremaCount())
{
throw new FilterDesignException("Couldn't find the minimum extremal frequencies in "
+ "error set - found:" + mExtremalIndices.size() + " required:" + mSpecification.getExtremaCount());
}
}
/**
* Indicates if the absolute value of the argument is greater than or equal to the delta
* value with accuracy to 14 digits of precision. This avoids rounding errors at 15 digits or
* precision or greater.
*
* @param value to evaluate
* @return true if the absolute value is greater than or equal to the absolute delta value
*/
private boolean isGTEDelta(double value)
{
return Math.abs(value) - Math.abs(mDelta) > -1.0e-5;
}
/**
* Checks for convergence of the frequency response of the current set of extremal indices to
* the filter specification by comparing the maximum absolute error value against the delta
* value to determine if these two values are within the convergence threshold.
*/
public void checkConvergence()
{
double maximum = Math.abs(mGridErrors[mExtremalIndices.get(0)]);
for(int i = 1; i < mExtremalIndices.size(); i++)
{
double current = Math.abs(mGridErrors[mExtremalIndices.get(i)]);
if(current > maximum)
{
maximum = current;
}
}
double convergence = maximum - Math.abs(mDelta);
mLog.debug("Convergence: " + convergence);
mConverged = convergence < CONVERGENCE_THRESHOLD;
}
/**
* Coverts/casts the double array to a float array
*/
private static float[] convertToFloatArray(double[] samples)
{
float[] converted = new float[samples.length];
for(int x = 0; x < samples.length; x++)
{
converted[x] = (float)samples[x];
}
return converted;
}
/**
* Resamples the designed filter at half of the filter length at evenly spaced intervals of the
* frequency spectrum from 0 to PI radians.
*
* @return frequency response of the filter sampled the desired filter length and order
*/
private double[] resample()
{
int length = mSpecification.getFilterLength();
if(length % 2 == 0)
{
length--;
}
double half = (double)length / 2.0;
double[] resampled = new double[(int)Math.ceil(half)];
for(int x = 0; x < resampled.length; x++)
{
resampled[x] = getFrequencyResponse(Math.cos(Math.PI * (double)x / half));
}
return resampled;
}
/**
* Applies Parks/McClelland filter type correction to the final frequency response array
*
* @param frequencyResponse of the resampled filter design from 0 <> ~ PI radians
* @return corrected frequency response
*/
private double[] correctFrequencyResponse(double[] frequencyResponse)
{
double filterLength = mSpecification.getFilterLength();
switch(mSpecification.getFilterType())
{
case TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL:
//No correction needed
break;
case TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL:
//No correction needed
break;
case TYPE_3_ODD_LENGTH_EVEN_ORDER_ANTI_SYMMETRICAL:
for(int x = 0; x < frequencyResponse.length; x++)
{
double frequencyRadians = Math.PI * (((double)x) / filterLength);
frequencyResponse[x] *= Math.sin(TWO_PI * frequencyRadians);
}
break;
case TYPE_4_EVEN_LENGTH_ODD_ORDER_ANTI_SYMMETRICAL:
for(int x = 0; x < frequencyResponse.length; x++)
{
double frequencyRadians = Math.PI * (((double)x) / filterLength);
frequencyResponse[x] *= Math.sin(Math.PI * frequencyRadians);
}
break;
default:
break;
}
return frequencyResponse;
}
}

View File

@@ -142,7 +142,7 @@ public class AutomaticGainControl
public float process( float currentSample )
{
float delayedSample = mDelayBuffer.get( currentSample );
float delayedSample = mDelayBuffer.putAndGet( currentSample );
double gain = MANUAL_AGC_GAIN;

View File

@@ -74,7 +74,7 @@ public class ComplexFeedForwardGainControl implements ComplexSampleListener
}
/* Replace oldest envelope value with current envelope value */
float oldestEnvelope = mEnvelopeHistory.get( envelope );
float oldestEnvelope = mEnvelopeHistory.putAndGet( envelope );
/* If the oldest envelope value was the max envelope value, then we
* have to rediscover the max value from the envelope history */

View File

@@ -0,0 +1,188 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.mixer;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LowPhaseNoiseOscillator
{
private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class);
private static final double TWO_PI = 2.0 * Math.PI;
private static final float THREE_HALVES = 3.0f / 2.0f;
private int mSampleRate;
private long mFrequency;
private float mInphase = 1.0f;
private float mQuadrature = 0.0f;
private float mPreviousInphase = 1.0f;
private float mPreviousQuadrature = 0.0f;
private float mCosineAngle;
private float mSineAngle;
private float mGain = 1.0f;
/**
* Ultra-low phase noise complex oscillator as described in Digital Signal Processing 3e, Lyons, p.786
*
* @param sampleRate for the oscillator
* @param frequency to generate
*/
public LowPhaseNoiseOscillator(int sampleRate, long frequency)
{
mSampleRate = sampleRate;
mFrequency = frequency;
update();
}
/**
* Sets the sample rate for this oscillator
*/
public void setSampleRate(int sampleRate)
{
mSampleRate = sampleRate;
update();
}
/**
* Sample rate for this oscillator
*/
public int getSampleRate()
{
return mSampleRate;
}
/**
* Sets the tone frequency generated by this oscillator
*/
public void setFrequency(long frequency)
{
mFrequency = frequency;
update();
}
/**
* Current tone frequency for this oscillator
*/
public long getFrequency()
{
return mFrequency;
}
/**
* Updates the internal values after a frequency or sample rate change
*/
private void update()
{
double anglePerSample = TWO_PI * mFrequency / mSampleRate;
mCosineAngle = (float)Math.cos(anglePerSample);
mSineAngle = (float)Math.sin(anglePerSample);
}
private void rotate()
{
mInphase = ((mPreviousInphase * mCosineAngle) - (mPreviousQuadrature * mSineAngle)) * mGain;
mQuadrature = ((mPreviousInphase * mSineAngle) + (mPreviousQuadrature * mCosineAngle)) * mGain;
mPreviousInphase = mInphase;
mPreviousQuadrature = mQuadrature;
mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature));
}
/**
* Generates an array of samples with length of 2 x sample count containing interleaved inphase and quadrature
* samples.
*
* @param sampleCount number of samples to generate
* @return array that is sampleCount * 2 in length
*/
public float[] generate(int sampleCount)
{
float[] samples = new float[sampleCount * 2];
for(int x = 0; x < sampleCount; x++)
{
int index = x * 2;
samples[index] = mInphase;
samples[index + 1] = mQuadrature;
rotate();
}
return samples;
}
public static void main(String[] args)
{
int sampleRate = 10000000;
int frequency = 24000;
int sampleSize = 65536;
int warmUpIterations = 100000;
int testIterations = 500000;
LowPhaseNoiseOscillator o = new LowPhaseNoiseOscillator(sampleRate, frequency);
mLog.debug("Warming up ...");
for(int x = 0; x < warmUpIterations; x++)
{
float[] samples = o.generate(sampleSize);
}
mLog.debug("Testing ...");
long start = System.nanoTime();
for(int x = 0; x < testIterations; x++)
{
float[] samples = o.generate(sampleSize);
}
long duration = System.nanoTime() - start;
double durationSeconds = (double)duration / 1000000000.0;
mLog.debug("New Took:" + durationSeconds + " seconds to generate " + (testIterations * sampleSize) + " samples");
Oscillator o2 = new Oscillator(frequency, sampleRate);
mLog.debug("Warming up ...");
for(int x = 0; x < warmUpIterations; x++)
{
ComplexBuffer samples = o2.generateComplexBuffer(sampleSize);
}
mLog.debug("Testing ...");
long start2 = System.nanoTime();
for(int x = 0; x < testIterations; x++)
{
float[] samples = o.generate(sampleSize);
}
long duration2 = System.nanoTime() - start2;
double durationSeconds2 = (double)duration2 / 1000000000.0;
mLog.debug("New Took:" + durationSeconds2 + " seconds to generate " + (testIterations * sampleSize) + " samples");
}
}

View File

@@ -1,112 +1,152 @@
/*******************************************************************************
* 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/>
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.mixer;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Oscillator
{
private final static Logger mLog = LoggerFactory.getLogger( Oscillator.class );
private final static Logger mLog = LoggerFactory.getLogger(Oscillator.class);
private double mFrequency;
private double mSampleRate;
private boolean mEnabled = true;
private double mFrequency;
private double mSampleRate;
private Complex mAnglePerSample;
private Complex mCurrentAngle = new Complex( 0.0f, -1.0f );
private Complex mAnglePerSample;
private Complex mCurrentAngle = new Complex(0.0f, -1.0f);
/**
* Oscillator produces complex or float samples corresponding to a sine wave
* oscillating at the specified frequency and sample rate
*
* @param frequency - positive or negative frequency in hertz
* @param sampleRate - in hertz
*/
public Oscillator( long frequency, int sampleRate )
{
mSampleRate = (double)sampleRate;
mFrequency = (double)frequency;
update();
}
/**
* Updates the internal values after a frequency or sample rate change
*/
private void update()
{
float anglePerSample =
(float)( 2.0d * Math.PI * mFrequency / mSampleRate );
/**
* Oscillator produces complex or float samples corresponding to a sine wave
* oscillating at the specified frequency and sample rate
*
* @param frequency - positive or negative frequency in hertz
* @param sampleRate - in hertz
*/
public Oscillator(long frequency, double sampleRate)
{
mEnabled = frequency != 0;
mAnglePerSample = Complex.fromAngle( anglePerSample );
}
mSampleRate = sampleRate;
mFrequency = (double)frequency;
/**
* Sets or changes the frequency of this oscillator
*/
public void setFrequency( long frequency )
{
mFrequency = (double)frequency;
update();
}
update();
}
/**
* Sets or changes the sample rate of this oscillator
*/
public void setSampleRate( int sampleRate )
{
mSampleRate = (double)sampleRate;
update();
}
/**
* Indicates if this oscillator has a non-zero frequency setting. When used as a frequency translating
* mixer, setting a frequency of zero indicates that no frequency correction is needed, so this flag can
* indicate when to apply the oscillator to frequency correct samples.
*
* @return true if the current frequency value is non-zero.
*/
public boolean enabled()
{
return mEnabled;
}
/**
* Steps the current angle by the angle per sample amount
*/
public void rotate()
{
mCurrentAngle.multiply( mAnglePerSample );
mCurrentAngle.fastNormalize();
}
/**
* Updates the internal values after a frequency or sample rate change
*/
private void update()
{
float anglePerSample =
(float)(2.0d * Math.PI * mFrequency / mSampleRate);
public float inphase()
{
return mCurrentAngle.inphase();
}
public float quadrature()
{
return mCurrentAngle.quadrature();
}
/**
* Get next complex sample
*/
public Complex getComplex()
{
return mCurrentAngle.copy();
}
mAnglePerSample = Complex.fromAngle(anglePerSample);
}
/**
* Get the next float sample
*/
public float getFloat()
{
return mCurrentAngle.real();
}
/**
* Sets or changes the frequency of this oscillator
*/
public void setFrequency(long frequency)
{
mEnabled = frequency != 0;
mFrequency = (double)frequency;
update();
}
/**
* Sets or changes the sample rate of this oscillator
*/
public void setSampleRate(double sampleRate)
{
mSampleRate = sampleRate;
update();
}
/**
* Steps the current angle by the angle per sample amount
*/
public void rotate()
{
mCurrentAngle.multiply(mAnglePerSample);
mCurrentAngle.fastNormalize();
}
public float inphase()
{
return mCurrentAngle.inphase();
}
public float quadrature()
{
return mCurrentAngle.quadrature();
}
/**
* Get next complex sample
*/
public Complex getComplex()
{
return mCurrentAngle.copy();
}
/**
* Get the next float sample
*/
public float getFloat()
{
return mCurrentAngle.real();
}
/**
* Generates the requested number of complex samples and returns them in a complex buffer
*
* @param sampleCount to generate
* @return complex buffer containing the requested complex samples
*/
public ComplexBuffer generateComplexBuffer(int sampleCount)
{
float[] buffer = new float[sampleCount * 2];
for(int x = 0; x < sampleCount; x++)
{
int index = 2 * x;
buffer[index] = inphase();
buffer[index + 1] = quadrature();
rotate();
}
return new ComplexBuffer(buffer);
}
}

View File

@@ -41,7 +41,7 @@ import io.github.dsheirer.instrument.tap.stream.EyeDiagramDataTap;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
import io.github.dsheirer.source.ISourceEventListener;
import java.util.ArrayList;
import java.util.Arrays;
@@ -72,7 +72,7 @@ public class LSMDemodulator implements Instrumentable, ComplexSampleListener
private CostasLoop mCostasLoop = new CostasLoop();
private IFrequencyChangeListener mFrequencyChangeListener;
private ISourceEventListener mFrequencyChangeListener;
private EyeDiagramDataTap mEyeDiagramDataTap;
@@ -146,12 +146,12 @@ public class LSMDemodulator implements Instrumentable, ComplexSampleListener
}
}
public void addListener( IFrequencyChangeListener listener )
public void addListener( ISourceEventListener listener )
{
mFrequencyChangeListener = listener;
}
public void removeListener( IFrequencyChangeListener listener )
public void removeListener( ISourceEventListener listener )
{
mFrequencyChangeListener = null;
}
@@ -201,8 +201,8 @@ public class LSMDemodulator implements Instrumentable, ComplexSampleListener
// if( correctionNeeded && mFrequencyChangeListener != null )
// {
// mLog.debug( "Issuing Frequency Correction: " + mFrequencyError );
// mFrequencyChangeListener.frequencyChanged(
// new FrequencyChangeEvent( Attribute.FREQUENCY_ERROR,
// mFrequencyChangeListener.process(
// new SourceEvent( Attribute.FREQUENCY_ERROR,
// mFrequencyError ) );
// }
//

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,17 +17,25 @@
******************************************************************************/
package io.github.dsheirer.gui.control;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.InvalidFrequencyException;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceEvent.Event;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
@@ -40,13 +48,12 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class JFrequencyControl extends JPanel implements IFrequencyChangeProcessor
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger(JFrequencyControl.class);
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
private List<IFrequencyChangeProcessor> mProcessors = new ArrayList<>();
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
private Color mHighlightColor = Color.YELLOW;
@@ -54,7 +61,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
private Cursor mBlankCursor;
private HashMap<Integer, Digit> mDigits = new HashMap<Integer, Digit>();
private HashMap<Integer,Digit> mDigits = new HashMap<Integer,Digit>();
public JFrequencyControl()
{
@@ -73,7 +80,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
Font font = new Font(Font.MONOSPACED, Font.BOLD, 30);
for (int x = 9; x >= 0; x--)
for(int x = 9; x >= 0; x--)
{
Digit digit = null;
@@ -81,13 +88,13 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
{
digit = new Digit(x);
}
catch (ParseException e)
catch(ParseException e)
{
mLog.error("JFrequencyControl - parse exception "
+ "constructing a digit - " + e);
+ "constructing a digit - " + e);
}
if (digit != null)
if(digit != null)
{
mDigits.put(x, digit);
@@ -95,7 +102,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
digit.setFont(font);
if (x == 6)
if(x == 6)
{
JLabel period = new JLabel(".");
@@ -116,7 +123,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
Image cursorImage = Toolkit.getDefaultToolkit().createImage(imageByte);
mBlankCursor = Toolkit.getDefaultToolkit()
.createCustomCursor(cursorImage, new Point(0, 0), "cursor");
.createCustomCursor(cursorImage, new Point(0, 0), "cursor");
revalidate();
}
@@ -126,7 +133,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
{
super.setEnabled(enabled);
for (Digit digit : mDigits.values())
for(Digit digit : mDigits.values())
{
digit.setEnabled(enabled);
}
@@ -137,9 +144,9 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
* rebroadcast this event, just set the control to indicate the new frequency.
*/
@Override
public void frequencyChanged(FrequencyChangeEvent event)
public void process(SourceEvent event)
{
if (event.getEvent() == Event.NOTIFICATION_FREQUENCY_CHANGE)
if(event.getEvent() == Event.NOTIFICATION_FREQUENCY_CHANGE)
{
setFrequency(event.getValue().longValue(), false);
}
@@ -153,7 +160,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
{
mFrequency = frequency;
for (Digit digit : mDigits.values())
for(Digit digit : mDigits.values())
{
digit.setFrequency(frequency, fireChangeEvent);
}
@@ -168,7 +175,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
{
long frequency = 0;
for (Digit digit : mDigits.values())
for(Digit digit : mDigits.values())
{
frequency += digit.getFrequency();
}
@@ -176,27 +183,26 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
mFrequency = frequency;
}
private void fireFrequencyChanged() throws SourceException
private void fireSourceChanged() throws SourceException
{
updateFrequency();
Iterator<IFrequencyChangeProcessor> it = mProcessors.iterator();
Iterator<ISourceEventProcessor> it = mProcessors.iterator();
FrequencyChangeEvent event = new FrequencyChangeEvent(
Event.REQUEST_FREQUENCY_CHANGE, mFrequency);
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
while (it.hasNext())
while(it.hasNext())
{
it.next().frequencyChanged(event);
it.next().process(event);
}
}
public void addListener(IFrequencyChangeProcessor processor)
public void addListener(ISourceEventProcessor processor)
{
mProcessors.add(processor);
}
public void removeListener(IFrequencyChangeProcessor processor)
public void removeListener(ISourceEventProcessor processor)
{
mProcessors.remove(processor);
}
@@ -228,17 +234,17 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
public void setFrequency(long frequency, boolean fireChangeEvent)
{
//Strip the digits higher than this one
long lower = frequency % (long) (Math.pow(10, mPower + 1));
long lower = frequency % (long)(Math.pow(10, mPower + 1));
//Set the value to int value of dividing by 10 to this power
long value = (long) (lower / (long) (Math.pow(10, mPower)));
long value = (long)(lower / (long)(Math.pow(10, mPower)));
set(value, fireChangeEvent);
}
public long getFrequency()
{
return mValue * (long) Math.pow(10, mPower);
return mValue * (long)Math.pow(10, mPower);
}
public void increment()
@@ -248,7 +254,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
public void increment(boolean fireChangeEvent)
{
if (isEnabled())
if(isEnabled())
{
set(mValue + 1, fireChangeEvent);
}
@@ -261,7 +267,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
public void decrement(boolean fireChangeEvent)
{
if (isEnabled())
if(isEnabled())
{
set(mValue - 1, fireChangeEvent);
}
@@ -287,13 +293,13 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
mValue = amount;
while (mValue < 0)
while(mValue < 0)
{
mValue += 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if (nextHigherDigit != null)
if(nextHigherDigit != null)
{
nextHigherDigit.decrement(false);
higherDigitDecremented = true;
@@ -301,55 +307,55 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
}
while (mValue > 9)
while(mValue > 9)
{
mValue -= 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if (nextHigherDigit != null)
if(nextHigherDigit != null)
{
nextHigherDigit.increment(false);
higherDigitIncremented = true;
}
}
if (fireChangeEvent)
if(fireChangeEvent)
{
try
{
fireFrequencyChanged();
fireSourceChanged();
}
catch (SourceException se)
catch(SourceException se)
{
mValue = previous;
if (higherDigitIncremented)
if(higherDigitIncremented)
{
Digit nextHigherDigit = mDigits.get(mPower + 1);
if (nextHigherDigit != null)
if(nextHigherDigit != null)
{
nextHigherDigit.decrement(false);
}
}
if (higherDigitDecremented)
if(higherDigitDecremented)
{
Digit nextHigherDigit = mDigits.get(mPower + 1);
if (nextHigherDigit != null)
if(nextHigherDigit != null)
{
nextHigherDigit.increment(false);
}
}
if (se instanceof InvalidFrequencyException)
if(se instanceof InvalidFrequencyException)
{
InvalidFrequencyException ife = (InvalidFrequencyException) se;
InvalidFrequencyException ife = (InvalidFrequencyException)se;
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
}
}
}
@@ -361,15 +367,15 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
}
private class Listener implements KeyListener,
MouseListener,
MouseWheelListener
MouseListener,
MouseWheelListener
{
@Override
public void keyReleased(KeyEvent e)
{
int key = e.getKeyCode();
switch (key)
switch(key)
{
case KeyEvent.VK_0:
case KeyEvent.VK_NUMPAD0:
@@ -456,7 +462,7 @@ public class JFrequencyControl extends JPanel implements IFrequencyChangeProcess
@Override
public void mouseClicked(MouseEvent e)
{
switch (e.getButton())
switch(e.getButton())
{
case MouseEvent.BUTTON1:
increment();

View File

@@ -21,7 +21,10 @@ import io.github.dsheirer.instrument.gui.SampleModel;
import io.github.dsheirer.instrument.tap.Tap;
import io.github.dsheirer.instrument.tap.TapViewPanel;
import java.awt.*;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
@@ -90,7 +93,7 @@ public class BinaryTapViewPanel extends TapViewPanel
* Paints a polyline representing a boolean value with a 0 or 1 label in
* the middle of it
*
* @param graphics
* @param graphics object
* @param index - index of the sample int the sample buffer
* @param value - bit value, 0 or 1
*/

View File

@@ -18,8 +18,6 @@
package io.github.dsheirer.module;
import java.util.concurrent.ScheduledExecutorService;
public abstract class Module
{
/**
@@ -59,9 +57,9 @@ public abstract class Module
public abstract void reset();
/**
* Start processing. Provides an executor to use for scheduling tasks
* Start processing.
*/
public abstract void start( ScheduledExecutorService executor );
public abstract void start();
/**
* Stop processing

View File

@@ -24,9 +24,6 @@ import io.github.dsheirer.audio.IAudioPacketProvider;
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.channel.heartbeat.Heartbeat;
import io.github.dsheirer.channel.heartbeat.IHeartbeatListener;
import io.github.dsheirer.channel.heartbeat.IHeartbeatProvider;
import io.github.dsheirer.channel.metadata.AttributeChangeRequest;
import io.github.dsheirer.channel.metadata.IAttributeChangeRequestListener;
import io.github.dsheirer.channel.metadata.IAttributeChangeRequestProvider;
@@ -60,12 +57,14 @@ import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener;
import io.github.dsheirer.sample.real.IUnFilteredRealBufferProvider;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.source.ComplexSource;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.RealSource;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -101,8 +100,8 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster<>();
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private Broadcaster<DecoderStateEvent> mDecoderStateEventBroadcaster = new Broadcaster<>();
private Broadcaster<FrequencyChangeEvent> mFrequencyChangeEventBroadcaster = new Broadcaster<>();
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
private Broadcaster<Message> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
@@ -190,11 +189,13 @@ public class ProcessingChain implements IChannelEventListener
}
/**
* Applies a source to this processing chain. Processing won't start until the start() method is invoked.
* Applies a sample source to this processing chain. Processing won't
* start until the start() method is invoked.
*
* @param source - real or complex source
* @throws IllegalStateException if the processing chain is currently processing with another source. Invoke stop()
* before applying a new source.
* @param source - real or complex sample source
* @throws IllegalStateException if the processing chain is currently
* processing with another source. Invoke stop() before applying a new
* source.
*/
public void setSource(Source source) throws IllegalStateException
{
@@ -316,14 +317,13 @@ public class ProcessingChain implements IChannelEventListener
mDecoderStateEventBroadcaster.addListener(((IDecoderStateEventListener) module).getDecoderStateListener());
}
if(module instanceof IFrequencyChangeListener)
if(module instanceof ISourceEventListener)
{
Listener<FrequencyChangeEvent> frequencyChangeEventListener =
((IFrequencyChangeListener)module).getFrequencyChangeListener();
Listener<SourceEvent> listener = ((ISourceEventListener)module).getSourceEventListener();
if(frequencyChangeEventListener != null)
if(listener != null)
{
mFrequencyChangeEventBroadcaster.addListener(frequencyChangeEventListener);
mSourceEventBroadcaster.addListener(listener);
}
}
@@ -389,9 +389,9 @@ public class ProcessingChain implements IChannelEventListener
mDecoderStateEventBroadcaster.removeListener(((IDecoderStateEventListener) module).getDecoderStateListener());
}
if(module instanceof IFrequencyChangeListener)
if(module instanceof ISourceEventListener)
{
mFrequencyChangeEventBroadcaster.removeListener(((IFrequencyChangeListener) module).getFrequencyChangeListener());
mSourceEventBroadcaster.removeListener(((ISourceEventListener) module).getSourceEventListener());
}
if(module instanceof IHeartbeatListener)
@@ -451,14 +451,9 @@ public class ProcessingChain implements IChannelEventListener
((IDecoderStateEventProvider) module).setDecoderStateListener(mDecoderStateEventBroadcaster);
}
if(module instanceof IFrequencyChangeProvider)
{
((IFrequencyChangeProvider) module).setFrequencyChangeListener(mFrequencyChangeEventBroadcaster);
}
if(module instanceof IHeartbeatProvider)
{
((IHeartbeatProvider)module).setHeartbeatListener(mHeartbeatBroadcaster);
((IHeartbeatProvider)module).getHeartbeatManager().addHeartbeatListener(mHeartbeatBroadcaster);
}
if(module instanceof IMessageProvider)
@@ -471,6 +466,16 @@ public class ProcessingChain implements IChannelEventListener
((IFilteredRealBufferProvider) module).setFilteredRealBufferListener(mFilteredRealBufferBroadcaster);
}
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
}
if(module instanceof ISourceEventProvider)
{
((ISourceEventProvider) module).setSourceEventListener(mSourceEventBroadcaster);
}
if(module instanceof ISquelchStateProvider)
{
((ISquelchStateProvider) module).setSquelchStateListener(mSquelchStateBroadcaster);
@@ -513,14 +518,9 @@ public class ProcessingChain implements IChannelEventListener
((IDecoderStateEventProvider) module).setDecoderStateListener(null);
}
if(module instanceof IFrequencyChangeProvider)
{
((IFrequencyChangeProvider) module).setFrequencyChangeListener(null);
}
if(module instanceof IHeartbeatProvider)
{
((IHeartbeatProvider)module).removeHeartbeatListener();
((IHeartbeatProvider)module).getHeartbeatManager().removeHeartbeatListener(mHeartbeatBroadcaster);
}
if(module instanceof IMessageProvider)
@@ -533,6 +533,16 @@ public class ProcessingChain implements IChannelEventListener
((IFilteredRealBufferProvider) module).setFilteredRealBufferListener(null);
}
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(null);
}
if(module instanceof ISourceEventProvider)
{
((ISourceEventProvider) module).setSourceEventListener(null);
}
if(module instanceof ISquelchStateProvider)
{
((ISquelchStateProvider) module).setSquelchStateListener(null);
@@ -584,7 +594,7 @@ public class ProcessingChain implements IChannelEventListener
{
try
{
module.start(ThreadPool.SCHEDULED);
module.start();
}
catch(Exception e)
{
@@ -600,8 +610,7 @@ public class ProcessingChain implements IChannelEventListener
}
/**
* Stops processing if the chain is currently processing. Invocations on
* an already stopped chain have no effect.
* Stops processing if the chain is currently processing. Invocations on an already stopped chain have no effect.
*/
public void stop()
{
@@ -628,15 +637,14 @@ public class ProcessingChain implements IChannelEventListener
((RealSource) mSource).removeListener(mFilteredRealBufferBroadcaster);
break;
default:
throw new IllegalArgumentException("Unrecognized source "
+ "sample type - cannot start processing chain");
throw new IllegalArgumentException("Unrecognized source sample type - cannot start processing " +
"chain");
}
/* Release the source */
mSource.dispose();
mSource = null;
}
}
}
@@ -767,18 +775,18 @@ public class ProcessingChain implements IChannelEventListener
* Adds the listener to receive frequency change events from the processing chain
* @param listener to receive events
*/
public void addFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
public void addFrequencyChangeListener(Listener<SourceEvent> listener)
{
mFrequencyChangeEventBroadcaster.addListener(listener);
mSourceEventBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving frequency change events
* @param listener to remove
*/
public void removeFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
public void removeFrequencyChangeListener(Listener<SourceEvent> listener)
{
mFrequencyChangeEventBroadcaster.removeListener(listener);
mSourceEventBroadcaster.removeListener(listener);
}
/**

View File

@@ -22,8 +22,7 @@ import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
public abstract class Decoder extends Module
implements IMessageProvider, Listener<Message>
public abstract class Decoder extends Module implements IMessageProvider, Listener<Message>
{
/* This has to be a broadcaster in order for references to persist */
protected Listener<Message> mMessageListener;
@@ -82,4 +81,19 @@ public abstract class Decoder extends Module
{
broadcast( message );
}
@Override
public void reset()
{
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
}

View File

@@ -66,6 +66,7 @@ import io.github.dsheirer.module.decode.mdc1200.MDCDecoderState;
import io.github.dsheirer.module.decode.mdc1200.MDCMessageFilter;
import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
import io.github.dsheirer.module.decode.mpt1327.MPT1327Decoder;
import io.github.dsheirer.module.decode.mpt1327.MPT1327Decoder.Sync;
import io.github.dsheirer.module.decode.mpt1327.MPT1327DecoderEditor;
import io.github.dsheirer.module.decode.mpt1327.MPT1327DecoderState;
import io.github.dsheirer.module.decode.mpt1327.MPT1327MessageFilter;
@@ -73,7 +74,7 @@ import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.decode.nbfm.NBFMDecoder;
import io.github.dsheirer.module.decode.nbfm.NBFMDecoderEditor;
import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.P25Decoder;
import io.github.dsheirer.module.decode.p25.P25Decoder.Modulation;
import io.github.dsheirer.module.decode.p25.P25DecoderEditor;
import io.github.dsheirer.module.decode.p25.P25DecoderState;
import io.github.dsheirer.module.decode.p25.P25_C4FMDecoder;
@@ -145,7 +146,7 @@ public class DecoderFactory
Metadata metadata)
{
/* Get the optional alias list for the decode modules to use */
/* Get the optional alias list for the decode modules to use */
AliasList aliasList = aliasModel.getAliasList(channel.getAliasListName());
List<Module> modules = getPrimaryModules(channelModel, channelMapModel, channelProcessingManager, aliasList,
@@ -170,7 +171,7 @@ public class DecoderFactory
ChannelType channelType = channel.getChannelType();
/* Baseband low-pass filter pass and stop frequencies */
/* Baseband low-pass filter pass and stop frequencies */
DecodeConfiguration decodeConfig = channel.getDecodeConfiguration();
int iqPass = decodeConfig.getDecoderType().getChannelBandwidth() / 2;
@@ -212,7 +213,7 @@ public class DecoderFactory
ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName());
MPT1327Decoder.Sync sync = mptConfig.getSync();
Sync sync = mptConfig.getSync();
modules.add(new MPT1327Decoder(aliasList, sync));
@@ -221,7 +222,7 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD)
{
modules.add(new TrafficChannelManager(channelModel, decodeConfig, channel.getEventLogConfiguration(),
modules.add(new TrafficChannelManager(channelModel, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize()));
}
@@ -240,7 +241,7 @@ public class DecoderFactory
case P25_PHASE1:
DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1) decodeConfig;
P25Decoder.Modulation modulation = p25Config.getModulation();
Modulation modulation = p25Config.getModulation();
switch(modulation)
@@ -249,12 +250,12 @@ public class DecoderFactory
modules.add(new FMDemodulatorModule(P25_C4FM_IQ_FILTER));
modules.add(new DemodulatedAudioFilterModule(P25_C4FM_DEMOD_FILTER, 1.0f));
modules.add(new P25_C4FMDecoder(aliasList, decodeConfig.getAFCMaximumCorrection()));
modules.add(new P25DecoderState(aliasList, channelType, P25Decoder.Modulation.C4FM,
modules.add(new P25DecoderState(aliasList, channelType, Modulation.C4FM,
p25Config.getIgnoreDataCalls()));
break;
case CQPSK:
modules.add(new P25_LSMDecoder(aliasList));
modules.add(new P25DecoderState(aliasList, channelType, P25Decoder.Modulation.CQPSK, p25Config.getIgnoreDataCalls()));
modules.add(new P25DecoderState(aliasList, channelType, Modulation.CQPSK, p25Config.getIgnoreDataCalls()));
break;
default:
throw new IllegalArgumentException("Unrecognized P25 Phase 1 Modulation [" + modulation + "]");
@@ -262,7 +263,7 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD)
{
modules.add(new TrafficChannelManager(channelModel, decodeConfig, channel.getEventLogConfiguration(),
modules.add(new TrafficChannelManager(channelModel, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize()));
}
@@ -337,7 +338,7 @@ public class DecoderFactory
}
}
/* If we don't have any filters, add an ALL-PASS filter */
/* If we don't have any filters, add an ALL-PASS filter */
if(filterSet.getFilters().isEmpty())
{
filterSet.addFilter(new AllPassFilter<Message>());
@@ -510,4 +511,4 @@ public class DecoderFactory
return null;
}
}
}

View File

@@ -22,8 +22,6 @@ import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.PrimaryDecoder;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import java.util.concurrent.ScheduledExecutorService;
public class AMDecoder extends PrimaryDecoder
{
public AMDecoder( DecodeConfiguration config )
@@ -42,10 +40,10 @@ public class AMDecoder extends PrimaryDecoder
{
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void start()
{
}
@Override
public void stop()

View File

@@ -38,7 +38,6 @@ import io.github.dsheirer.sample.real.RealBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
/**
* Fleetsync II Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
@@ -173,19 +172,4 @@ public class Fleetsync2Decoder extends Decoder implements IFilteredRealBufferLis
{
return mDecimationFilter;
}
@Override
public void reset()
{
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
}
}

View File

@@ -34,7 +34,6 @@ import io.github.dsheirer.util.StringUtils;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class Fleetsync2DecoderState extends DecoderState
{
@@ -63,27 +62,12 @@ public class Fleetsync2DecoderState extends DecoderState
return DecoderType.FLEETSYNC2;
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
@Override
public void dispose()
{
super.dispose();
}
@Override
public void init()
{
}
/**
* Resets the overall decoder state and clears any accumulated event details
*/
@@ -96,6 +80,24 @@ public class Fleetsync2DecoderState extends DecoderState
resetState();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
/**
* Resets this decoder state
*/

View File

@@ -41,7 +41,6 @@ import io.github.dsheirer.sample.real.RealSampleListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
/**
* LJ1200 - 1200 baud 2FSK decoder
@@ -191,19 +190,4 @@ public class LJ1200Decoder extends Decoder implements IFilteredRealBufferListene
{
return mDecimationFilter;
}
@Override
public void reset()
{
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
}
}

View File

@@ -36,7 +36,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class LJ1200DecoderState extends DecoderState
{
@@ -60,16 +59,6 @@ public class LJ1200DecoderState extends DecoderState
return DecoderType.LJ_1200;
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
@Override
public void receive(Message message)
{
@@ -122,12 +111,6 @@ public class LJ1200DecoderState extends DecoderState
return sb.toString();
}
@Override
public void init()
{
/* No initialization steps */
}
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{
@@ -148,4 +131,22 @@ public class LJ1200DecoderState extends DecoderState
mAddresses.clear();
resetState();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
}

View File

@@ -33,7 +33,6 @@ import io.github.dsheirer.sample.real.RealBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
public class LTRNetDecoder extends Decoder
implements IUnFilteredRealBufferListener, Instrumentable
@@ -119,14 +118,4 @@ public class LTRNetDecoder extends Decoder
{
mLTRMessageFramer.reset();
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
}
}

View File

@@ -39,7 +39,6 @@ import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class LTRNetDecoderState extends DecoderState
{
@@ -695,6 +694,24 @@ public class LTRNetDecoderState extends DecoderState
resetState();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
/**
* Resets the decoder state after a call or other decode event
*/
@@ -764,12 +781,6 @@ public class LTRNetDecoderState extends DecoderState
}
}
@Override
public void init()
{
/* No initialization steps required */
}
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{
@@ -785,14 +796,4 @@ public class LTRNetDecoderState extends DecoderState
break;
}
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
}

View File

@@ -28,8 +28,6 @@ import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener;
import io.github.dsheirer.sample.real.RealBuffer;
import java.util.concurrent.ScheduledExecutorService;
public class LTRStandardDecoder extends Decoder implements IUnFilteredRealBufferListener
{
public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
@@ -84,14 +82,4 @@ public class LTRStandardDecoder extends Decoder implements IUnFilteredRealBuffer
{
mLTRMessageFramer.reset();
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
}
}

View File

@@ -46,7 +46,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class LTRStandardDecoderState extends DecoderState
{
@@ -75,18 +74,6 @@ public class LTRStandardDecoderState extends DecoderState
return DecoderType.LTR_STANDARD;
}
@Override
public void start(ScheduledExecutorService executor)
{
// TODO Auto-generated method stub
}
@Override
public void stop()
{
}
@Override
public void receive(Message message)
{
@@ -211,6 +198,24 @@ public class LTRStandardDecoderState extends DecoderState
resetState();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
/**
* Performs a temporal reset following a call or other decode event
*/
@@ -256,11 +261,6 @@ public class LTRStandardDecoderState extends DecoderState
}
}
@Override
public void init()
{
}
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{

View File

@@ -40,7 +40,6 @@ import io.github.dsheirer.sample.real.RealBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
/**
* MDC1200 Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
@@ -201,25 +200,4 @@ public class MDCDecoder extends Decoder implements IFilteredRealBufferListener,
{
return mDecimationFilter;
}
@Override
public void reset()
{
// TODO Auto-generated method stub
}
@Override
public void start( ScheduledExecutorService executor )
{
// TODO Auto-generated method stub
}
@Override
public void stop()
{
// TODO Auto-generated method stub
}
}

View File

@@ -34,7 +34,6 @@ import io.github.dsheirer.util.StringUtils;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class MDCDecoderState extends DecoderState
{
@@ -59,16 +58,6 @@ public class MDCDecoderState extends DecoderState
return DecoderType.MDC1200;
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
@Override
public void reset()
{
@@ -78,6 +67,24 @@ public class MDCDecoderState extends DecoderState
resetState();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
private void resetState()
{
mFromAttribute.reset();
@@ -245,12 +252,6 @@ public class MDCDecoderState extends DecoderState
return sb.toString();
}
@Override
public void init()
{
/* No initialization required */
}
/**
* Responds to reset events issued by the channel state
*/

View File

@@ -44,7 +44,6 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
/**
* MPT1327 Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
@@ -296,16 +295,4 @@ public class MPT1327Decoder extends Decoder implements IFilteredRealBufferListen
mControlMessageFramer.reset();
mTrafficMessageFramer.reset();
}
@Override
public void start(ScheduledExecutorService executor)
{
// TODO Auto-generated method stub
}
@Override
public void stop()
{
// TODO Auto-generated method stub
}
}

View File

@@ -46,7 +46,6 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class MPT1327DecoderState extends DecoderState
{
@@ -85,16 +84,6 @@ public class MPT1327DecoderState extends DecoderState
return DecoderType.MPT1327;
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
public ChannelType getChannelType()
{
return mChannelType;
@@ -300,6 +289,24 @@ public class MPT1327DecoderState extends DecoderState
resetState();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
private void resetState()
{
mFromAttribute.reset();
@@ -499,12 +506,6 @@ public class MPT1327DecoderState extends DecoderState
return sb.toString();
}
@Override
public void init()
{
/* No initialization required */
}
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{

View File

@@ -22,8 +22,6 @@ import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.PrimaryDecoder;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import java.util.concurrent.ScheduledExecutorService;
public class NBFMDecoder extends PrimaryDecoder
{
public NBFMDecoder( DecodeConfiguration config )
@@ -43,7 +41,7 @@ public class NBFMDecoder extends PrimaryDecoder
}
@Override
public void start( ScheduledExecutorService executor )
public void start()
{
}

View File

@@ -10,499 +10,499 @@ import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
import io.github.dsheirer.sample.real.RealSampleProvider;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import java.util.ArrayList;
import java.util.List;
public class C4FMSymbolFilter implements Listener<RealBuffer>, IFrequencyChangeListener, RealSampleProvider, Instrumentable
public class C4FMSymbolFilter implements Listener<RealBuffer>, ISourceEventListener, RealSampleProvider, Instrumentable
{
private static final float TAPS[][] =
{
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
{ -3.09412e-04f, 1.70888e-03f, -5.55134e-03f, 1.58840e-02f, 9.96891e-01f, -1.07209e-02f, 2.47942e-03f, -3.96391e-04f }, // 2/128
{ -4.64053e-04f, 2.56486e-03f, -8.34364e-03f, 2.39714e-02f, 9.95074e-01f, -1.59305e-02f, 3.69852e-03f, -5.92100e-04f }, // 3/128
{ -6.18544e-04f, 3.42130e-03f, -1.11453e-02f, 3.21531e-02f, 9.93082e-01f, -2.10389e-02f, 4.90322e-03f, -7.86031e-04f }, // 4/128
{ -7.72802e-04f, 4.27773e-03f, -1.39548e-02f, 4.04274e-02f, 9.90917e-01f, -2.60456e-02f, 6.09305e-03f, -9.78093e-04f }, // 5/128
{ -9.26747e-04f, 5.13372e-03f, -1.67710e-02f, 4.87921e-02f, 9.88580e-01f, -3.09503e-02f, 7.26755e-03f, -1.16820e-03f }, // 6/128
{ -1.08030e-03f, 5.98883e-03f, -1.95925e-02f, 5.72454e-02f, 9.86071e-01f, -3.57525e-02f, 8.42626e-03f, -1.35627e-03f }, // 7/128
{ -1.23337e-03f, 6.84261e-03f, -2.24178e-02f, 6.57852e-02f, 9.83392e-01f, -4.04519e-02f, 9.56876e-03f, -1.54221e-03f }, // 8/128
{ -1.38589e-03f, 7.69462e-03f, -2.52457e-02f, 7.44095e-02f, 9.80543e-01f, -4.50483e-02f, 1.06946e-02f, -1.72594e-03f }, // 9/128
{ -1.53777e-03f, 8.54441e-03f, -2.80746e-02f, 8.31162e-02f, 9.77526e-01f, -4.95412e-02f, 1.18034e-02f, -1.90738e-03f }, // 10/128
{ -1.68894e-03f, 9.39154e-03f, -3.09033e-02f, 9.19033e-02f, 9.74342e-01f, -5.39305e-02f, 1.28947e-02f, -2.08645e-03f }, // 11/128
{ -1.83931e-03f, 1.02356e-02f, -3.37303e-02f, 1.00769e-01f, 9.70992e-01f, -5.82159e-02f, 1.39681e-02f, -2.26307e-03f }, // 12/128
{ -1.98880e-03f, 1.10760e-02f, -3.65541e-02f, 1.09710e-01f, 9.67477e-01f, -6.23972e-02f, 1.50233e-02f, -2.43718e-03f }, // 13/128
{ -2.13733e-03f, 1.19125e-02f, -3.93735e-02f, 1.18725e-01f, 9.63798e-01f, -6.64743e-02f, 1.60599e-02f, -2.60868e-03f }, // 14/128
{ -2.28483e-03f, 1.27445e-02f, -4.21869e-02f, 1.27812e-01f, 9.59958e-01f, -7.04471e-02f, 1.70776e-02f, -2.77751e-03f }, // 15/128
{ -2.43121e-03f, 1.35716e-02f, -4.49929e-02f, 1.36968e-01f, 9.55956e-01f, -7.43154e-02f, 1.80759e-02f, -2.94361e-03f }, // 16/128
{ -2.57640e-03f, 1.43934e-02f, -4.77900e-02f, 1.46192e-01f, 9.51795e-01f, -7.80792e-02f, 1.90545e-02f, -3.10689e-03f }, // 17/128
{ -2.72032e-03f, 1.52095e-02f, -5.05770e-02f, 1.55480e-01f, 9.47477e-01f, -8.17385e-02f, 2.00132e-02f, -3.26730e-03f }, // 18/128
{ -2.86289e-03f, 1.60193e-02f, -5.33522e-02f, 1.64831e-01f, 9.43001e-01f, -8.52933e-02f, 2.09516e-02f, -3.42477e-03f }, // 19/128
{ -3.00403e-03f, 1.68225e-02f, -5.61142e-02f, 1.74242e-01f, 9.38371e-01f, -8.87435e-02f, 2.18695e-02f, -3.57923e-03f }, // 20/128
{ -3.14367e-03f, 1.76185e-02f, -5.88617e-02f, 1.83711e-01f, 9.33586e-01f, -9.20893e-02f, 2.27664e-02f, -3.73062e-03f }, // 21/128
{ -3.28174e-03f, 1.84071e-02f, -6.15931e-02f, 1.93236e-01f, 9.28650e-01f, -9.53307e-02f, 2.36423e-02f, -3.87888e-03f }, // 22/128
{ -3.41815e-03f, 1.91877e-02f, -6.43069e-02f, 2.02814e-01f, 9.23564e-01f, -9.84679e-02f, 2.44967e-02f, -4.02397e-03f }, // 23/128
{ -3.55283e-03f, 1.99599e-02f, -6.70018e-02f, 2.12443e-01f, 9.18329e-01f, -1.01501e-01f, 2.53295e-02f, -4.16581e-03f }, // 24/128
{ -3.68570e-03f, 2.07233e-02f, -6.96762e-02f, 2.22120e-01f, 9.12947e-01f, -1.04430e-01f, 2.61404e-02f, -4.30435e-03f }, // 25/128
{ -3.81671e-03f, 2.14774e-02f, -7.23286e-02f, 2.31843e-01f, 9.07420e-01f, -1.07256e-01f, 2.69293e-02f, -4.43955e-03f }, // 26/128
{ -3.94576e-03f, 2.22218e-02f, -7.49577e-02f, 2.41609e-01f, 9.01749e-01f, -1.09978e-01f, 2.76957e-02f, -4.57135e-03f }, // 27/128
{ -4.07279e-03f, 2.29562e-02f, -7.75620e-02f, 2.51417e-01f, 8.95936e-01f, -1.12597e-01f, 2.84397e-02f, -4.69970e-03f }, // 28/128
{ -4.19774e-03f, 2.36801e-02f, -8.01399e-02f, 2.61263e-01f, 8.89984e-01f, -1.15113e-01f, 2.91609e-02f, -4.82456e-03f }, // 29/128
{ -4.32052e-03f, 2.43930e-02f, -8.26900e-02f, 2.71144e-01f, 8.83893e-01f, -1.17526e-01f, 2.98593e-02f, -4.94589e-03f }, // 30/128
{ -4.44107e-03f, 2.50946e-02f, -8.52109e-02f, 2.81060e-01f, 8.77666e-01f, -1.19837e-01f, 3.05345e-02f, -5.06363e-03f }, // 31/128
{ -4.55932e-03f, 2.57844e-02f, -8.77011e-02f, 2.91006e-01f, 8.71305e-01f, -1.22047e-01f, 3.11866e-02f, -5.17776e-03f }, // 32/128
{ -4.67520e-03f, 2.64621e-02f, -9.01591e-02f, 3.00980e-01f, 8.64812e-01f, -1.24154e-01f, 3.18153e-02f, -5.28823e-03f }, // 33/128
{ -4.78866e-03f, 2.71272e-02f, -9.25834e-02f, 3.10980e-01f, 8.58189e-01f, -1.26161e-01f, 3.24205e-02f, -5.39500e-03f }, // 34/128
{ -4.89961e-03f, 2.77794e-02f, -9.49727e-02f, 3.21004e-01f, 8.51437e-01f, -1.28068e-01f, 3.30021e-02f, -5.49804e-03f }, // 35/128
{ -5.00800e-03f, 2.84182e-02f, -9.73254e-02f, 3.31048e-01f, 8.44559e-01f, -1.29874e-01f, 3.35600e-02f, -5.59731e-03f }, // 36/128
{ -5.11376e-03f, 2.90433e-02f, -9.96402e-02f, 3.41109e-01f, 8.37557e-01f, -1.31581e-01f, 3.40940e-02f, -5.69280e-03f }, // 37/128
{ -5.21683e-03f, 2.96543e-02f, -1.01915e-01f, 3.51186e-01f, 8.30432e-01f, -1.33189e-01f, 3.46042e-02f, -5.78446e-03f }, // 38/128
{ -5.31716e-03f, 3.02507e-02f, -1.04150e-01f, 3.61276e-01f, 8.23188e-01f, -1.34699e-01f, 3.50903e-02f, -5.87227e-03f }, // 39/128
{ -5.41467e-03f, 3.08323e-02f, -1.06342e-01f, 3.71376e-01f, 8.15826e-01f, -1.36111e-01f, 3.55525e-02f, -5.95620e-03f }, // 40/128
{ -5.50931e-03f, 3.13987e-02f, -1.08490e-01f, 3.81484e-01f, 8.08348e-01f, -1.37426e-01f, 3.59905e-02f, -6.03624e-03f }, // 41/128
{ -5.60103e-03f, 3.19495e-02f, -1.10593e-01f, 3.91596e-01f, 8.00757e-01f, -1.38644e-01f, 3.64044e-02f, -6.11236e-03f }, // 42/128
{ -5.68976e-03f, 3.24843e-02f, -1.12650e-01f, 4.01710e-01f, 7.93055e-01f, -1.39767e-01f, 3.67941e-02f, -6.18454e-03f }, // 43/128
{ -5.77544e-03f, 3.30027e-02f, -1.14659e-01f, 4.11823e-01f, 7.85244e-01f, -1.40794e-01f, 3.71596e-02f, -6.25277e-03f }, // 44/128
{ -5.85804e-03f, 3.35046e-02f, -1.16618e-01f, 4.21934e-01f, 7.77327e-01f, -1.41727e-01f, 3.75010e-02f, -6.31703e-03f }, // 45/128
{ -5.93749e-03f, 3.39894e-02f, -1.18526e-01f, 4.32038e-01f, 7.69305e-01f, -1.42566e-01f, 3.78182e-02f, -6.37730e-03f }, // 46/128
{ -6.01374e-03f, 3.44568e-02f, -1.20382e-01f, 4.42134e-01f, 7.61181e-01f, -1.43313e-01f, 3.81111e-02f, -6.43358e-03f }, // 47/128
{ -6.08674e-03f, 3.49066e-02f, -1.22185e-01f, 4.52218e-01f, 7.52958e-01f, -1.43968e-01f, 3.83800e-02f, -6.48585e-03f }, // 48/128
{ -6.15644e-03f, 3.53384e-02f, -1.23933e-01f, 4.62289e-01f, 7.44637e-01f, -1.44531e-01f, 3.86247e-02f, -6.53412e-03f }, // 49/128
{ -6.22280e-03f, 3.57519e-02f, -1.25624e-01f, 4.72342e-01f, 7.36222e-01f, -1.45004e-01f, 3.88454e-02f, -6.57836e-03f }, // 50/128
{ -6.28577e-03f, 3.61468e-02f, -1.27258e-01f, 4.82377e-01f, 7.27714e-01f, -1.45387e-01f, 3.90420e-02f, -6.61859e-03f }, // 51/128
{ -6.34530e-03f, 3.65227e-02f, -1.28832e-01f, 4.92389e-01f, 7.19116e-01f, -1.45682e-01f, 3.92147e-02f, -6.65479e-03f }, // 52/128
{ -6.40135e-03f, 3.68795e-02f, -1.30347e-01f, 5.02377e-01f, 7.10431e-01f, -1.45889e-01f, 3.93636e-02f, -6.68698e-03f }, // 53/128
{ -6.45388e-03f, 3.72167e-02f, -1.31800e-01f, 5.12337e-01f, 7.01661e-01f, -1.46009e-01f, 3.94886e-02f, -6.71514e-03f }, // 54/128
{ -6.50285e-03f, 3.75341e-02f, -1.33190e-01f, 5.22267e-01f, 6.92808e-01f, -1.46043e-01f, 3.95900e-02f, -6.73929e-03f }, // 55/128
{ -6.54823e-03f, 3.78315e-02f, -1.34515e-01f, 5.32164e-01f, 6.83875e-01f, -1.45993e-01f, 3.96678e-02f, -6.75943e-03f }, // 56/128
{ -6.58996e-03f, 3.81085e-02f, -1.35775e-01f, 5.42025e-01f, 6.74865e-01f, -1.45859e-01f, 3.97222e-02f, -6.77557e-03f }, // 57/128
{ -6.62802e-03f, 3.83650e-02f, -1.36969e-01f, 5.51849e-01f, 6.65779e-01f, -1.45641e-01f, 3.97532e-02f, -6.78771e-03f }, // 58/128
{ -6.66238e-03f, 3.86006e-02f, -1.38094e-01f, 5.61631e-01f, 6.56621e-01f, -1.45343e-01f, 3.97610e-02f, -6.79588e-03f }, // 59/128
{ -6.69300e-03f, 3.88151e-02f, -1.39150e-01f, 5.71370e-01f, 6.47394e-01f, -1.44963e-01f, 3.97458e-02f, -6.80007e-03f }, // 60/128
{ -6.71985e-03f, 3.90083e-02f, -1.40136e-01f, 5.81063e-01f, 6.38099e-01f, -1.44503e-01f, 3.97077e-02f, -6.80032e-03f }, // 61/128
{ -6.74291e-03f, 3.91800e-02f, -1.41050e-01f, 5.90706e-01f, 6.28739e-01f, -1.43965e-01f, 3.96469e-02f, -6.79662e-03f }, // 62/128
{ -6.76214e-03f, 3.93299e-02f, -1.41891e-01f, 6.00298e-01f, 6.19318e-01f, -1.43350e-01f, 3.95635e-02f, -6.78902e-03f }, // 63/128
{ -6.77751e-03f, 3.94578e-02f, -1.42658e-01f, 6.09836e-01f, 6.09836e-01f, -1.42658e-01f, 3.94578e-02f, -6.77751e-03f }, // 64/128
{ -6.78902e-03f, 3.95635e-02f, -1.43350e-01f, 6.19318e-01f, 6.00298e-01f, -1.41891e-01f, 3.93299e-02f, -6.76214e-03f }, // 65/128
{ -6.79662e-03f, 3.96469e-02f, -1.43965e-01f, 6.28739e-01f, 5.90706e-01f, -1.41050e-01f, 3.91800e-02f, -6.74291e-03f }, // 66/128
{ -6.80032e-03f, 3.97077e-02f, -1.44503e-01f, 6.38099e-01f, 5.81063e-01f, -1.40136e-01f, 3.90083e-02f, -6.71985e-03f }, // 67/128
{ -6.80007e-03f, 3.97458e-02f, -1.44963e-01f, 6.47394e-01f, 5.71370e-01f, -1.39150e-01f, 3.88151e-02f, -6.69300e-03f }, // 68/128
{ -6.79588e-03f, 3.97610e-02f, -1.45343e-01f, 6.56621e-01f, 5.61631e-01f, -1.38094e-01f, 3.86006e-02f, -6.66238e-03f }, // 69/128
{ -6.78771e-03f, 3.97532e-02f, -1.45641e-01f, 6.65779e-01f, 5.51849e-01f, -1.36969e-01f, 3.83650e-02f, -6.62802e-03f }, // 70/128
{ -6.77557e-03f, 3.97222e-02f, -1.45859e-01f, 6.74865e-01f, 5.42025e-01f, -1.35775e-01f, 3.81085e-02f, -6.58996e-03f }, // 71/128
{ -6.75943e-03f, 3.96678e-02f, -1.45993e-01f, 6.83875e-01f, 5.32164e-01f, -1.34515e-01f, 3.78315e-02f, -6.54823e-03f }, // 72/128
{ -6.73929e-03f, 3.95900e-02f, -1.46043e-01f, 6.92808e-01f, 5.22267e-01f, -1.33190e-01f, 3.75341e-02f, -6.50285e-03f }, // 73/128
{ -6.71514e-03f, 3.94886e-02f, -1.46009e-01f, 7.01661e-01f, 5.12337e-01f, -1.31800e-01f, 3.72167e-02f, -6.45388e-03f }, // 74/128
{ -6.68698e-03f, 3.93636e-02f, -1.45889e-01f, 7.10431e-01f, 5.02377e-01f, -1.30347e-01f, 3.68795e-02f, -6.40135e-03f }, // 75/128
{ -6.65479e-03f, 3.92147e-02f, -1.45682e-01f, 7.19116e-01f, 4.92389e-01f, -1.28832e-01f, 3.65227e-02f, -6.34530e-03f }, // 76/128
{ -6.61859e-03f, 3.90420e-02f, -1.45387e-01f, 7.27714e-01f, 4.82377e-01f, -1.27258e-01f, 3.61468e-02f, -6.28577e-03f }, // 77/128
{ -6.57836e-03f, 3.88454e-02f, -1.45004e-01f, 7.36222e-01f, 4.72342e-01f, -1.25624e-01f, 3.57519e-02f, -6.22280e-03f }, // 78/128
{ -6.53412e-03f, 3.86247e-02f, -1.44531e-01f, 7.44637e-01f, 4.62289e-01f, -1.23933e-01f, 3.53384e-02f, -6.15644e-03f }, // 79/128
{ -6.48585e-03f, 3.83800e-02f, -1.43968e-01f, 7.52958e-01f, 4.52218e-01f, -1.22185e-01f, 3.49066e-02f, -6.08674e-03f }, // 80/128
{ -6.43358e-03f, 3.81111e-02f, -1.43313e-01f, 7.61181e-01f, 4.42134e-01f, -1.20382e-01f, 3.44568e-02f, -6.01374e-03f }, // 81/128
{ -6.37730e-03f, 3.78182e-02f, -1.42566e-01f, 7.69305e-01f, 4.32038e-01f, -1.18526e-01f, 3.39894e-02f, -5.93749e-03f }, // 82/128
{ -6.31703e-03f, 3.75010e-02f, -1.41727e-01f, 7.77327e-01f, 4.21934e-01f, -1.16618e-01f, 3.35046e-02f, -5.85804e-03f }, // 83/128
{ -6.25277e-03f, 3.71596e-02f, -1.40794e-01f, 7.85244e-01f, 4.11823e-01f, -1.14659e-01f, 3.30027e-02f, -5.77544e-03f }, // 84/128
{ -6.18454e-03f, 3.67941e-02f, -1.39767e-01f, 7.93055e-01f, 4.01710e-01f, -1.12650e-01f, 3.24843e-02f, -5.68976e-03f }, // 85/128
{ -6.11236e-03f, 3.64044e-02f, -1.38644e-01f, 8.00757e-01f, 3.91596e-01f, -1.10593e-01f, 3.19495e-02f, -5.60103e-03f }, // 86/128
{ -6.03624e-03f, 3.59905e-02f, -1.37426e-01f, 8.08348e-01f, 3.81484e-01f, -1.08490e-01f, 3.13987e-02f, -5.50931e-03f }, // 87/128
{ -5.95620e-03f, 3.55525e-02f, -1.36111e-01f, 8.15826e-01f, 3.71376e-01f, -1.06342e-01f, 3.08323e-02f, -5.41467e-03f }, // 88/128
{ -5.87227e-03f, 3.50903e-02f, -1.34699e-01f, 8.23188e-01f, 3.61276e-01f, -1.04150e-01f, 3.02507e-02f, -5.31716e-03f }, // 89/128
{ -5.78446e-03f, 3.46042e-02f, -1.33189e-01f, 8.30432e-01f, 3.51186e-01f, -1.01915e-01f, 2.96543e-02f, -5.21683e-03f }, // 90/128
{ -5.69280e-03f, 3.40940e-02f, -1.31581e-01f, 8.37557e-01f, 3.41109e-01f, -9.96402e-02f, 2.90433e-02f, -5.11376e-03f }, // 91/128
{ -5.59731e-03f, 3.35600e-02f, -1.29874e-01f, 8.44559e-01f, 3.31048e-01f, -9.73254e-02f, 2.84182e-02f, -5.00800e-03f }, // 92/128
{ -5.49804e-03f, 3.30021e-02f, -1.28068e-01f, 8.51437e-01f, 3.21004e-01f, -9.49727e-02f, 2.77794e-02f, -4.89961e-03f }, // 93/128
{ -5.39500e-03f, 3.24205e-02f, -1.26161e-01f, 8.58189e-01f, 3.10980e-01f, -9.25834e-02f, 2.71272e-02f, -4.78866e-03f }, // 94/128
{ -5.28823e-03f, 3.18153e-02f, -1.24154e-01f, 8.64812e-01f, 3.00980e-01f, -9.01591e-02f, 2.64621e-02f, -4.67520e-03f }, // 95/128
{ -5.17776e-03f, 3.11866e-02f, -1.22047e-01f, 8.71305e-01f, 2.91006e-01f, -8.77011e-02f, 2.57844e-02f, -4.55932e-03f }, // 96/128
{ -5.06363e-03f, 3.05345e-02f, -1.19837e-01f, 8.77666e-01f, 2.81060e-01f, -8.52109e-02f, 2.50946e-02f, -4.44107e-03f }, // 97/128
{ -4.94589e-03f, 2.98593e-02f, -1.17526e-01f, 8.83893e-01f, 2.71144e-01f, -8.26900e-02f, 2.43930e-02f, -4.32052e-03f }, // 98/128
{ -4.82456e-03f, 2.91609e-02f, -1.15113e-01f, 8.89984e-01f, 2.61263e-01f, -8.01399e-02f, 2.36801e-02f, -4.19774e-03f }, // 99/128
{ -4.69970e-03f, 2.84397e-02f, -1.12597e-01f, 8.95936e-01f, 2.51417e-01f, -7.75620e-02f, 2.29562e-02f, -4.07279e-03f }, // 100/128
{ -4.57135e-03f, 2.76957e-02f, -1.09978e-01f, 9.01749e-01f, 2.41609e-01f, -7.49577e-02f, 2.22218e-02f, -3.94576e-03f }, // 101/128
{ -4.43955e-03f, 2.69293e-02f, -1.07256e-01f, 9.07420e-01f, 2.31843e-01f, -7.23286e-02f, 2.14774e-02f, -3.81671e-03f }, // 102/128
{ -4.30435e-03f, 2.61404e-02f, -1.04430e-01f, 9.12947e-01f, 2.22120e-01f, -6.96762e-02f, 2.07233e-02f, -3.68570e-03f }, // 103/128
{ -4.16581e-03f, 2.53295e-02f, -1.01501e-01f, 9.18329e-01f, 2.12443e-01f, -6.70018e-02f, 1.99599e-02f, -3.55283e-03f }, // 104/128
{ -4.02397e-03f, 2.44967e-02f, -9.84679e-02f, 9.23564e-01f, 2.02814e-01f, -6.43069e-02f, 1.91877e-02f, -3.41815e-03f }, // 105/128
{ -3.87888e-03f, 2.36423e-02f, -9.53307e-02f, 9.28650e-01f, 1.93236e-01f, -6.15931e-02f, 1.84071e-02f, -3.28174e-03f }, // 106/128
{ -3.73062e-03f, 2.27664e-02f, -9.20893e-02f, 9.33586e-01f, 1.83711e-01f, -5.88617e-02f, 1.76185e-02f, -3.14367e-03f }, // 107/128
{ -3.57923e-03f, 2.18695e-02f, -8.87435e-02f, 9.38371e-01f, 1.74242e-01f, -5.61142e-02f, 1.68225e-02f, -3.00403e-03f }, // 108/128
{ -3.42477e-03f, 2.09516e-02f, -8.52933e-02f, 9.43001e-01f, 1.64831e-01f, -5.33522e-02f, 1.60193e-02f, -2.86289e-03f }, // 109/128
{ -3.26730e-03f, 2.00132e-02f, -8.17385e-02f, 9.47477e-01f, 1.55480e-01f, -5.05770e-02f, 1.52095e-02f, -2.72032e-03f }, // 110/128
{ -3.10689e-03f, 1.90545e-02f, -7.80792e-02f, 9.51795e-01f, 1.46192e-01f, -4.77900e-02f, 1.43934e-02f, -2.57640e-03f }, // 111/128
{ -2.94361e-03f, 1.80759e-02f, -7.43154e-02f, 9.55956e-01f, 1.36968e-01f, -4.49929e-02f, 1.35716e-02f, -2.43121e-03f }, // 112/128
{ -2.77751e-03f, 1.70776e-02f, -7.04471e-02f, 9.59958e-01f, 1.27812e-01f, -4.21869e-02f, 1.27445e-02f, -2.28483e-03f }, // 113/128
{ -2.60868e-03f, 1.60599e-02f, -6.64743e-02f, 9.63798e-01f, 1.18725e-01f, -3.93735e-02f, 1.19125e-02f, -2.13733e-03f }, // 114/128
{ -2.43718e-03f, 1.50233e-02f, -6.23972e-02f, 9.67477e-01f, 1.09710e-01f, -3.65541e-02f, 1.10760e-02f, -1.98880e-03f }, // 115/128
{ -2.26307e-03f, 1.39681e-02f, -5.82159e-02f, 9.70992e-01f, 1.00769e-01f, -3.37303e-02f, 1.02356e-02f, -1.83931e-03f }, // 116/128
{ -2.08645e-03f, 1.28947e-02f, -5.39305e-02f, 9.74342e-01f, 9.19033e-02f, -3.09033e-02f, 9.39154e-03f, -1.68894e-03f }, // 117/128
{ -1.90738e-03f, 1.18034e-02f, -4.95412e-02f, 9.77526e-01f, 8.31162e-02f, -2.80746e-02f, 8.54441e-03f, -1.53777e-03f }, // 118/128
{ -1.72594e-03f, 1.06946e-02f, -4.50483e-02f, 9.80543e-01f, 7.44095e-02f, -2.52457e-02f, 7.69462e-03f, -1.38589e-03f }, // 119/128
{ -1.54221e-03f, 9.56876e-03f, -4.04519e-02f, 9.83392e-01f, 6.57852e-02f, -2.24178e-02f, 6.84261e-03f, -1.23337e-03f }, // 120/128
{ -1.35627e-03f, 8.42626e-03f, -3.57525e-02f, 9.86071e-01f, 5.72454e-02f, -1.95925e-02f, 5.98883e-03f, -1.08030e-03f }, // 121/128
{ -1.16820e-03f, 7.26755e-03f, -3.09503e-02f, 9.88580e-01f, 4.87921e-02f, -1.67710e-02f, 5.13372e-03f, -9.26747e-04f }, // 122/128
{ -9.78093e-04f, 6.09305e-03f, -2.60456e-02f, 9.90917e-01f, 4.04274e-02f, -1.39548e-02f, 4.27773e-03f, -7.72802e-04f }, // 123/128
{ -7.86031e-04f, 4.90322e-03f, -2.10389e-02f, 9.93082e-01f, 3.21531e-02f, -1.11453e-02f, 3.42130e-03f, -6.18544e-04f }, // 124/128
{ -5.92100e-04f, 3.69852e-03f, -1.59305e-02f, 9.95074e-01f, 2.39714e-02f, -8.34364e-03f, 2.56486e-03f, -4.64053e-04f }, // 125/128
{ -3.96391e-04f, 2.47942e-03f, -1.07209e-02f, 9.96891e-01f, 1.58840e-02f, -5.55134e-03f, 1.70888e-03f, -3.09412e-04f }, // 126/128
{ -1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f }, // 127/128
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 128/128
};
private static final float TAPS[][] =
{
{0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f}, // 0/128
{-1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f}, // 1/128
{-3.09412e-04f, 1.70888e-03f, -5.55134e-03f, 1.58840e-02f, 9.96891e-01f, -1.07209e-02f, 2.47942e-03f, -3.96391e-04f}, // 2/128
{-4.64053e-04f, 2.56486e-03f, -8.34364e-03f, 2.39714e-02f, 9.95074e-01f, -1.59305e-02f, 3.69852e-03f, -5.92100e-04f}, // 3/128
{-6.18544e-04f, 3.42130e-03f, -1.11453e-02f, 3.21531e-02f, 9.93082e-01f, -2.10389e-02f, 4.90322e-03f, -7.86031e-04f}, // 4/128
{-7.72802e-04f, 4.27773e-03f, -1.39548e-02f, 4.04274e-02f, 9.90917e-01f, -2.60456e-02f, 6.09305e-03f, -9.78093e-04f}, // 5/128
{-9.26747e-04f, 5.13372e-03f, -1.67710e-02f, 4.87921e-02f, 9.88580e-01f, -3.09503e-02f, 7.26755e-03f, -1.16820e-03f}, // 6/128
{-1.08030e-03f, 5.98883e-03f, -1.95925e-02f, 5.72454e-02f, 9.86071e-01f, -3.57525e-02f, 8.42626e-03f, -1.35627e-03f}, // 7/128
{-1.23337e-03f, 6.84261e-03f, -2.24178e-02f, 6.57852e-02f, 9.83392e-01f, -4.04519e-02f, 9.56876e-03f, -1.54221e-03f}, // 8/128
{-1.38589e-03f, 7.69462e-03f, -2.52457e-02f, 7.44095e-02f, 9.80543e-01f, -4.50483e-02f, 1.06946e-02f, -1.72594e-03f}, // 9/128
{-1.53777e-03f, 8.54441e-03f, -2.80746e-02f, 8.31162e-02f, 9.77526e-01f, -4.95412e-02f, 1.18034e-02f, -1.90738e-03f}, // 10/128
{-1.68894e-03f, 9.39154e-03f, -3.09033e-02f, 9.19033e-02f, 9.74342e-01f, -5.39305e-02f, 1.28947e-02f, -2.08645e-03f}, // 11/128
{-1.83931e-03f, 1.02356e-02f, -3.37303e-02f, 1.00769e-01f, 9.70992e-01f, -5.82159e-02f, 1.39681e-02f, -2.26307e-03f}, // 12/128
{-1.98880e-03f, 1.10760e-02f, -3.65541e-02f, 1.09710e-01f, 9.67477e-01f, -6.23972e-02f, 1.50233e-02f, -2.43718e-03f}, // 13/128
{-2.13733e-03f, 1.19125e-02f, -3.93735e-02f, 1.18725e-01f, 9.63798e-01f, -6.64743e-02f, 1.60599e-02f, -2.60868e-03f}, // 14/128
{-2.28483e-03f, 1.27445e-02f, -4.21869e-02f, 1.27812e-01f, 9.59958e-01f, -7.04471e-02f, 1.70776e-02f, -2.77751e-03f}, // 15/128
{-2.43121e-03f, 1.35716e-02f, -4.49929e-02f, 1.36968e-01f, 9.55956e-01f, -7.43154e-02f, 1.80759e-02f, -2.94361e-03f}, // 16/128
{-2.57640e-03f, 1.43934e-02f, -4.77900e-02f, 1.46192e-01f, 9.51795e-01f, -7.80792e-02f, 1.90545e-02f, -3.10689e-03f}, // 17/128
{-2.72032e-03f, 1.52095e-02f, -5.05770e-02f, 1.55480e-01f, 9.47477e-01f, -8.17385e-02f, 2.00132e-02f, -3.26730e-03f}, // 18/128
{-2.86289e-03f, 1.60193e-02f, -5.33522e-02f, 1.64831e-01f, 9.43001e-01f, -8.52933e-02f, 2.09516e-02f, -3.42477e-03f}, // 19/128
{-3.00403e-03f, 1.68225e-02f, -5.61142e-02f, 1.74242e-01f, 9.38371e-01f, -8.87435e-02f, 2.18695e-02f, -3.57923e-03f}, // 20/128
{-3.14367e-03f, 1.76185e-02f, -5.88617e-02f, 1.83711e-01f, 9.33586e-01f, -9.20893e-02f, 2.27664e-02f, -3.73062e-03f}, // 21/128
{-3.28174e-03f, 1.84071e-02f, -6.15931e-02f, 1.93236e-01f, 9.28650e-01f, -9.53307e-02f, 2.36423e-02f, -3.87888e-03f}, // 22/128
{-3.41815e-03f, 1.91877e-02f, -6.43069e-02f, 2.02814e-01f, 9.23564e-01f, -9.84679e-02f, 2.44967e-02f, -4.02397e-03f}, // 23/128
{-3.55283e-03f, 1.99599e-02f, -6.70018e-02f, 2.12443e-01f, 9.18329e-01f, -1.01501e-01f, 2.53295e-02f, -4.16581e-03f}, // 24/128
{-3.68570e-03f, 2.07233e-02f, -6.96762e-02f, 2.22120e-01f, 9.12947e-01f, -1.04430e-01f, 2.61404e-02f, -4.30435e-03f}, // 25/128
{-3.81671e-03f, 2.14774e-02f, -7.23286e-02f, 2.31843e-01f, 9.07420e-01f, -1.07256e-01f, 2.69293e-02f, -4.43955e-03f}, // 26/128
{-3.94576e-03f, 2.22218e-02f, -7.49577e-02f, 2.41609e-01f, 9.01749e-01f, -1.09978e-01f, 2.76957e-02f, -4.57135e-03f}, // 27/128
{-4.07279e-03f, 2.29562e-02f, -7.75620e-02f, 2.51417e-01f, 8.95936e-01f, -1.12597e-01f, 2.84397e-02f, -4.69970e-03f}, // 28/128
{-4.19774e-03f, 2.36801e-02f, -8.01399e-02f, 2.61263e-01f, 8.89984e-01f, -1.15113e-01f, 2.91609e-02f, -4.82456e-03f}, // 29/128
{-4.32052e-03f, 2.43930e-02f, -8.26900e-02f, 2.71144e-01f, 8.83893e-01f, -1.17526e-01f, 2.98593e-02f, -4.94589e-03f}, // 30/128
{-4.44107e-03f, 2.50946e-02f, -8.52109e-02f, 2.81060e-01f, 8.77666e-01f, -1.19837e-01f, 3.05345e-02f, -5.06363e-03f}, // 31/128
{-4.55932e-03f, 2.57844e-02f, -8.77011e-02f, 2.91006e-01f, 8.71305e-01f, -1.22047e-01f, 3.11866e-02f, -5.17776e-03f}, // 32/128
{-4.67520e-03f, 2.64621e-02f, -9.01591e-02f, 3.00980e-01f, 8.64812e-01f, -1.24154e-01f, 3.18153e-02f, -5.28823e-03f}, // 33/128
{-4.78866e-03f, 2.71272e-02f, -9.25834e-02f, 3.10980e-01f, 8.58189e-01f, -1.26161e-01f, 3.24205e-02f, -5.39500e-03f}, // 34/128
{-4.89961e-03f, 2.77794e-02f, -9.49727e-02f, 3.21004e-01f, 8.51437e-01f, -1.28068e-01f, 3.30021e-02f, -5.49804e-03f}, // 35/128
{-5.00800e-03f, 2.84182e-02f, -9.73254e-02f, 3.31048e-01f, 8.44559e-01f, -1.29874e-01f, 3.35600e-02f, -5.59731e-03f}, // 36/128
{-5.11376e-03f, 2.90433e-02f, -9.96402e-02f, 3.41109e-01f, 8.37557e-01f, -1.31581e-01f, 3.40940e-02f, -5.69280e-03f}, // 37/128
{-5.21683e-03f, 2.96543e-02f, -1.01915e-01f, 3.51186e-01f, 8.30432e-01f, -1.33189e-01f, 3.46042e-02f, -5.78446e-03f}, // 38/128
{-5.31716e-03f, 3.02507e-02f, -1.04150e-01f, 3.61276e-01f, 8.23188e-01f, -1.34699e-01f, 3.50903e-02f, -5.87227e-03f}, // 39/128
{-5.41467e-03f, 3.08323e-02f, -1.06342e-01f, 3.71376e-01f, 8.15826e-01f, -1.36111e-01f, 3.55525e-02f, -5.95620e-03f}, // 40/128
{-5.50931e-03f, 3.13987e-02f, -1.08490e-01f, 3.81484e-01f, 8.08348e-01f, -1.37426e-01f, 3.59905e-02f, -6.03624e-03f}, // 41/128
{-5.60103e-03f, 3.19495e-02f, -1.10593e-01f, 3.91596e-01f, 8.00757e-01f, -1.38644e-01f, 3.64044e-02f, -6.11236e-03f}, // 42/128
{-5.68976e-03f, 3.24843e-02f, -1.12650e-01f, 4.01710e-01f, 7.93055e-01f, -1.39767e-01f, 3.67941e-02f, -6.18454e-03f}, // 43/128
{-5.77544e-03f, 3.30027e-02f, -1.14659e-01f, 4.11823e-01f, 7.85244e-01f, -1.40794e-01f, 3.71596e-02f, -6.25277e-03f}, // 44/128
{-5.85804e-03f, 3.35046e-02f, -1.16618e-01f, 4.21934e-01f, 7.77327e-01f, -1.41727e-01f, 3.75010e-02f, -6.31703e-03f}, // 45/128
{-5.93749e-03f, 3.39894e-02f, -1.18526e-01f, 4.32038e-01f, 7.69305e-01f, -1.42566e-01f, 3.78182e-02f, -6.37730e-03f}, // 46/128
{-6.01374e-03f, 3.44568e-02f, -1.20382e-01f, 4.42134e-01f, 7.61181e-01f, -1.43313e-01f, 3.81111e-02f, -6.43358e-03f}, // 47/128
{-6.08674e-03f, 3.49066e-02f, -1.22185e-01f, 4.52218e-01f, 7.52958e-01f, -1.43968e-01f, 3.83800e-02f, -6.48585e-03f}, // 48/128
{-6.15644e-03f, 3.53384e-02f, -1.23933e-01f, 4.62289e-01f, 7.44637e-01f, -1.44531e-01f, 3.86247e-02f, -6.53412e-03f}, // 49/128
{-6.22280e-03f, 3.57519e-02f, -1.25624e-01f, 4.72342e-01f, 7.36222e-01f, -1.45004e-01f, 3.88454e-02f, -6.57836e-03f}, // 50/128
{-6.28577e-03f, 3.61468e-02f, -1.27258e-01f, 4.82377e-01f, 7.27714e-01f, -1.45387e-01f, 3.90420e-02f, -6.61859e-03f}, // 51/128
{-6.34530e-03f, 3.65227e-02f, -1.28832e-01f, 4.92389e-01f, 7.19116e-01f, -1.45682e-01f, 3.92147e-02f, -6.65479e-03f}, // 52/128
{-6.40135e-03f, 3.68795e-02f, -1.30347e-01f, 5.02377e-01f, 7.10431e-01f, -1.45889e-01f, 3.93636e-02f, -6.68698e-03f}, // 53/128
{-6.45388e-03f, 3.72167e-02f, -1.31800e-01f, 5.12337e-01f, 7.01661e-01f, -1.46009e-01f, 3.94886e-02f, -6.71514e-03f}, // 54/128
{-6.50285e-03f, 3.75341e-02f, -1.33190e-01f, 5.22267e-01f, 6.92808e-01f, -1.46043e-01f, 3.95900e-02f, -6.73929e-03f}, // 55/128
{-6.54823e-03f, 3.78315e-02f, -1.34515e-01f, 5.32164e-01f, 6.83875e-01f, -1.45993e-01f, 3.96678e-02f, -6.75943e-03f}, // 56/128
{-6.58996e-03f, 3.81085e-02f, -1.35775e-01f, 5.42025e-01f, 6.74865e-01f, -1.45859e-01f, 3.97222e-02f, -6.77557e-03f}, // 57/128
{-6.62802e-03f, 3.83650e-02f, -1.36969e-01f, 5.51849e-01f, 6.65779e-01f, -1.45641e-01f, 3.97532e-02f, -6.78771e-03f}, // 58/128
{-6.66238e-03f, 3.86006e-02f, -1.38094e-01f, 5.61631e-01f, 6.56621e-01f, -1.45343e-01f, 3.97610e-02f, -6.79588e-03f}, // 59/128
{-6.69300e-03f, 3.88151e-02f, -1.39150e-01f, 5.71370e-01f, 6.47394e-01f, -1.44963e-01f, 3.97458e-02f, -6.80007e-03f}, // 60/128
{-6.71985e-03f, 3.90083e-02f, -1.40136e-01f, 5.81063e-01f, 6.38099e-01f, -1.44503e-01f, 3.97077e-02f, -6.80032e-03f}, // 61/128
{-6.74291e-03f, 3.91800e-02f, -1.41050e-01f, 5.90706e-01f, 6.28739e-01f, -1.43965e-01f, 3.96469e-02f, -6.79662e-03f}, // 62/128
{-6.76214e-03f, 3.93299e-02f, -1.41891e-01f, 6.00298e-01f, 6.19318e-01f, -1.43350e-01f, 3.95635e-02f, -6.78902e-03f}, // 63/128
{-6.77751e-03f, 3.94578e-02f, -1.42658e-01f, 6.09836e-01f, 6.09836e-01f, -1.42658e-01f, 3.94578e-02f, -6.77751e-03f}, // 64/128
{-6.78902e-03f, 3.95635e-02f, -1.43350e-01f, 6.19318e-01f, 6.00298e-01f, -1.41891e-01f, 3.93299e-02f, -6.76214e-03f}, // 65/128
{-6.79662e-03f, 3.96469e-02f, -1.43965e-01f, 6.28739e-01f, 5.90706e-01f, -1.41050e-01f, 3.91800e-02f, -6.74291e-03f}, // 66/128
{-6.80032e-03f, 3.97077e-02f, -1.44503e-01f, 6.38099e-01f, 5.81063e-01f, -1.40136e-01f, 3.90083e-02f, -6.71985e-03f}, // 67/128
{-6.80007e-03f, 3.97458e-02f, -1.44963e-01f, 6.47394e-01f, 5.71370e-01f, -1.39150e-01f, 3.88151e-02f, -6.69300e-03f}, // 68/128
{-6.79588e-03f, 3.97610e-02f, -1.45343e-01f, 6.56621e-01f, 5.61631e-01f, -1.38094e-01f, 3.86006e-02f, -6.66238e-03f}, // 69/128
{-6.78771e-03f, 3.97532e-02f, -1.45641e-01f, 6.65779e-01f, 5.51849e-01f, -1.36969e-01f, 3.83650e-02f, -6.62802e-03f}, // 70/128
{-6.77557e-03f, 3.97222e-02f, -1.45859e-01f, 6.74865e-01f, 5.42025e-01f, -1.35775e-01f, 3.81085e-02f, -6.58996e-03f}, // 71/128
{-6.75943e-03f, 3.96678e-02f, -1.45993e-01f, 6.83875e-01f, 5.32164e-01f, -1.34515e-01f, 3.78315e-02f, -6.54823e-03f}, // 72/128
{-6.73929e-03f, 3.95900e-02f, -1.46043e-01f, 6.92808e-01f, 5.22267e-01f, -1.33190e-01f, 3.75341e-02f, -6.50285e-03f}, // 73/128
{-6.71514e-03f, 3.94886e-02f, -1.46009e-01f, 7.01661e-01f, 5.12337e-01f, -1.31800e-01f, 3.72167e-02f, -6.45388e-03f}, // 74/128
{-6.68698e-03f, 3.93636e-02f, -1.45889e-01f, 7.10431e-01f, 5.02377e-01f, -1.30347e-01f, 3.68795e-02f, -6.40135e-03f}, // 75/128
{-6.65479e-03f, 3.92147e-02f, -1.45682e-01f, 7.19116e-01f, 4.92389e-01f, -1.28832e-01f, 3.65227e-02f, -6.34530e-03f}, // 76/128
{-6.61859e-03f, 3.90420e-02f, -1.45387e-01f, 7.27714e-01f, 4.82377e-01f, -1.27258e-01f, 3.61468e-02f, -6.28577e-03f}, // 77/128
{-6.57836e-03f, 3.88454e-02f, -1.45004e-01f, 7.36222e-01f, 4.72342e-01f, -1.25624e-01f, 3.57519e-02f, -6.22280e-03f}, // 78/128
{-6.53412e-03f, 3.86247e-02f, -1.44531e-01f, 7.44637e-01f, 4.62289e-01f, -1.23933e-01f, 3.53384e-02f, -6.15644e-03f}, // 79/128
{-6.48585e-03f, 3.83800e-02f, -1.43968e-01f, 7.52958e-01f, 4.52218e-01f, -1.22185e-01f, 3.49066e-02f, -6.08674e-03f}, // 80/128
{-6.43358e-03f, 3.81111e-02f, -1.43313e-01f, 7.61181e-01f, 4.42134e-01f, -1.20382e-01f, 3.44568e-02f, -6.01374e-03f}, // 81/128
{-6.37730e-03f, 3.78182e-02f, -1.42566e-01f, 7.69305e-01f, 4.32038e-01f, -1.18526e-01f, 3.39894e-02f, -5.93749e-03f}, // 82/128
{-6.31703e-03f, 3.75010e-02f, -1.41727e-01f, 7.77327e-01f, 4.21934e-01f, -1.16618e-01f, 3.35046e-02f, -5.85804e-03f}, // 83/128
{-6.25277e-03f, 3.71596e-02f, -1.40794e-01f, 7.85244e-01f, 4.11823e-01f, -1.14659e-01f, 3.30027e-02f, -5.77544e-03f}, // 84/128
{-6.18454e-03f, 3.67941e-02f, -1.39767e-01f, 7.93055e-01f, 4.01710e-01f, -1.12650e-01f, 3.24843e-02f, -5.68976e-03f}, // 85/128
{-6.11236e-03f, 3.64044e-02f, -1.38644e-01f, 8.00757e-01f, 3.91596e-01f, -1.10593e-01f, 3.19495e-02f, -5.60103e-03f}, // 86/128
{-6.03624e-03f, 3.59905e-02f, -1.37426e-01f, 8.08348e-01f, 3.81484e-01f, -1.08490e-01f, 3.13987e-02f, -5.50931e-03f}, // 87/128
{-5.95620e-03f, 3.55525e-02f, -1.36111e-01f, 8.15826e-01f, 3.71376e-01f, -1.06342e-01f, 3.08323e-02f, -5.41467e-03f}, // 88/128
{-5.87227e-03f, 3.50903e-02f, -1.34699e-01f, 8.23188e-01f, 3.61276e-01f, -1.04150e-01f, 3.02507e-02f, -5.31716e-03f}, // 89/128
{-5.78446e-03f, 3.46042e-02f, -1.33189e-01f, 8.30432e-01f, 3.51186e-01f, -1.01915e-01f, 2.96543e-02f, -5.21683e-03f}, // 90/128
{-5.69280e-03f, 3.40940e-02f, -1.31581e-01f, 8.37557e-01f, 3.41109e-01f, -9.96402e-02f, 2.90433e-02f, -5.11376e-03f}, // 91/128
{-5.59731e-03f, 3.35600e-02f, -1.29874e-01f, 8.44559e-01f, 3.31048e-01f, -9.73254e-02f, 2.84182e-02f, -5.00800e-03f}, // 92/128
{-5.49804e-03f, 3.30021e-02f, -1.28068e-01f, 8.51437e-01f, 3.21004e-01f, -9.49727e-02f, 2.77794e-02f, -4.89961e-03f}, // 93/128
{-5.39500e-03f, 3.24205e-02f, -1.26161e-01f, 8.58189e-01f, 3.10980e-01f, -9.25834e-02f, 2.71272e-02f, -4.78866e-03f}, // 94/128
{-5.28823e-03f, 3.18153e-02f, -1.24154e-01f, 8.64812e-01f, 3.00980e-01f, -9.01591e-02f, 2.64621e-02f, -4.67520e-03f}, // 95/128
{-5.17776e-03f, 3.11866e-02f, -1.22047e-01f, 8.71305e-01f, 2.91006e-01f, -8.77011e-02f, 2.57844e-02f, -4.55932e-03f}, // 96/128
{-5.06363e-03f, 3.05345e-02f, -1.19837e-01f, 8.77666e-01f, 2.81060e-01f, -8.52109e-02f, 2.50946e-02f, -4.44107e-03f}, // 97/128
{-4.94589e-03f, 2.98593e-02f, -1.17526e-01f, 8.83893e-01f, 2.71144e-01f, -8.26900e-02f, 2.43930e-02f, -4.32052e-03f}, // 98/128
{-4.82456e-03f, 2.91609e-02f, -1.15113e-01f, 8.89984e-01f, 2.61263e-01f, -8.01399e-02f, 2.36801e-02f, -4.19774e-03f}, // 99/128
{-4.69970e-03f, 2.84397e-02f, -1.12597e-01f, 8.95936e-01f, 2.51417e-01f, -7.75620e-02f, 2.29562e-02f, -4.07279e-03f}, // 100/128
{-4.57135e-03f, 2.76957e-02f, -1.09978e-01f, 9.01749e-01f, 2.41609e-01f, -7.49577e-02f, 2.22218e-02f, -3.94576e-03f}, // 101/128
{-4.43955e-03f, 2.69293e-02f, -1.07256e-01f, 9.07420e-01f, 2.31843e-01f, -7.23286e-02f, 2.14774e-02f, -3.81671e-03f}, // 102/128
{-4.30435e-03f, 2.61404e-02f, -1.04430e-01f, 9.12947e-01f, 2.22120e-01f, -6.96762e-02f, 2.07233e-02f, -3.68570e-03f}, // 103/128
{-4.16581e-03f, 2.53295e-02f, -1.01501e-01f, 9.18329e-01f, 2.12443e-01f, -6.70018e-02f, 1.99599e-02f, -3.55283e-03f}, // 104/128
{-4.02397e-03f, 2.44967e-02f, -9.84679e-02f, 9.23564e-01f, 2.02814e-01f, -6.43069e-02f, 1.91877e-02f, -3.41815e-03f}, // 105/128
{-3.87888e-03f, 2.36423e-02f, -9.53307e-02f, 9.28650e-01f, 1.93236e-01f, -6.15931e-02f, 1.84071e-02f, -3.28174e-03f}, // 106/128
{-3.73062e-03f, 2.27664e-02f, -9.20893e-02f, 9.33586e-01f, 1.83711e-01f, -5.88617e-02f, 1.76185e-02f, -3.14367e-03f}, // 107/128
{-3.57923e-03f, 2.18695e-02f, -8.87435e-02f, 9.38371e-01f, 1.74242e-01f, -5.61142e-02f, 1.68225e-02f, -3.00403e-03f}, // 108/128
{-3.42477e-03f, 2.09516e-02f, -8.52933e-02f, 9.43001e-01f, 1.64831e-01f, -5.33522e-02f, 1.60193e-02f, -2.86289e-03f}, // 109/128
{-3.26730e-03f, 2.00132e-02f, -8.17385e-02f, 9.47477e-01f, 1.55480e-01f, -5.05770e-02f, 1.52095e-02f, -2.72032e-03f}, // 110/128
{-3.10689e-03f, 1.90545e-02f, -7.80792e-02f, 9.51795e-01f, 1.46192e-01f, -4.77900e-02f, 1.43934e-02f, -2.57640e-03f}, // 111/128
{-2.94361e-03f, 1.80759e-02f, -7.43154e-02f, 9.55956e-01f, 1.36968e-01f, -4.49929e-02f, 1.35716e-02f, -2.43121e-03f}, // 112/128
{-2.77751e-03f, 1.70776e-02f, -7.04471e-02f, 9.59958e-01f, 1.27812e-01f, -4.21869e-02f, 1.27445e-02f, -2.28483e-03f}, // 113/128
{-2.60868e-03f, 1.60599e-02f, -6.64743e-02f, 9.63798e-01f, 1.18725e-01f, -3.93735e-02f, 1.19125e-02f, -2.13733e-03f}, // 114/128
{-2.43718e-03f, 1.50233e-02f, -6.23972e-02f, 9.67477e-01f, 1.09710e-01f, -3.65541e-02f, 1.10760e-02f, -1.98880e-03f}, // 115/128
{-2.26307e-03f, 1.39681e-02f, -5.82159e-02f, 9.70992e-01f, 1.00769e-01f, -3.37303e-02f, 1.02356e-02f, -1.83931e-03f}, // 116/128
{-2.08645e-03f, 1.28947e-02f, -5.39305e-02f, 9.74342e-01f, 9.19033e-02f, -3.09033e-02f, 9.39154e-03f, -1.68894e-03f}, // 117/128
{-1.90738e-03f, 1.18034e-02f, -4.95412e-02f, 9.77526e-01f, 8.31162e-02f, -2.80746e-02f, 8.54441e-03f, -1.53777e-03f}, // 118/128
{-1.72594e-03f, 1.06946e-02f, -4.50483e-02f, 9.80543e-01f, 7.44095e-02f, -2.52457e-02f, 7.69462e-03f, -1.38589e-03f}, // 119/128
{-1.54221e-03f, 9.56876e-03f, -4.04519e-02f, 9.83392e-01f, 6.57852e-02f, -2.24178e-02f, 6.84261e-03f, -1.23337e-03f}, // 120/128
{-1.35627e-03f, 8.42626e-03f, -3.57525e-02f, 9.86071e-01f, 5.72454e-02f, -1.95925e-02f, 5.98883e-03f, -1.08030e-03f}, // 121/128
{-1.16820e-03f, 7.26755e-03f, -3.09503e-02f, 9.88580e-01f, 4.87921e-02f, -1.67710e-02f, 5.13372e-03f, -9.26747e-04f}, // 122/128
{-9.78093e-04f, 6.09305e-03f, -2.60456e-02f, 9.90917e-01f, 4.04274e-02f, -1.39548e-02f, 4.27773e-03f, -7.72802e-04f}, // 123/128
{-7.86031e-04f, 4.90322e-03f, -2.10389e-02f, 9.93082e-01f, 3.21531e-02f, -1.11453e-02f, 3.42130e-03f, -6.18544e-04f}, // 124/128
{-5.92100e-04f, 3.69852e-03f, -1.59305e-02f, 9.95074e-01f, 2.39714e-02f, -8.34364e-03f, 2.56486e-03f, -4.64053e-04f}, // 125/128
{-3.96391e-04f, 2.47942e-03f, -1.07209e-02f, 9.96891e-01f, 1.58840e-02f, -5.55134e-03f, 1.70888e-03f, -3.09412e-04f}, // 126/128
{-1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f}, // 127/128
{0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f}, // 128/128
};
/* Instrumentation Taps */
private static final String INSTRUMENT_SYMBOL_SPREAD = "Tap Point: Symbol Spread (Goal=2.0)";
private FloatTap mSymbolSpreadTap;
private static final String INSTRUMENT_SYMBOL_SPREAD = "Tap Point: Symbol Spread (Goal=2.0)";
private FloatTap mSymbolSpreadTap;
private List<TapGroup> mAvailableTaps;
private static final int NUMBER_FILTER_TAPS = 8;
private static final int NUMBER_FILTER_STEPS = 128;
private static final int SAMPLE_RATE = 48000;
private static final int SYMBOL_RATE = 4800;
/* Tracking loop gain constant */
private static final double K_SYMBOL_SPREAD = 0.0100;
/* Constraints on symbol spreading */
private static final float SYMBOL_SPREAD_MAX = 2.4f; // upper range limit: +20%
private static final float SYMBOL_SPREAD_MIN = 1.6f; // lower range limit: -20%
private static final int NUMBER_FILTER_TAPS = 8;
private static final int NUMBER_FILTER_STEPS = 128;
/* Symbol clock tracking loop gain */
private static final double K_SYMBOL_TIMING = 0.025;
/* Coarse and fine frequency tracking constants */
private static final double K_COARSE_FREQUENCY = 0.00125;
private static final double K_FINE_FREQUENCY = 0.125;
/* Frequency correction broadcast threshold */
private static final int SAMPLE_RATE = 48000;
private static final int SYMBOL_RATE = 4800;
/* Tracking loop gain constant */
private static final double K_SYMBOL_SPREAD = 0.0100;
/* Constraints on symbol spreading */
private static final float SYMBOL_SPREAD_MAX = 2.4f; // upper range limit: +20%
private static final float SYMBOL_SPREAD_MIN = 1.6f; // lower range limit: -20%
/* Symbol clock tracking loop gain */
private static final double K_SYMBOL_TIMING = 0.025;
/* Coarse and fine frequency tracking constants */
private static final double K_COARSE_FREQUENCY = 0.00125;
private static final double K_FINE_FREQUENCY = 0.125;
/* Frequency correction broadcast threshold */
// private static final double COARSE_FREQUENCY_DEADBAND = 1.66;
private static final double COARSE_FREQUENCY_THRESHOLD = 1.20;
/* 2.0 symbol spread gives -3, -1, 1, 3 */
private float mSymbolSpread = 2.0f;
private float mSymbolClock = 0.0f;
private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
private float mFineFrequencyCorrection = 0.0f;
private float mCoarseFrequencyCorrection = 0.0f;
private static final double COARSE_FREQUENCY_THRESHOLD = 1.20;
private float mHistory[] = new float[ NUMBER_FILTER_TAPS ];
private int mHistoryLast = 0;
private RealSampleListener mListener;
private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
/* 2.0 symbol spread gives -3, -1, 1, 3 */
private float mSymbolSpread = 2.0f;
private float mSymbolClock = 0.0f;
private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
private int mFrequencyAdjustmentRequested = 0;
private int mFrequencyCorrection = 0;
private int mFrequencyCorrectionMaximum = 3000;
private boolean mResetFrequencyTracker = false;
private FrequencyCorrectionProcessor mFrequencyCorrectionProcessor;
private Listener<FrequencyChangeEvent> mFrequencyChangeListener;
/**
* C4FM Symbol Filter
*
* Sample Gain values - the gain of incoming sample values will critically
* impact the performance of this filter. Gain should be adjusted to
* optimize the value of mSymbolSpread toward a value of 2.0. If the value
* is less than 2.0, then increase gain. If the value is over 2.0, then
* decrease gain.
*
* When preceded by the RealAutomaticGainFilter class, an optimal gain
* setting for this filter is 15.4 and that yields a symbol spread that
* is centered on 2.0, ranging 1.92 to 2.08.
*/
public C4FMSymbolFilter( int frequencyCorrectionMaximum )
{
mFrequencyCorrectionMaximum = frequencyCorrectionMaximum;
}
public void dispose()
{
mGainController = null;
private float mFineFrequencyCorrection = 0.0f;
private float mCoarseFrequencyCorrection = 0.0f;
private float mHistory[] = new float[NUMBER_FILTER_TAPS];
private int mHistoryLast = 0;
private RealSampleListener mListener;
private DirectGainControl mGainController = new DirectGainControl(15.0f, 0.1f, 35.0f, 0.3f);
private int mFrequencyAdjustmentRequested = 0;
private int mFrequencyCorrection = 0;
private int mFrequencyCorrectionMaximum = 3000;
private boolean mResetFrequencyTracker = false;
private FrequencyCorrectionProcessor mFrequencyCorrectionProcessor;
private Listener<SourceEvent> mFrequencyChangeListener;
/**
* C4FM Symbol Filter
*
* Sample Gain values - the gain of incoming sample values will critically
* impact the performance of this filter. Gain should be adjusted to
* optimize the value of mSymbolSpread toward a value of 2.0. If the value
* is less than 2.0, then increase gain. If the value is over 2.0, then
* decrease gain.
*
* When preceded by the RealAutomaticGainFilter class, an optimal gain
* setting for this filter is 15.4 and that yields a symbol spread that
* is centered on 2.0, ranging 1.92 to 2.08.
*/
public C4FMSymbolFilter(int frequencyCorrectionMaximum)
{
mFrequencyCorrectionMaximum = frequencyCorrectionMaximum;
}
public void dispose()
{
mGainController = null;
// mFrequencyCorrectionControl = null;
mListener = null;
}
@Override
public void receive( RealBuffer buffer )
{
for( float sample: buffer.getSamples() )
{
receive( sample );
}
mListener = null;
}
@Override
public void receive(RealBuffer buffer)
{
for(float sample : buffer.getSamples())
{
receive(sample);
}
/* If a frequency correction was requested during the processing of this
* buffer, we'll apply the change and it will be reflected in the next
* buffer, we'll apply the change and it will be reflected in the next
* arriving buffer. Reset the lock on frequency correction and reset
* the internal frequency correction tracker */
if( mFrequencyAdjustmentRequested != 0 )
{
int correction = mFrequencyCorrection + mFrequencyAdjustmentRequested;
if( correction > mFrequencyCorrectionMaximum )
{
correction = mFrequencyCorrectionMaximum;
}
else if( correction < -mFrequencyCorrectionMaximum )
{
correction = -mFrequencyCorrectionMaximum;
}
if(mFrequencyAdjustmentRequested != 0)
{
int correction = mFrequencyCorrection + mFrequencyAdjustmentRequested;
broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
if(correction > mFrequencyCorrectionMaximum)
{
correction = mFrequencyCorrectionMaximum;
}
else if(correction < -mFrequencyCorrectionMaximum)
{
correction = -mFrequencyCorrectionMaximum;
}
mFrequencyAdjustmentRequested = 0;
}
}
broadcast(SourceEvent.channelFrequencyCorrectionRequest(correction));
public void receive( float sample )
mFrequencyAdjustmentRequested = 0;
}
}
public void receive(float sample)
{
sample = mGainController.correct( sample );
if( mResetFrequencyTracker )
{
mCoarseFrequencyCorrection = 0.0f;
mFineFrequencyCorrection = 0.0f;
broadcast( new FrequencyChangeEvent(
Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, 0 ));
mResetFrequencyTracker = false;
}
mSymbolClock += mSymbolTime;
mHistory[ mHistoryLast++ ] = sample;
mHistoryLast %= NUMBER_FILTER_TAPS;
if( mSymbolClock > 1.0f )
{
mSymbolClock -= 1.0f;
int imu = (int)Math.floor( 0.5 +
( (float)NUMBER_FILTER_STEPS * ( mSymbolClock / mSymbolTime ) ) );
if( imu >= NUMBER_FILTER_STEPS )
{
imu = NUMBER_FILTER_STEPS - 1;
}
sample = mGainController.correct(sample);
int imu_p1 = imu + 1;
if(mResetFrequencyTracker)
{
mCoarseFrequencyCorrection = 0.0f;
mFineFrequencyCorrection = 0.0f;
int j = mHistoryLast;
double interp = 0.0;
double interp_p1 = 0.0;
for( int i = 0; i < NUMBER_FILTER_TAPS; i++ )
{
interp += TAPS[ imu][ i ] * mHistory[ j ];
interp_p1 += TAPS[ imu_p1 ][ i ] * mHistory[ j ];
j = ( j + 1 ) % NUMBER_FILTER_TAPS;
}
broadcast(SourceEvent.channelFrequencyCorrectionRequest(0l));
mResetFrequencyTracker = false;
}
mSymbolClock += mSymbolTime;
mHistory[mHistoryLast++] = sample;
mHistoryLast %= NUMBER_FILTER_TAPS;
if(mSymbolClock > 1.0f)
{
mSymbolClock -= 1.0f;
int imu = (int)Math.floor(0.5 +
((float)NUMBER_FILTER_STEPS * (mSymbolClock / mSymbolTime)));
if(imu >= NUMBER_FILTER_STEPS)
{
imu = NUMBER_FILTER_STEPS - 1;
}
int imu_p1 = imu + 1;
int j = mHistoryLast;
double interp = 0.0;
double interp_p1 = 0.0;
for(int i = 0; i < NUMBER_FILTER_TAPS; i++)
{
interp += TAPS[imu][i] * mHistory[j];
interp_p1 += TAPS[imu_p1][i] * mHistory[j];
j = (j + 1) % NUMBER_FILTER_TAPS;
}
/* Output symbol will be interpolated value corrected for symbol
* spread and frequency offset */
interp -= mFineFrequencyCorrection;
interp_p1 -= mFineFrequencyCorrection;
interp -= mFineFrequencyCorrection;
interp_p1 -= mFineFrequencyCorrection;
/* Correct output for symbol deviation (spread) */
float output = (float)( 2.0 * interp / mSymbolSpread );
float output = (float)(2.0 * interp / mSymbolSpread);
/* Detect received symbol error: basically use a hard decision and
* subtract off expected position nominal symbol level which will be
* +/- 0.5 * symbol spread and +/- 1.5 symbol spread. Remember that
* nominal symbol spread will be 2.0 */
double symbolError;
if( interp < -mSymbolSpread )
{
double symbolError;
if(interp < -mSymbolSpread)
{
/* symbol is -3: Expected at -1.5 * symbol spread */
symbolError = interp + ( 1.5 * mSymbolSpread );
mSymbolSpread -= ( symbolError * 0.5 * K_SYMBOL_SPREAD );
}
else if( interp < 0.0 )
{
symbolError = interp + (1.5 * mSymbolSpread);
mSymbolSpread -= (symbolError * 0.5 * K_SYMBOL_SPREAD);
}
else if(interp < 0.0)
{
/* symbol is -1: Expected at -0.5 * symbol_spread */
symbolError = interp + (0.5 * mSymbolSpread );
mSymbolSpread -= ( symbolError * K_SYMBOL_SPREAD );
}
else if( interp < mSymbolSpread )
{
symbolError = interp + (0.5 * mSymbolSpread);
mSymbolSpread -= (symbolError * K_SYMBOL_SPREAD);
}
else if(interp < mSymbolSpread)
{
/* symbol is +1: Expected at +0.5 * symbol_spread */
symbolError = interp - ( 0.5 * mSymbolSpread );
mSymbolSpread += ( symbolError * K_SYMBOL_SPREAD );
}
else
{
symbolError = interp - (0.5 * mSymbolSpread);
mSymbolSpread += (symbolError * K_SYMBOL_SPREAD);
}
else
{
/* symbol is +3: Expected at +1.5 * symbol_spread */
symbolError = interp - ( 1.5 * mSymbolSpread );
mSymbolSpread += ( symbolError * 0.5 * K_SYMBOL_SPREAD );
}
symbolError = interp - (1.5 * mSymbolSpread);
mSymbolSpread += (symbolError * 0.5 * K_SYMBOL_SPREAD);
}
/* Symbol clock tracking loop adjustment */
if( interp_p1 < interp )
{
mSymbolClock += symbolError * K_SYMBOL_TIMING;
}
else
{
mSymbolClock -= symbolError * K_SYMBOL_TIMING;
}
if( mSymbolSpread < SYMBOL_SPREAD_MIN )
{
mGainController.increase();
mSymbolSpread = SYMBOL_SPREAD_MIN;
}
else if( mSymbolSpread > SYMBOL_SPREAD_MAX )
{
mGainController.decrease();
if(interp_p1 < interp)
{
mSymbolClock += symbolError * K_SYMBOL_TIMING;
}
else
{
mSymbolClock -= symbolError * K_SYMBOL_TIMING;
}
mSymbolSpread = SYMBOL_SPREAD_MAX;
}
if(mSymbolSpread < SYMBOL_SPREAD_MIN)
{
mGainController.increase();
mCoarseFrequencyCorrection += ( ( mFineFrequencyCorrection -
mCoarseFrequencyCorrection ) * K_COARSE_FREQUENCY );
mFineFrequencyCorrection += ( symbolError * K_FINE_FREQUENCY );
mSymbolSpread = SYMBOL_SPREAD_MIN;
}
else if(mSymbolSpread > SYMBOL_SPREAD_MAX)
{
mGainController.decrease();
mSymbolSpread = SYMBOL_SPREAD_MAX;
}
mCoarseFrequencyCorrection += ((mFineFrequencyCorrection -
mCoarseFrequencyCorrection) * K_COARSE_FREQUENCY);
mFineFrequencyCorrection += (symbolError * K_FINE_FREQUENCY);
/* Queue a frequency adjustment (once per buffer) as needed */
if( Math.abs( mCoarseFrequencyCorrection ) > COARSE_FREQUENCY_THRESHOLD )
{
mFrequencyAdjustmentRequested =
500 * ( mCoarseFrequencyCorrection > 0 ? 1 : -1 );
}
if( mSymbolSpreadTap != null )
{
mSymbolSpreadTap.receive( mSymbolSpread );
}
if(Math.abs(mCoarseFrequencyCorrection) > COARSE_FREQUENCY_THRESHOLD)
{
mFrequencyAdjustmentRequested =
500 * (mCoarseFrequencyCorrection > 0 ? 1 : -1);
}
if(mSymbolSpreadTap != null)
{
mSymbolSpreadTap.receive(mSymbolSpread);
}
/* dispatch the interpolated value to the listener */
if( mListener != null )
{
mListener.receive( output );
}
}
if(mListener != null)
{
mListener.receive(output);
}
}
}
@Override
public void setListener( RealSampleListener listener )
@Override
public void setListener(RealSampleListener listener)
{
mListener = listener;
mListener = listener;
}
@Override
public void removeListener( RealSampleListener listener )
@Override
public void removeListener(RealSampleListener listener)
{
mListener = null;
mListener = null;
}
@Override
@Override
public List<TapGroup> getTapGroups()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<>();
if(mAvailableTaps == null)
{
mAvailableTaps = new ArrayList<>();
/*
* Since the target is 2.0, we subtract 2.0 from the value so that
* a good value is plotted right on the zero center line
*/
TapGroup group = new TapGroup( "C4FM Symbol Filter" );
group.add(
new AdditiveFloatTap( INSTRUMENT_SYMBOL_SPREAD, 0, 0.1f, -2.0f ) );
mAvailableTaps.add( group );
}
return mAvailableTaps;
TapGroup group = new TapGroup("C4FM Symbol Filter");
group.add(
new AdditiveFloatTap(INSTRUMENT_SYMBOL_SPREAD, 0, 0.1f, -2.0f));
mAvailableTaps.add(group);
}
return mAvailableTaps;
}
@Override
public void registerTap( Tap tap )
@Override
public void registerTap(Tap tap)
{
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
{
mSymbolSpreadTap = (FloatTap)tap;
}
if(tap.getName().contentEquals(INSTRUMENT_SYMBOL_SPREAD))
{
mSymbolSpreadTap = (FloatTap)tap;
}
}
@Override
public void unregisterTap( Tap tap )
@Override
public void unregisterTap(Tap tap)
{
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
{
mSymbolSpreadTap = null;
}
if(tap.getName().contentEquals(INSTRUMENT_SYMBOL_SPREAD))
{
mSymbolSpreadTap = null;
}
}
/**
* Broadcasts a frequency change event to the registered listener
*
* @param event
*/
public void broadcast( FrequencyChangeEvent event )
{
if( mFrequencyChangeListener != null )
{
mFrequencyChangeListener.receive( event );
}
}
public void broadcast(SourceEvent event)
{
if(mFrequencyChangeListener != null)
{
mFrequencyChangeListener.receive(event);
}
}
/**
* Sets the listener for frequency change events.
*
* @param listener
*/
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
{
mFrequencyChangeListener = listener;
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
if( mFrequencyCorrectionProcessor == null )
{
mFrequencyCorrectionProcessor = new FrequencyCorrectionProcessor();
}
return mFrequencyCorrectionProcessor;
}
public void setFrequencyChangeListener(Listener<SourceEvent> listener)
{
mFrequencyChangeListener = listener;
}
/**
* Receives notifications that the channel frequency correction has been
* applied and updates internal tracking value.
*/
public class FrequencyCorrectionProcessor implements Listener<FrequencyChangeEvent>
{
@Override
public void receive( FrequencyChangeEvent event )
{
switch( event.getEvent() )
{
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
mFrequencyCorrection = event.getValue().intValue();
@Override
public Listener<SourceEvent> getSourceEventListener()
{
if(mFrequencyCorrectionProcessor == null)
{
mFrequencyCorrectionProcessor = new FrequencyCorrectionProcessor();
}
//Reset internal frequency tracking
mCoarseFrequencyCorrection = 0.0f;
mFineFrequencyCorrection = 0.0f;
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mResetFrequencyTracker = true;
break;
default:
break;
}
}
}
return mFrequencyCorrectionProcessor;
}
/**
* Receives notifications that the channel frequency correction has been
* applied and updates internal tracking value.
*/
public class FrequencyCorrectionProcessor implements Listener<SourceEvent>
{
@Override
public void receive(SourceEvent event)
{
switch(event.getEvent())
{
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
mFrequencyCorrection = event.getValue().intValue();
//Reset internal frequency tracking
mCoarseFrequencyCorrection = 0.0f;
mFineFrequencyCorrection = 0.0f;
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mResetFrequencyTracker = true;
break;
default:
break;
}
}
}
}

View File

@@ -4,10 +4,10 @@ import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.instrument.Instrumentable;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
public abstract class P25Decoder extends Decoder implements ISourceEventListener, ISourceEventProvider, Instrumentable
{
private P25MessageProcessor mMessageProcessor;

View File

@@ -142,7 +142,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class P25DecoderState extends DecoderState
{
@@ -223,11 +222,6 @@ public class P25DecoderState extends DecoderState
return DecoderType.P25_PHASE1;
}
@Override
public void stop()
{
}
/**
* Performs a full reset to prepare this object for reuse on a new channel
*/
@@ -241,6 +235,12 @@ public class P25DecoderState extends DecoderState
mSystem = null;
}
@Override
public void init()
{
}
/**
* Resets any temporal state details
*/
@@ -259,14 +259,6 @@ public class P25DecoderState extends DecoderState
mCurrentCallEvent = null;
}
/**
* Performs any initialization operations to prepare for use
*/
@Override
public void init()
{
}
/**
* Indicates if there a call event exists for the specified channel and
* from and to talkgroup identifiers.
@@ -3625,7 +3617,7 @@ public class P25DecoderState extends DecoderState
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
//Change the default (45-second) traffic channel timeout to 1 second
if(mChannelType == ChannelType.TRAFFIC)
@@ -3633,4 +3625,10 @@ public class P25DecoderState extends DecoderState
broadcast(new ChangeChannelTimeoutEvent(this, ChannelType.TRAFFIC, 1000));
}
}
@Override
public void stop()
{
}
}

View File

@@ -26,13 +26,12 @@ import io.github.dsheirer.instrument.tap.stream.FloatTap;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
{
@@ -60,7 +59,7 @@ public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferLi
* infrastructure and subscriber identities that will be included in each
* decoded message
*/
public P25_C4FMDecoder(AliasList aliasList, int frequencyCorrectionMaximum )
public P25_C4FMDecoder( AliasList aliasList, int frequencyCorrectionMaximum )
{
super( aliasList );
@@ -193,28 +192,7 @@ public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferLi
}
@Override
public void reset()
{
// TODO Auto-generated method stub
}
@Override
public void start( ScheduledExecutorService executor )
{
// TODO Auto-generated method stub
}
@Override
public void stop()
{
// TODO Auto-generated method stub
}
@Override
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
public void setSourceEventListener(Listener<SourceEvent> listener )
{
if( mSymbolFilter != null )
{
@@ -223,7 +201,7 @@ public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferLi
}
@Override
public void removeFrequencyChangeListener()
public void removeSourceEventListener()
{
if( mSymbolFilter != null )
{
@@ -232,11 +210,11 @@ public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferLi
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
public Listener<SourceEvent> getSourceEventListener()
{
if( mSymbolFilter != null )
{
return mSymbolFilter.getFrequencyChangeListener();
return mSymbolFilter.getSourceEventListener();
}
return null;

View File

@@ -34,13 +34,12 @@ import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter;
import io.github.dsheirer.sample.complex.IComplexBufferListener;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
{
@@ -67,7 +66,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
super( aliasList );
mBasebandFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass(
48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0f );
48000, 7250, 8000, 60, WindowType.HANN, true ), 1.0f );
mBasebandFilter.setListener( mStreamConverter );
@@ -106,29 +105,6 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
mMessageFramer = null;
}
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
}
@Override
public void removeFrequencyChangeListener()
{
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return new Listener<FrequencyChangeEvent>()
{
@Override
public void receive(FrequencyChangeEvent frequencyChangeEvent)
{
//Ignored
}
};
}
@Override
public Listener<ComplexBuffer> getComplexBufferListener()
{
@@ -237,23 +213,20 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
}
@Override
public void reset()
public Listener<SourceEvent> getSourceEventListener()
{
// TODO Auto-generated method stub
return null;
}
@Override
public void start( ScheduledExecutorService executor )
public void setSourceEventListener(Listener<SourceEvent> listener)
{
// TODO Auto-generated method stub
}
@Override
public void stop()
public void removeSourceEventListener()
{
// TODO Auto-generated method stub
}
}

View File

@@ -20,8 +20,6 @@ import jmbe.iface.AudioConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
public class P25AudioModule extends Module implements Listener<Message>, IAudioPacketProvider, IMessageListener,
ISquelchStateListener
{
@@ -71,7 +69,7 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
}

View File

@@ -33,7 +33,6 @@ import io.github.dsheirer.sample.real.RealBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
public class PassportDecoder extends Decoder
implements IUnFilteredRealBufferListener, Instrumentable
@@ -108,14 +107,4 @@ public class PassportDecoder extends Decoder
{
mPassportMessageFramer.reset();
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
}
}

View File

@@ -40,7 +40,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class PassportDecoderState extends DecoderState
{
@@ -76,21 +75,6 @@ public class PassportDecoderState extends DecoderState
return DecoderType.PASSPORT;
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void init()
{
}
@Override
public void stop()
{
}
private void logTalkgroup(String talkgroup)
{
if(mTalkgroupsFirstHeard.contains(talkgroup))
@@ -519,6 +503,24 @@ public class PassportDecoderState extends DecoderState
resetState();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
private void resetState()
{
mToTalkgroupAttribute.reset();

View File

@@ -40,7 +40,6 @@ import io.github.dsheirer.sample.real.RealBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
/**
* TAIT 1200 - 1200 baud 2FSK decoder
@@ -189,25 +188,4 @@ public class Tait1200Decoder extends Decoder implements IFilteredRealBufferListe
{
return DecoderType.TAIT_1200;
}
@Override
public void reset()
{
// TODO Auto-generated method stub
}
@Override
public void start( ScheduledExecutorService executor )
{
// TODO Auto-generated method stub
}
@Override
public void stop()
{
// TODO Auto-generated method stub
}
}

View File

@@ -38,7 +38,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class Tait1200DecoderState extends DecoderState
{
@@ -65,16 +64,6 @@ public class Tait1200DecoderState extends DecoderState
return DecoderType.TAIT_1200;
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
@Override
public void reset()
{
@@ -83,6 +72,24 @@ public class Tait1200DecoderState extends DecoderState
resetState();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
private void resetState()
{
mFromAttribute.reset();
@@ -176,12 +183,6 @@ public class Tait1200DecoderState extends DecoderState
return sb.toString();
}
@Override
public void init()
{
/* No initialization required */
}
/**
* Responds to reset events issued by the channel state
*/

View File

@@ -30,95 +30,81 @@ import io.github.dsheirer.sample.complex.IComplexBufferListener;
import io.github.dsheirer.sample.real.IFilteredRealBufferProvider;
import io.github.dsheirer.sample.real.RealBuffer;
import java.util.concurrent.ScheduledExecutorService;
public class AMDemodulatorModule extends Module
implements IComplexBufferListener, IFilteredRealBufferProvider
implements IComplexBufferListener, IFilteredRealBufferProvider
{
private static final int SAMPLE_RATE = 48000;
private ComplexFIRFilter_CB_CB mIQFilter;
private AMDemodulator_CB mDemodulator;
private RealFIRFilter_RB_RB mLowPassFilter;
private static final int SAMPLE_RATE = 48000;
private ComplexFIRFilter_CB_CB mIQFilter;
private AMDemodulator_CB mDemodulator;
private RealFIRFilter_RB_RB mLowPassFilter;
private AutomaticGainControl_RB mAGC;
/**
* FM Demodulator with integrated DC removal and automatic frequency
* correction control.
*
* @param pass - pass frequency for IQ filtering prior to demodulation. This
* frequency should be half of the signal bandwidth since the filter will
* be applied against each of the inphase and quadrature signals and the
* combined pass bandwidth will be twice this value.
*
* @param stop - stop frequency for IQ filtering prior to demodulation.
*
* @param maxFrequencyCorrection - defines the maximum frequency +/- that
* the correction controller can adjust from the center tuned frequency.
* Set this value to 0 for no frequency correction control.
*/
public AMDemodulatorModule()
{
mIQFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass(
SAMPLE_RATE, 5000, 73, WindowType.HAMMING ), 1.0f );
mDemodulator = new AMDemodulator_CB( 500.0f );
mIQFilter.setListener( mDemodulator );
/**
* AM Demodulator Module
*/
public AMDemodulatorModule()
{
mIQFilter = new ComplexFIRFilter_CB_CB(FilterFactory.getLowPass(
SAMPLE_RATE, 5000, 73, WindowType.HAMMING), 1.0f);
mLowPassFilter = new RealFIRFilter_RB_RB(
FilterFactory.getLowPass( 48000, 3000, 31, WindowType.COSINE ), 1.0f );
mDemodulator.setListener( mLowPassFilter );
mAGC = new AutomaticGainControl_RB();
mLowPassFilter.setListener( mAGC );
}
mDemodulator = new AMDemodulator_CB(500.0f);
mIQFilter.setListener(mDemodulator);
@Override
public Listener<ComplexBuffer> getComplexBufferListener()
{
return mIQFilter;
}
mLowPassFilter = new RealFIRFilter_RB_RB(
FilterFactory.getLowPass(48000, 3000, 31, WindowType.COSINE), 1.0f);
mDemodulator.setListener(mLowPassFilter);
@Override
public void dispose()
{
mIQFilter.dispose();
mIQFilter = null;
mDemodulator.dispose();
mDemodulator = null;
mLowPassFilter.dispose();
mLowPassFilter = null;
mAGC.dispose();
mAGC = null;
}
mAGC = new AutomaticGainControl_RB();
mLowPassFilter.setListener(mAGC);
}
@Override
public void reset()
{
}
@Override
public Listener<ComplexBuffer> getComplexBufferListener()
{
return mIQFilter;
}
@Override
public void setFilteredRealBufferListener( Listener<RealBuffer> listener )
{
mAGC.setListener( listener );
}
@Override
public void dispose()
{
mIQFilter.dispose();
mIQFilter = null;
@Override
public void removeFilteredRealBufferListener()
{
mAGC.removeListener();
}
mDemodulator.dispose();
mDemodulator = null;
@Override
public void start( ScheduledExecutorService executor )
{
}
mLowPassFilter.dispose();
mLowPassFilter = null;
@Override
public void stop()
{
}
mAGC.dispose();
mAGC = null;
}
@Override
public void reset()
{
}
@Override
public void setFilteredRealBufferListener(Listener<RealBuffer> listener)
{
mAGC.setListener(listener);
}
@Override
public void removeFilteredRealBufferListener()
{
mAGC.removeListener();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
}

View File

@@ -30,8 +30,6 @@ import io.github.dsheirer.sample.real.RealBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
public class DemodulatedAudioFilterModule extends Module implements IUnFilteredRealBufferListener, IFilteredRealBufferProvider
{
private final static Logger mLog = LoggerFactory.getLogger(DemodulatedAudioFilterModule.class);
@@ -53,7 +51,7 @@ public class DemodulatedAudioFilterModule extends Module implements IUnFilteredR
//TODO: change this to a band pass filter
mBandPassFilter = new RealFIRFilter_RB_RB(FilterFactory.getLowPass(48000, pass, stop, 60,
WindowType.HANNING, true), 1.0f);
WindowType.HANN, true), 1.0f);
mDCFilter.setListener(mBandPassFilter);
}
@@ -97,7 +95,7 @@ public class DemodulatedAudioFilterModule extends Module implements IUnFilteredR
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
}

View File

@@ -30,8 +30,6 @@ import io.github.dsheirer.sample.real.RealBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
public class FMDemodulatorModule extends Module implements IComplexBufferListener, IUnFilteredRealBufferProvider
{
private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatorModule.class);
@@ -104,7 +102,7 @@ public class FMDemodulatorModule extends Module implements IComplexBufferListene
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
}

View File

@@ -30,7 +30,6 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.file.Path;
import java.util.concurrent.ScheduledExecutorService;
public abstract class EventLogger extends Module
{
@@ -65,7 +64,7 @@ public abstract class EventLogger extends Module
public abstract String getHeader();
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
if(mLogFile == null)
{
@@ -80,6 +79,8 @@ public abstract class EventLogger extends Module
mLogFileName = sb.toString();
mLog.info("Creating log file:" + mLogFileName);
mLogFile = new OutputStreamWriter(new FileOutputStream(mLogFileName));
write(getHeader());
@@ -123,17 +124,14 @@ public abstract class EventLogger extends Module
protected void write(String eventLogEntry)
{
if(mLogFile != null)
try
{
try
{
mLogFile.write(eventLogEntry + "\n");
mLogFile.flush();
}
catch(IOException e)
{
mLog.error("Error writing entry to event log file", e);
}
mLogFile.write(eventLogEntry + "\n");
mLogFile.flush();
}
catch(IOException e)
{
mLog.error("Error writing entry to event log file", e);
}
}
}

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.audio.IAudioPacketListener;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,7 +34,6 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -193,10 +193,8 @@ public abstract class AudioRecorder extends Module implements Listener<AudioPack
/**
* Starts this recorder as a scheduled thread running under the executor argument
*
* @param executor to use in scheduling audio conversion and file writes.
*/
public void start(ScheduledExecutorService executor)
public void start()
{
if(mRunning.compareAndSet(false, true))
{
@@ -212,7 +210,7 @@ public abstract class AudioRecorder extends Module implements Listener<AudioPack
mFileOutputStream = new FileOutputStream(mPath.toFile());
/* Schedule the handler to run every half second */
mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
mProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
}
catch(IOException io)
{

View File

@@ -144,7 +144,7 @@ public class RecorderManager implements Listener<AudioPacket>
{
recorder = new AudioPacketWaveRecorder(filePrefix, audioPacket.getMetadata());
recorder.start(ThreadPool.SCHEDULED);
recorder.start();
recorder.receive(audioPacket);
mRecorders.put(identifier, recorder);

View File

@@ -22,6 +22,7 @@ import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.ConversionUtils;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,7 +32,6 @@ import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -89,7 +89,7 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
return mFile;
}
public void start(ScheduledExecutorService executor)
public void start()
{
if(mRunning.compareAndSet(false, true))
{
@@ -111,7 +111,7 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
mWriter = new WaveWriter(mAudioFormat, mFile);
/* Schedule the processor to run every 500 milliseconds */
mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
mProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
}
catch(IOException io)
{

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.sample.ConversionUtils;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.IComplexBufferListener;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,7 +33,6 @@ import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -43,8 +43,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class ComplexBufferWaveRecorder extends Module
implements IComplexBufferListener, Listener<ComplexBuffer>
{
private final static Logger mLog =
LoggerFactory.getLogger( ComplexBufferWaveRecorder.class );
private final static Logger mLog = LoggerFactory.getLogger( ComplexBufferWaveRecorder.class );
private WaveWriter mWriter;
private String mFilePrefix;
@@ -68,19 +67,19 @@ public class ComplexBufferWaveRecorder extends Module
mFilePrefix = filePrefix;
}
public Path getFile()
{
return mFile;
}
public void start( ScheduledExecutorService executor )
{
if( mRunning.compareAndSet( false, true ) )
{
if( mBufferProcessor == null )
{
mBufferProcessor = new BufferProcessor();
}
public Path getFile()
{
return mFile;
}
public void start()
{
if(mRunning.compareAndSet(false, true))
{
if(mBufferProcessor == null)
{
mBufferProcessor = new BufferProcessor();
}
try
{
@@ -95,7 +94,7 @@ public class ComplexBufferWaveRecorder extends Module
mWriter = new WaveWriter( mAudioFormat, mFile );
/* Schedule the processor to run every 500 milliseconds */
mProcessorHandle = executor.scheduleAtFixedRate(
mProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(
mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS );
}
catch( IOException io )

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.sample.ConversionUtils;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,7 +33,6 @@ import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -40,8 +40,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
* WAVE audio recorder module for recording real sample buffers to a wave file
*/
public class RealBufferWaveRecorder extends Module
implements IFilteredRealBufferListener, Listener<RealBuffer>
public class RealBufferWaveRecorder extends Module implements IFilteredRealBufferListener, Listener<RealBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(RealBufferWaveRecorder.class);
@@ -89,7 +88,7 @@ public class RealBufferWaveRecorder extends Module
return mFile;
}
public void start(ScheduledExecutorService executor)
public void start()
{
if(mRunning.compareAndSet(false, true))
{
@@ -111,7 +110,7 @@ public class RealBufferWaveRecorder extends Module
mWriter = new WaveWriter(mAudioFormat, mFile);
/* Schedule the processor to run every 500 milliseconds */
mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
mProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
}
catch(IOException io)
{

View File

@@ -25,62 +25,100 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public class Broadcaster<T> implements Listener<T>
{
private List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
private List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
@Override
public void receive( T t )
/**
* Implements the Listener<T> interface to receive an element and broadcast that element to all registered
* listeners.
*
* @param t element to broadcast
*/
@Override
public void receive(T t)
{
broadcast( t );
broadcast(t);
}
/**
* Clear listeners to prepare for garbage collection
*/
public void dispose()
{
mListeners.clear();
}
public boolean hasListeners()
{
return !mListeners.isEmpty();
}
public int getListenerCount()
{
return mListeners.size();
}
public List<Listener<T>> getListeners()
{
return mListeners;
}
public void addListener( Listener<T> listener )
{
if(listener == null)
{
throw new IllegalArgumentException("Listener cannot be null");
}
mListeners.add( listener );
}
public void removeListener( Listener<T> listener )
{
mListeners.remove( listener );
}
public void clear()
{
mListeners.clear();
}
public void broadcast( T t )
/**
* Clear listeners to prepare for garbage collection
*/
public void dispose()
{
for( Listener<T> listener: mListeners )
{
listener.receive( t );
}
mListeners.clear();
}
/**
* Indicates if this broadcaster has any listeners registered
*/
public boolean hasListeners()
{
return !mListeners.isEmpty();
}
/**
* Indicates if the listener is currently registered with this broadcaster
*/
public boolean hasListener(Listener<T> listener)
{
return listener != null && mListeners.contains(listener);
}
/**
* The count of listeners currently registered with this broadcaster
*/
public int getListenerCount()
{
return mListeners.size();
}
/**
* The list of listeners currently registered with this broadcaster
*/
public List<Listener<T>> getListeners()
{
return mListeners;
}
/**
* Registers the listener to receive elements from this broadcaster
*
* @param listener
*/
public void addListener(Listener<T> listener)
{
if(listener != null && !mListeners.contains(listener))
{
mListeners.add(listener);
}
}
/**
* Deregisters the listener from receiving elements from this broadcaster
*/
public void removeListener(Listener<T> listener)
{
if(listener != null && mListeners.contains(listener))
{
mListeners.remove(listener);
}
}
/**
* Deregisters all listeners from this broadcaster
*/
public void clear()
{
mListeners.clear();
}
/**
* Broadcasts the element to all registered listeners
*/
public void broadcast(T t)
{
for(Listener<T> listener : mListeners)
{
listener.receive(t);
}
}
}

View File

@@ -19,6 +19,7 @@
package io.github.dsheirer.sample;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.Source;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,6 +34,7 @@ public class OverflowableTransferQueue<E>
public enum State {NORMAL, OVERFLOW};
private IOverflowListener mOverflowListener;
private Source mSourceOverflowListener;
private LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
private AtomicInteger mCounter = new AtomicInteger();
@@ -73,6 +75,14 @@ public class OverflowableTransferQueue<E>
}
}
/**
* Removes and returns a single element from the head of the queue or null if the queue is empty
*/
public E poll()
{
return mQueue.poll();
}
/**
* Retrieves elements from the queue into the collection up to the maximum number of elements specified
*/
@@ -91,13 +101,21 @@ public class OverflowableTransferQueue<E>
}
/**
* Sets a listener to receive overflow state change events
* Sets a listener to receive overflow state change events.
*/
public void setOverflowListener(IOverflowListener listener)
{
mOverflowListener = listener;
}
/**
* Sets the source to receive overflow state change events (in addition to an IOverflow listener)
*/
public void setSourceOverflowListener(Source source)
{
mSourceOverflowListener = source;
}
/**
* Toggles the overflow state and broadcast state change to listener
@@ -110,6 +128,11 @@ public class OverflowableTransferQueue<E>
{
mOverflowListener.sourceOverflow(overflow);
}
if(mSourceOverflowListener != null)
{
mSourceOverflowListener.broadcastOverflowState(overflow);
}
}
}
@@ -125,4 +148,4 @@ public class OverflowableTransferQueue<E>
mOverflow.set(false);
}
}
}
}

View File

@@ -2,7 +2,6 @@ package io.github.dsheirer.sample;
public enum SampleType
{
COMPLEX, //Baseband
REAL, //Demodulated
COMPLEX_AND_REAL; //Baseband and Demodulated
COMPLEX, // I/Q
REAL; // Real
}

View File

@@ -0,0 +1,39 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.sample.complex;
import io.github.dsheirer.sample.Listener;
public interface IComplexBufferProvider
{
/**
* Adds the listener to receive complex buffer samples
*/
void addComplexBufferListener(Listener<ComplexBuffer> listener);
/**
* Removes the listener from receiving complex buffer samples
*/
void removeComplexBufferListener(Listener<ComplexBuffer> listener);
/**
* Indicates if there are any complex buffer listeners registered
*/
boolean hasComplexBufferListeners();
}

View File

@@ -6,8 +6,9 @@ import io.github.dsheirer.sample.complex.ComplexBuffer;
public abstract class ComplexSource extends Source implements Provider<ComplexBuffer>
{
public ComplexSource()
{
super( SampleType.COMPLEX );
}
@Override
public SampleType getSampleType()
{
return SampleType.COMPLEX;
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source;
import io.github.dsheirer.sample.Listener;
/**
* Interface for broadcasting changes to frequency, bandwidth, sample rate,
* and actual sample rate values to all registered listeners.
*
* Note: broadcasted frequency should be the uncorrected, or displayable
* frequency value. All frequency correction aspects should be handled within
* the device that implements the tuning of the frequency.
*/
public interface ISourceEventListener
{
Listener<SourceEvent> getSourceEventListener();
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source;
public interface ISourceEventProcessor
{
void process(SourceEvent event) throws SourceException;
}

View File

@@ -16,15 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source.tuner.frequency;
package io.github.dsheirer.source;
import io.github.dsheirer.sample.Listener;
/**
* Provider of Frequency Change Events
* Provider of Source Change Events
*/
public interface IFrequencyChangeProvider
public interface ISourceEventProvider
{
void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener );
void removeFrequencyChangeListener();
void setSourceEventListener(Listener<SourceEvent> listener );
void removeSourceEventListener();
}

View File

@@ -54,4 +54,11 @@ public class InvalidFrequencyException extends SourceException
{
return mValidFrequency;
}
@Override
public String getMessage()
{
return super.getMessage() + " Invalid Frequency: " + getInvalidFrequency() +
" - current valid frequency:" + getValidFrequency();
}
}

View File

@@ -7,8 +7,9 @@ import io.github.dsheirer.sample.real.RealBuffer;
public abstract class RealSource extends Source implements Provider<RealBuffer>
{
public RealSource()
{
super( SampleType.REAL );
}
@Override
public SampleType getSampleType()
{
return SampleType.REAL;
}
}

View File

@@ -17,42 +17,53 @@
******************************************************************************/
package io.github.dsheirer.source;
import io.github.dsheirer.channel.heartbeat.IHeartbeatProvider;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
/**
* Abstract class to define the minimum functionality of a sample data provider.
*/
public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider,
IHeartbeatProvider
public abstract class Source extends Module implements ISourceEventListener, ISourceEventProvider, IHeartbeatProvider
{
protected SampleType mSampleType;
private HeartbeatManager mHeartbeatManager = new HeartbeatManager();
/**
* Heartbeat manager for this source
*/
public HeartbeatManager getHeartbeatManager()
{
return mHeartbeatManager;
}
protected IOverflowListener mOverflowListener;
public Source( SampleType sampleType )
{
mSampleType = sampleType;
}
public abstract int getSampleRate() throws SourceException;
public abstract long getFrequency() throws SourceException;
/**
* Indicates the type of samples provided by this source: real or complex
*/
public abstract SampleType getSampleType();
/**
* Sample rate provided by this source
*
* @throws SourceException if there is an issue determining the sample rate for this source
*/
public abstract double getSampleRate();
/**
* Center frequency for this source in Hertz
*
* @throws SourceException if there is an issue in determining the center frequency for this source
*/
public abstract long getFrequency();
/**
* Process any cleanup actions to prepare for garbage collection of this source
*/
public abstract void dispose();
public SampleType getSampleType()
{
return mSampleType;
}
public void setSampleType( SampleType sampleType )
{
mSampleType = sampleType;
}
/**
* Registers the listener to receive overflow state changes. Use null argument to clear the listener
@@ -65,9 +76,9 @@ public abstract class Source extends Module implements IFrequencyChangeListener,
/**
* Broadcasts an overflow state
*
* @param overflow true if overlow, false if normal
* @param overflow true if overflow, false if normal
*/
protected void broadcastOverflowState(boolean overflow)
public void broadcastOverflowState(boolean overflow)
{
if(mOverflowListener != null)
{

View File

@@ -0,0 +1,202 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source;
public class SourceEvent
{
public enum Event
{
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE,
NOTIFICATION_FREQUENCY_CHANGE,
NOTIFICATION_FREQUENCY_CORRECTION_CHANGE,
NOTIFICATION_POLYPHASE_CHANNEL_COUNT_CHANGE,
NOTIFICATION_SAMPLE_RATE_CHANGE,
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
REQUEST_FREQUENCY_CHANGE,
REQUEST_SOURCE_DISPOSE,
REQUEST_START_SAMPLE_STREAM,
REQUEST_STOP_SAMPLE_STREAM;
}
private Event mEvent;
private Number mValue;
private Source mSource;
/**
* Private constructor. Use the static constructor methods to create an event.
*/
private SourceEvent(Event event, Number value)
{
mEvent = event;
mValue = value;
}
/**
* Private constructor. Use the static constructor methods to create an event.
*/
private SourceEvent(Event event, Source source)
{
mEvent = event;
mSource = source;
}
/**
* Event description.
*/
public Event getEvent()
{
return mEvent;
}
/**
* Value associated with the event.
*/
public Number getValue()
{
return mValue;
}
public boolean hasValue()
{
return mValue != null;
}
/**
* Source associated with the event
*/
public Source getSource()
{
return mSource;
}
/**
* Indicates if this event contains an optional source
*/
public boolean hasSource()
{
return mSource != null;
}
/**
* Creates a new frequency change event
*
* @param frequency in hertz
*/
public static SourceEvent frequencyChange(long frequency)
{
return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency);
}
/**
* Creates a new frequency correction change event
*
* @param frequencyCorrection in hertz
*/
public static SourceEvent frequencyCorrectionChange(long frequencyCorrection)
{
return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CORRECTION_CHANGE, frequencyCorrection);
}
/**
* Creates a new channel frequency correction change event
*
* @param frequencyCorrection in hertz
*/
public static SourceEvent channelFrequencyCorrectionChange(long frequencyCorrection)
{
return new SourceEvent(Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequencyCorrection);
}
/**
* Creates a new sample rate change event
*
* @param sampleRate in hertz
*/
public static SourceEvent sampleRateChange(double sampleRate)
{
return new SourceEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, sampleRate);
}
/**
* Creates a new channel sample rate change event
*
* @param sampleRate in hertz
*/
public static SourceEvent channelSampleRateChange(double sampleRate)
{
return new SourceEvent(Event.NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE, sampleRate);
}
/**
* Creates a new channel count change event
*
* @param channelCount
*/
public static SourceEvent channelCountChange(int channelCount)
{
return new SourceEvent(Event.NOTIFICATION_POLYPHASE_CHANNEL_COUNT_CHANGE, channelCount);
}
/**
* Creates a new frequency change request event
*
* @param frequency requested
*/
public static SourceEvent frequencyRequest(long frequency)
{
return new SourceEvent(Event.REQUEST_FREQUENCY_CHANGE, frequency);
}
/**
* Creates a new channel frequency correction change request event
*
* @param frequency requested
*/
public static SourceEvent channelFrequencyCorrectionRequest(long frequency)
{
return new SourceEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequency);
}
/**
* Creates a new start sample stream request event
*/
public static SourceEvent startSampleStream(Source source)
{
return new SourceEvent(Event.REQUEST_START_SAMPLE_STREAM, source);
}
/**
* Creates a new stop sample stream request event
*/
public static SourceEvent stopSampleStream(Source source)
{
return new SourceEvent(Event.REQUEST_STOP_SAMPLE_STREAM, source);
}
/**
* Creates a request to dispose of the source. This is normally used for TunerChannelSource to notify the
* parent source manager that the channel is no longer needed
*/
public static SourceEvent sourceDisposeRequest(Source source)
{
return new SourceEvent(Event.REQUEST_SOURCE_DISPOSE, source);
}
}

View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SourceEventListenerToProcessorAdapter implements Listener<SourceEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(SourceEventListenerToProcessorAdapter.class);
private ISourceEventProcessor mSourceEventProcessor;
/**
* Adapter to implement ISourceEventListener/Listener<SourceEvent> and adapt to a source event processor
*/
public SourceEventListenerToProcessorAdapter(ISourceEventProcessor sourceEventProcessor)
{
mSourceEventProcessor = sourceEventProcessor;
}
public void dispose()
{
mSourceEventProcessor = null;
}
@Override
public void receive(SourceEvent sourceEvent)
{
if(mSourceEventProcessor != null)
{
try
{
mSourceEventProcessor.process(sourceEvent);
}
catch(SourceException e)
{
mLog.error("Error while transferring source event to source event processor [" +
mSourceEventProcessor.getClass() + "]", e);
}
}
}
}

View File

@@ -1,26 +1,23 @@
/*******************************************************************************
* 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-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source.config;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannel.Type;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
public class SourceConfigRecording extends SourceConfiguration
{
@@ -59,11 +56,11 @@ public class SourceConfigRecording extends SourceConfiguration
{
return getRecordingAlias();
}
public TunerChannel getTunerChannel()
{
/* Create a tuner channel with a default bandwidth of 15 kHz */
return new TunerChannel( Type.LOCKED, mFrequency, 15000 );
}
public TunerChannel getTunerChannel()
{
/* Create a tuner channel with a default bandwidth of 12.5 kHz */
return new TunerChannel(mFrequency, 12500);
}
}

View File

@@ -21,15 +21,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannel.Type;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import java.text.DecimalFormat;
@JsonSubTypes.Type(value = SourceConfigTuner.class, name = "sourceConfigTuner")
public class SourceConfigTuner extends SourceConfiguration
{
private static DecimalFormat sFORMAT = new DecimalFormat("0.00000");
private static DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private long mFrequency = 0;
private int mBandwidth = 12500;
@@ -62,12 +61,12 @@ public class SourceConfigTuner extends SourceConfiguration
@Override
public String getDescription()
{
return sFORMAT.format((double)mFrequency / 1000000.0d) + " MHz";
return FREQUENCY_FORMAT.format((double)mFrequency / 1000000.0d) + " MHz";
}
@JsonIgnore
public TunerChannel getTunerChannel()
{
return new TunerChannel(Type.LOCKED, mFrequency, mBandwidth);
return new TunerChannel(mFrequency, mBandwidth);
}
}

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.channel.heartbeat;
package io.github.dsheirer.source.heartbeat;
public class Heartbeat
{

View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source.heartbeat;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
public class HeartbeatManager
{
private static final Heartbeat HEARTBEAT = new Heartbeat();
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
/**
* Broadcasts heartbeat as commanded and handles listener registration details.
*/
public HeartbeatManager()
{
}
/**
* Sends a heartbeat to all registered listeners
*/
public void broadcast()
{
mHeartbeatBroadcaster.broadcast(HEARTBEAT);
}
/**
* Adds the listener to receive heartbeats
*/
public void addHeartbeatListener(Listener<Heartbeat> listener)
{
mHeartbeatBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving heartbeats
* @param listener
*/
public void removeHeartbeatListener(Listener<Heartbeat> listener)
{
mHeartbeatBroadcaster.removeListener(listener);
}
}

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.channel.heartbeat;
package io.github.dsheirer.source.heartbeat;
import io.github.dsheirer.sample.Listener;

View File

@@ -16,13 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.channel.heartbeat;
import io.github.dsheirer.sample.Listener;
package io.github.dsheirer.source.heartbeat;
public interface IHeartbeatProvider
{
public void setHeartbeatListener(Listener<Heartbeat> listener);
public void removeHeartbeatListener();
public HeartbeatManager getHeartbeatManager();
}

View File

@@ -17,14 +17,11 @@
******************************************************************************/
package io.github.dsheirer.source.mixer;
import io.github.dsheirer.channel.heartbeat.Heartbeat;
import io.github.dsheirer.dsp.gain.AutomaticGainControl;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.adapter.ISampleAdapter;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.source.RealSource;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,7 +31,6 @@ import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
public class RealMixerSource extends RealSource
@@ -49,9 +45,6 @@ public class RealMixerSource extends RealSource
private AudioFormat mAudioFormat;
private int mBytesPerFrame = 0;
private ISampleAdapter mSampleAdapter;
private AutomaticGainControl mAGC = new AutomaticGainControl();
private Listener<Heartbeat> mHeartbeatListener;
private static final Heartbeat HEARTBEAT = new Heartbeat();
private CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = new CopyOnWriteArrayList<Listener<RealBuffer>>();
@@ -76,44 +69,24 @@ public class RealMixerSource extends RealSource
mSampleAdapter = sampleAdapter;
}
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public void removeSourceEventListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
/**
* Registers the listener to receive heartbeats from this source.
*
* @param listener to receive heartbeats
*/
@Override
public void setHeartbeatListener(Listener<Heartbeat> listener)
{
mHeartbeatListener = listener;
}
/**
* Removes the currently registered heartbeat listener
*/
@Override
public void removeHeartbeatListener()
{
mHeartbeatListener = null;
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
//Not implemented
return null;
}
@Override
public void reset()
@@ -121,7 +94,7 @@ public class RealMixerSource extends RealSource
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
}
@@ -176,11 +149,11 @@ public class RealMixerSource extends RealSource
}
}
public int getSampleRate()
public double getSampleRate()
{
if(mTargetDataLine != null)
{
return (int)mTargetDataLine.getFormat().getSampleRate();
return mTargetDataLine.getFormat().getSampleRate();
}
else
{
@@ -191,7 +164,7 @@ public class RealMixerSource extends RealSource
/**
* Returns the frequency of this source. Default is 0.
*/
public long getFrequency() throws SourceException
public long getFrequency()
{
return mFrequency;
}
@@ -220,11 +193,7 @@ public class RealMixerSource extends RealSource
{
if(mRunning.compareAndSet(false, true))
{
//Send a heartbeat every time we run
if(mHeartbeatListener != null)
{
mHeartbeatListener.receive(HEARTBEAT);
}
getHeartbeatManager().broadcast();
if(mTargetDataLine == null)
{

View File

@@ -1,26 +1,37 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source.recording;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.ITunerChannelProvider;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannelSource;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.io.File;
import java.util.ArrayList;
import java.util.concurrent.RejectedExecutionException;
import java.util.List;
public class Recording implements Comparable<Recording>,
IFrequencyChangeProcessor,
ITunerChannelProvider
public class Recording implements Comparable<Recording>, ISourceEventProcessor, ITunerChannelProvider
{
private RecordingConfiguration mConfiguration;
private ArrayList<TunerChannelSource> mTunerChannels =
new ArrayList<TunerChannelSource>();
private List<TunerChannelSource> mTunerChannels = new ArrayList<TunerChannelSource>();
@SuppressWarnings( "unused" )
private long mCenterFrequency;
@@ -65,8 +76,7 @@ public class Recording implements Comparable<Recording>,
}
@Override
public TunerChannelSource getChannel( TunerChannel channel )
throws RejectedExecutionException, SourceException
public TunerChannelSource getChannel( TunerChannel channel )
{
// TODO Auto-generated method stub
return null;
@@ -89,9 +99,9 @@ public class Recording implements Comparable<Recording>,
}
@Override
public void frequencyChanged( FrequencyChangeEvent event ) throws SourceException
public void process(SourceEvent event ) throws SourceException
{
if( event.getEvent() == Event.NOTIFICATION_FREQUENCY_CHANGE )
if( event.getEvent() == SourceEvent.Event.NOTIFICATION_FREQUENCY_CHANGE )
{
long frequency = event.getValue().longValue();
@@ -103,7 +113,7 @@ public class Recording implements Comparable<Recording>,
for( TunerChannelSource channel: mTunerChannels )
{
channel.frequencyChanged( event );
channel.process( event );
}
}
}

View File

@@ -22,8 +22,8 @@ import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.config.SourceConfigRecording;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannelSource;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,8 +33,7 @@ import java.util.List;
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>();
@@ -50,12 +49,12 @@ public class RecordingSourceManager
* Iterates current recordings to get a tuner channel source for the frequency
* specified in the channel config's source config object
*/
public Source getSource(SourceConfiguration config, int bandwidth )
public Source getSource( SourceConfiguration config, int bandwidth )
throws SourceException
{
TunerChannelSource retVal = null;
if( config instanceof SourceConfigRecording)
if( config instanceof SourceConfigRecording )
{
SourceConfigRecording configRecording = (SourceConfigRecording)config;

View File

@@ -18,6 +18,8 @@
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.util.concurrent.RejectedExecutionException;
@@ -25,18 +27,14 @@ public interface ITunerChannelProvider
{
/**
* Returns a tuner frequency channel source, tuned to the correct frequency
*
* @param frequency - desired frequency
*
*
* @return - source for 48k sample rate
*/
public abstract TunerChannelSource getChannel( TunerChannel channel )
public abstract TunerChannelSource getChannel(TunerChannel channel )
throws RejectedExecutionException, SourceException;
/**
* Releases the tuned channel resources
*
* @param channel - previously obtained tuner channel
*/
public abstract void releaseChannel( TunerChannelSource source );

View File

@@ -1,129 +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.source.tuner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.adapter.ISampleAdapter;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.ComplexSampleListListener;
import io.github.dsheirer.source.mixer.ComplexMixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.TargetDataLine;
import java.util.ArrayList;
public abstract class MixerTuner extends Tuner implements Listener<ComplexBuffer>
{
private final static Logger mLog =
LoggerFactory.getLogger( MixerTuner.class );
protected MixerTunerType mMixerTunerType;
protected ComplexMixer mComplexMixer;
protected ArrayList<ComplexSampleListListener> mListeners =
new ArrayList<ComplexSampleListListener>();
/**
* Wrapper class to add basic Tuner functionality to the ComplexMixer.
* Subclasses should couple this functionality with a tuner controller class
* to support tuning.
*/
public MixerTuner( String name,
TunerController tunerController,
MixerTunerType mixerTunerType,
TargetDataLine targetDataLine,
ISampleAdapter sampleAdapter )
{
super( name, tunerController );
mMixerTunerType = mixerTunerType;
mComplexMixer = new ComplexMixer( targetDataLine,
mMixerTunerType.getAudioFormat(),
name,
sampleAdapter,
(Listener<ComplexBuffer>)this );
}
public MixerTuner( String name,
TunerController tunerController,
MixerTunerDataLine mixerTuner,
ISampleAdapter sampleAdapter )
{
this( name,
tunerController,
mixerTuner.getMixerTunerType(),
mixerTuner.getTargetDataLine(),
sampleAdapter );
}
public void dispose()
{
mComplexMixer.stop();
super.dispose();
}
public MixerTunerType getMixerTunerType()
{
return mMixerTunerType;
}
public TargetDataLine getTargetDataLine()
{
return mComplexMixer.getTargetDataLine();
}
/**
* Dispatches the received complex buffers from the mComplexMixer to all
* listeners registered on the parent Tuner class
*/
@Override
public void receive( ComplexBuffer buffer )
{
super.broadcast( buffer );
}
/**
* Overrides the parent Tuner class listener management method to allow
* starting the buffer processing thread
*/
@Override
public void addListener( Listener<ComplexBuffer> listener )
{
super.addListener( listener );
/* We can call start multiple times -- it ignores additional requests */
mComplexMixer.start();
}
/**
* Overrides the parent Tuner class listener management method to allow
* stopping the buffer processing thread when there are no more listeners
*/
@Override
public void removeListener( Listener<ComplexBuffer> listener )
{
super.removeListener( listener );
if( mSampleListeners.isEmpty() )
{
mComplexMixer.stop();
}
}
}

View File

@@ -17,196 +17,145 @@
******************************************************************************/
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.manager.AbstractSourceManager;
import io.github.dsheirer.source.tuner.manager.TunerSourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.RejectedExecutionException;
/**
* Tuner - provides tuner channel sources, representing a channel frequency
*/
public abstract class Tuner implements ITunerChannelProvider
public abstract class Tuner
{
private final static Logger mLog = LoggerFactory.getLogger( Tuner.class );
private String mName;
private TunerController mTunerController;
/**
* Sample Listeners - these will typically be the DFT processor for spectral
* display, or it will be one or more tuner channel sources
*/
protected List<Listener<ComplexBuffer>> mSampleListeners =
new CopyOnWriteArrayList<>();
protected List<Listener<TunerEvent>> mTunerChangeListeners =
new ArrayList<>();
public Tuner( String name, TunerController tunerController )
{
mName = name;
mTunerController = tunerController;
private final static Logger mLog = LoggerFactory.getLogger(Tuner.class);
//Rebroadcast frequency and sample rate change events as tuner events
mTunerController.addListener( new IFrequencyChangeProcessor()
{
@Override
public void frequencyChanged( FrequencyChangeEvent event )
{
switch( event.getEvent() )
{
case NOTIFICATION_FREQUENCY_CHANGE:
broadcast( new TunerEvent( Tuner.this,
TunerEvent.Event.FREQUENCY ));
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
broadcast( new TunerEvent( Tuner.this,
TunerEvent.Event.SAMPLE_RATE ));
break;
default:
break;
}
}
} );
}
public TunerController getTunerController()
{
return mTunerController;
}
public String toString()
{
return mName;
}
public void dispose()
{
mSampleListeners.clear();
}
public void setName( String name )
{
mName = name;
}
/**
* Unique identifier for this tuner, used to lookup a tuner configuration
* from the settings manager.
*
* @return - unique identifier like a serial number, or a usb bus location
* or ip address and port. Return some form of unique identification that
* allows this tuner to be identified from among several of the same types
* of tuners.
*/
public abstract String getUniqueID();
protected Broadcaster<TunerEvent> mTunerEventBroadcaster = new Broadcaster<>();
private TunerController mTunerController;
private String mName;
private AbstractSourceManager mTunerSourceManager;
/**
* @return - tuner class enum entry
*/
public abstract TunerClass getTunerClass();
/**
* @return - tuner type enum entry
*/
public abstract TunerType getTunerType();
/**
* Name of this tuner object
* @return - string name of this tuner object
*/
public String getName()
{
return mName;
}
/**
* Sample size in bits
*/
public abstract double getSampleSize();
/**
* Returns a tuner frequency channel source, tuned to the correct frequency
*
* Note: ensure the concrete implementation registers the returned
* tuner channel source as a listener on the tuner, to receive frequency
* change events.
*
* @param frequency - desired frequency
*
* @return - source for 48k sample rate
*/
public abstract TunerChannelSource getChannel( TunerChannel channel )
throws RejectedExecutionException, SourceException;
/**
* Releases the tuned channel resources
*
* Note: ensure the concrete implementation unregisters the tuner channel
* source from the tuner as a frequency change listener
*
* @param channel - previously obtained tuner channel
*/
public abstract void releaseChannel( TunerChannelSource source );
/**
* Registers the listener to receive complex float sample arrays
*/
public void addListener( Listener<ComplexBuffer> listener )
/**
* Abstract tuner class.
* @param name of the tuner
* @param tunerController for the tuner
*/
public Tuner(String name, TunerController tunerController)
{
mSampleListeners.add( listener );
}
mName = name;
mTunerController = tunerController;
/**
* Removes the registered listener
*/
public void removeListener( Listener<ComplexBuffer> listener )
{
mSampleListeners.remove( listener );
mTunerSourceManager = new TunerSourceManager(mTunerController);
//Rebroadcast frequency and sample rate change events as tuner events
mTunerController.addListener(new ISourceEventProcessor()
{
@Override
public void process(SourceEvent event)
{
switch(event.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
broadcast(new TunerEvent(Tuner.this, Event.FREQUENCY));
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
broadcast(new TunerEvent(Tuner.this, Event.SAMPLE_RATE));
break;
default:
break;
}
}
});
}
/**
* Broadcasts the samples to all registered listeners
* Source Manager. Provides access to registering for complex buffer samples and source event notifications.
*/
public void broadcast( ComplexBuffer sampleBuffer )
public AbstractSourceManager getSourceManager()
{
for( Listener<ComplexBuffer> listener: mSampleListeners )
{
listener.receive( sampleBuffer );
}
return mTunerSourceManager;
}
/**
* Registers the listener
*/
public void addTunerChangeListener( Listener<TunerEvent> listener )
public TunerController getTunerController()
{
mTunerChangeListeners.add( listener );
return mTunerController;
}
/**
* Removes the registered listener
*/
public void removeTunerChangeListener( Listener<TunerEvent> listener )
public String toString()
{
mTunerChangeListeners.remove( listener );
return mName;
}
public void dispose()
{
}
public void setName(String name)
{
mName = name;
}
/**
* Unique identifier for this tuner, used to lookup a tuner configuration
* from the settings manager.
*
* @return - unique identifier like a serial number, or a usb bus location
* or ip address and port. Return some form of unique identification that
* allows this tuner to be identified from among several of the same types
* of tuners.
*/
public abstract String getUniqueID();
/**
* @return - tuner class enum entry
*/
public abstract TunerClass getTunerClass();
/**
* @return - tuner type enum entry
*/
public abstract TunerType getTunerType();
/**
* Name of this tuner object
*
* @return - string name of this tuner object
*/
public String getName()
{
return mName;
}
/**
* Sample size in bits
*/
public abstract double getSampleSize();
/**
* Registers the listener
*/
public void addTunerChangeListener(Listener<TunerEvent> listener)
{
mTunerEventBroadcaster.addListener(listener);
}
/**
* Removes the registered listener
*/
public void removeTunerChangeListener(Listener<TunerEvent> listener)
{
mTunerEventBroadcaster.removeListener(listener);
}
/**
* Broadcasts the tuner change event
*/
public void broadcast( TunerEvent event )
public void broadcast(TunerEvent tunerEvent)
{
for( Listener<TunerEvent> listener: mTunerChangeListeners )
{
listener.receive( event );
}
mTunerEventBroadcaster.broadcast(tunerEvent);
}
}
}

View File

@@ -1,122 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.source.tuner;
public class TunerChannel implements Comparable<TunerChannel>
{
private Type mType;
private long mFrequency;
private int mBandwidth;
public TunerChannel( Type type, long frequency, int bandwidth )
{
mType = type;
mFrequency = frequency;
mBandwidth = bandwidth;
}
public Type getType()
{
return mType;
}
public void setType( Type type )
{
mType = type;
}
public long getFrequency()
{
return mFrequency;
}
public void setFrequency( long frequency )
{
mFrequency = frequency;
}
public int getBandwidth()
{
return mBandwidth;
}
public void setBandwidth( int bandwidth )
{
mBandwidth = bandwidth;
}
public long getMinFrequency()
{
return mFrequency - ( mBandwidth / 2 );
}
public long getMaxFrequency()
{
return mFrequency + ( mBandwidth / 2 );
}
/**
* Indicates if any part of this tuner channel overlaps the specified range
*/
public boolean overlaps( long minimum, long maximum )
{
long channelMin = getMinFrequency();
long channelMax = getMaxFrequency();
return ( ( minimum <= channelMin && channelMin <= maximum ) ||
( minimum <= channelMax && channelMax <= maximum ) ||
( minimum <= channelMin && channelMax <= maximum ) ||
( channelMin <= minimum && maximum <= channelMax ) );
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "Channel: " );
sb.append( getMinFrequency() );
sb.append( "-" );
sb.append( getMaxFrequency() );
return sb.toString();
}
@Override
public int compareTo( TunerChannel otherLock )
{
if( this.equals( otherLock ) )
{
return 0;
}
else if( mFrequency < otherLock.getFrequency() )
{
return -1;
}
else
{
return 1;
}
}
public enum Type
{
LOCKED, //Use for decoding channels, so that they cannot be tuned out of range
TRAFFIC, //Like locked, but for temporary allocated traffic channels
TUNABLE; //Tunable -- can be tuned out of range
}
}

View File

@@ -1,27 +0,0 @@
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.source.SourceException;
import java.util.concurrent.RejectedExecutionException;
public interface TunerChannelProvider
{
/**
* Returns a tuner frequency channel source, tuned to the correct frequency
*
* @param frequency - desired frequency
*
* @return - source for 48k sample rate
*/
public abstract TunerChannelSource getChannel( TunerChannel channel )
throws RejectedExecutionException, SourceException;
/**
* Releases the tuned channel resources
*
* @param channel - previously obtained tuner channel
*/
public abstract void releaseChannel( TunerChannelSource source );
}

View File

@@ -1,572 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.channel.heartbeat.Heartbeat;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.Buffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.ComplexSource;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class TunerChannelSource extends ComplexSource implements IFrequencyChangeProcessor, Listener<ComplexBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
//Maximum number of filled buffers for the blocking queue
private static final int BUFFER_MAX_CAPACITY = 300;
//Threshold for resetting buffer overflow condition
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
private static int CHANNEL_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000;
private static final Heartbeat HEARTBEAT = new Heartbeat();
private OverflowableTransferQueue<ComplexBuffer> mBuffer;
private Tuner mTuner;
private TunerChannel mTunerChannel;
private Oscillator mMixer;
private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ComplexBuffer> mListener;
private Listener<Heartbeat> mHeartbeatListener;
private IFrequencyChangeProcessor mFrequencyChangeProcessor;
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
private ScheduledFuture<?> mTaskHandle;
private long mTunerFrequency = 0;
private int mTunerSampleRate;
private int mChannelFrequencyCorrection = 0;
private DecimationProcessor mDecimationProcessor = new DecimationProcessor();
private AtomicBoolean mRunning = new AtomicBoolean();
private boolean mExpended = false;
/**
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
* tuner down to a 48 kHz IQ channel rate.
*
* Note: this class can only be used once (started and stopped) and a new
* tuner channel source must be requested from the tuner once this object
* has been stopped. This is because channels are managed dynamically and
* center tuned frequency may have changed since this source was obtained
* and thus the tuner might no longer be able to source this channel once it
* has been stopped.
*
* @param tuner to obtain wideband IQ samples from
* @param tunerChannel specifying the center frequency for the DDC
* @throws RejectedExecutionException if the thread pool manager cannot
* accept the decimation processing task
* @throws SourceException if the tuner has an issue providing IQ samples
*/
public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel) throws RejectedExecutionException, SourceException
{
mTuner = tuner;
mTunerChannel = tunerChannel;
mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
mTunerFrequency = mTuner.getTunerController().getFrequency();
mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
/* Setup the frequency translator to the current source frequency */
long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency();
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
/* Fire a sample rate change event to setup the decimation chain */
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
mTuner.getTunerController().getSampleRate()));
}
/**
* Overrides the default source overflow listener management to delegate responsibility to the overflow buffer
*/
@Override
public void setOverflowListener(IOverflowListener listener)
{
mBuffer.setOverflowListener(listener);
}
public void start(ScheduledExecutorService executor)
{
if(mExpended)
{
throw new IllegalStateException("Attempt to re-start an expended tuner channel source. TunerChannelSource" +
" objects can only be used once. ");
}
if(mRunning.compareAndSet(false, true))
{
//Broadcast current frequency and sample rate settings so all downstream components are aware
mDownstreamFrequencyEventProcessor.broadcastCurrentFrequency();
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
/* Finally, register to receive samples from the tuner */
mTuner.addListener((Listener<ComplexBuffer>) this);
}
else
{
mLog.warn("Attempt to start() an already running tuner channel source was ignored");
}
}
@Override
public void reset()
{
}
@Override
public void stop()
{
if(mRunning.compareAndSet(true, false))
{
mTuner.releaseChannel(this);
mDecimationProcessor.shutdown();
if(mTaskHandle != null)
{
mTaskHandle.cancel(true);
mTaskHandle = null;
}
mBuffer.clear();
mExpended = true;
}
else
{
mLog.warn("Attempt to stop() an already stopped tuner channel source was ignored");
}
}
@Override
public void dispose()
{
if(!mRunning.get())
{
/* Tell the tuner to release/unregister our resources */
mTuner.getTunerController().removeListener(this);
}
}
/**
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
* @param correction current frequency correction value.
*/
private void setFrequencyCorrection(int correction)
{
mChannelFrequencyCorrection = correction;
updateMixerFrequencyOffset();
mDownstreamFrequencyEventProcessor.broadcast(
new FrequencyChangeEvent( Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
}
public Tuner getTuner()
{
return mTuner;
}
public TunerChannel getTunerChannel()
{
return mTunerChannel;
}
@Override
public void receive(ComplexBuffer buffer)
{
if(mRunning.get())
{
mBuffer.offer(buffer);
}
}
public void setFrequencyChangeListener(IFrequencyChangeProcessor processor)
{
mFrequencyChangeProcessor = processor;
}
@Override
public void setListener(Listener<ComplexBuffer> listener)
{
/* Save a pointer to the listener so that if we have to change the
* decimation filter, we can re-add the listener */
mListener = listener;
mDecimationFilter.setListener(listener);
}
@Override
public void removeListener(Listener<ComplexBuffer> listener)
{
mDecimationFilter.removeListener();
}
/**
* Handler for frequency change events received from the tuner and channel
* frequency correction events received from the channel consumer/listener
*/
@Override
public void frequencyChanged(FrequencyChangeEvent event) throws SourceException
{
// Echo the event to the registered event listener
if(mFrequencyChangeProcessor != null)
{
mFrequencyChangeProcessor.frequencyChanged(event);
}
switch(event.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
mTunerFrequency = event.getValue().longValue();
updateMixerFrequencyOffset();
//Reset frequency correction so that downstream components can recalculate the value
setFrequencyCorrection(0);
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
int sampleRate = event.getValue().intValue();
setSampleRate(sampleRate);
break;
default:
break;
}
}
/**
* Updates the sample rate to the requested value and notifies any downstream components of the change
* @param sampleRate to set
*/
private void setSampleRate(int sampleRate)
{
if(mTunerSampleRate != sampleRate)
{
mMixer.setSampleRate(sampleRate);
/* Get new decimation filter */
mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
/* re-add the original output listener */
mDecimationFilter.setListener(mListener);
mTunerSampleRate = sampleRate;
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
}
}
/**
* Calculates the local mixer frequency offset from the tuned frequency,
* channel's requested frequency, and channel frequency correction.
*/
private void updateMixerFrequencyOffset()
{
long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection;
mMixer.setFrequency(offset);
}
public int getSampleRate() throws SourceException
{
return CHANNEL_RATE;
}
public long getFrequency() throws SourceException
{
return mTunerChannel.getFrequency();
}
/**
* Implements IFrequencyChangeProvider to enable this source to broadcast frequency change events to downstream
* listeners.
*
* @param listener to receive downstream events
*/
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mDownstreamFrequencyEventProcessor.setFrequencyChangeListener(listener);
}
/**
* Implements IFrequencyChangeProvider to remove the frequency change listener from receiving down-stream frequency
* change events.
*/
@Override
public void removeFrequencyChangeListener()
{
mDownstreamFrequencyEventProcessor.removeFrequencyChangeListener();
}
/**
* Implements IFrequencyChangeListener to receive frequency change events containing requests from downstream
* listeners to change frequency values.
* @return listener
*/
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener();
}
/**
* Registers the listener to receive heartbeats from this source.
* @param listener to receive heartbeats
*/
@Override
public void setHeartbeatListener(Listener<Heartbeat> listener)
{
mHeartbeatListener = listener;
}
/**
* Removes the currently registered heartbeat listener
*/
@Override
public void removeHeartbeatListener()
{
mHeartbeatListener = null;
}
/**
* Managers frequency change requests and notifications from/to any downstream component. Downstream
* components are those that receive samples from this tuner channel source. These downstream components will be
* notified of any frequency or sample rate change events and will also be able to request frequency correction
* updates.
*/
public class DownstreamProcessor implements IFrequencyChangeListener, IFrequencyChangeProvider,
Listener<FrequencyChangeEvent>
{
//Listener to receive downstream events
private Listener<FrequencyChangeEvent> mListener;
/**
* Broadcasts the frequency change event to the downstream frequency change listener
* @param event to broadcast
*/
public void broadcast(FrequencyChangeEvent event)
{
if(mListener != null)
{
mListener.receive(event);
}
}
/**
* Broadcasts the current frequency of this tuner channel source to the downstream listener
*/
public void broadcastCurrentFrequency()
{
try
{
long frequency = getFrequency();
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency));
}
catch(SourceException se)
{
mLog.error("Error obtaining frequency from tuner to broadcast downstream");
}
}
/**
* Broadcasts the current decimated sample rate of this tuner channel source
*/
public void broadcastCurrentSampleRate()
{
try
{
//Note: downstream sample rate is currently a fixed value -- it will change in the future
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, getSampleRate()));
}
catch(SourceException se)
{
mLog.error("Error obtaining sample rate from tuner to broadcast downstream");
}
}
/**
* Sets the downstream listener to receive frequency change events from this tuner channel source
* @param listener to receive events
*/
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mListener = listener;
}
/**
* Removes the downstream listener from receiving frequency change events.
*/
@Override
public void removeFrequencyChangeListener()
{
mListener = null;
}
/**
* Listener for receiving frequency change events from downstream components
*/
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return this;
}
/**
* Processes frequency change events from downstream components.
* @param event to process
*/
@Override
public void receive(FrequencyChangeEvent event)
{
switch(event.getEvent())
{
//Frequency correction requests are the only change requests supported from downstream components
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setFrequencyCorrection(event.getValue().intValue());
break;
}
}
}
/**
* Decimates an inbound buffer of I/Q samples from the source down to the
* standard 48000 channel sample rate
*/
public class DecimationProcessor implements Runnable
{
private boolean mProcessing = true;
private List<ComplexBuffer> mSampleBuffers = new ArrayList<ComplexBuffer>();
public void shutdown()
{
mProcessing = false;
}
@Override
public void run()
{
/* General exception handler so that any errors won't kill the
* decimation thread and cause the input buffers to fill up and
* run the program out of memory */
try
{
if(mProcessing)
{
//Send a heartbeat every time this runs to allow downstream components to perform periodic
//state monitoring functions on this thread
if(mHeartbeatListener != null)
{
mHeartbeatListener.receive(HEARTBEAT);
}
mBuffer.drainTo(mSampleBuffers, 20);
for(Buffer buffer : mSampleBuffers)
{
/* Check to see if we've been shutdown */
if(!mProcessing)
{
mBuffer.clear();
return;
}
else
{
float[] samples = buffer.getSamples();
/* We make a copy of the buffer so that we don't affect
* anyone else that is using the same buffer, like other
* channels or the spectral display */
float[] translated = new float[samples.length];
/* Perform frequency translation */
for(int x = 0; x < samples.length; x += 2)
{
mMixer.rotate();
translated[x] = Complex.multiplyInphase(
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
translated[x + 1] = Complex.multiplyQuadrature(
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
}
if(mProcessing)
{
final ComplexPrimeCICDecimate filter = mDecimationFilter;
filter.receive(new ComplexBuffer(translated));
}
}
}
mSampleBuffers.clear();
}
}
catch(Exception e)
{
/* Only log the stack trace if we're still processing */
if(mProcessing)
{
mLog.error("Error encountered during decimation process", e);
}
}
catch(Throwable throwable)
{
mLog.error("Code error encountered during decimation process - channel thread will probably die", throwable);
}
/* Check to see if we've been shutdown */
if(!mProcessing)
{
mBuffer.clear();
mSampleBuffers.clear();
}
}
}
}

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