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

@@ -1,188 +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 audio;
import audio.squelch.ISquelchStateListener;
import audio.squelch.SquelchState;
import channel.metadata.Metadata;
import dsp.filter.design.FilterDesignException;
import dsp.filter.fir.FIRFilterSpecification;
import dsp.filter.fir.remez.RemezFIRFilterDesigner;
import dsp.filter.polyphase.PolyphaseFIRDecimatingFilter_RB;
import module.Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.IFilteredRealBufferListener;
import sample.real.RealBuffer;
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
* metadata in constructed audio packets.
*
* Incorporates audio squelch state listener to control if audio packets are
* broadcast or ignored.
*/
public class AudioModule extends Module implements IAudioPacketProvider, IFilteredRealBufferListener,
ISquelchStateListener, Listener<RealBuffer>
{
protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private static float[] mDecimationCoefficients;
static
{
//Band-pass filter to both decimate (48kHz to 8kHz) and block 0 - 300 Hz (LTR signalling).
FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
.sampleRate(48000)
.stopFrequency1(150)
.passFrequencyBegin(300)
.passFrequencyEnd(3500)
.stopFrequency2(4000)
.stopRipple(0.0004)
.passRipple(0.008)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
try
{
if(!designer.isValid())
{
throw new FilterDesignException("Couldn't design the audio decimation filter");
}
mDecimationCoefficients = designer.getImpulseResponse();
}
catch(FilterDesignException e)
{
mLog.debug("Error designing filter", e);
}
}
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
private Listener<AudioPacket> mAudioPacketListener;
private PolyphaseFIRDecimatingFilter_RB mAudioDecimationFilter;
private Metadata mMetadata;
public AudioModule(Metadata metadata)
{
mMetadata = metadata;
mAudioDecimationFilter = new PolyphaseFIRDecimatingFilter_RB(mDecimationCoefficients, 6, 2.0f);
mAudioDecimationFilter.setListener(new Listener<RealBuffer>()
{
@Override
public void receive(RealBuffer realBuffer)
{
if(mAudioPacketListener != null)
{
AudioPacket packet = new AudioPacket(realBuffer.getSamples(), mMetadata.copyOf());
mAudioPacketListener.receive(packet);
}
}
});
}
@Override
public void dispose()
{
mSquelchStateListener = null;
mAudioPacketListener = null;
}
@Override
public void reset()
{
}
@Override
public void start()
{
/* No start operations provided */
}
@Override
public void stop()
{
/* Issue an end audio packet in case a recorder is still rolling */
if(mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
}
}
/**
* Processes demodulated audio samples into audio packets with current audio
* metadata and sends to the registered listener
*/
@Override
public void receive(RealBuffer buffer)
{
if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH)
{
mAudioDecimationFilter.receive(buffer);
}
}
@Override
public Listener<RealBuffer> getFilteredRealBufferListener()
{
return this;
}
@Override
public void setAudioPacketListener(Listener<AudioPacket> listener)
{
mAudioPacketListener = listener;
}
@Override
public void removeAudioPacketListener()
{
mAudioPacketListener = null;
}
@Override
public Listener<SquelchState> getSquelchStateListener()
{
return mSquelchStateListener;
}
/**
* Wrapper for squelch state listener
*/
public class SquelchStateListener implements Listener<SquelchState>
{
@Override
public void receive(SquelchState state)
{
if(state == SquelchState.SQUELCH && mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
}
mSquelchState = state;
}
}
}

View File

@@ -1,233 +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 audio.broadcast;
import audio.AudioPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.AudioRecorder;
import sample.Listener;
import util.ThreadPool;
import util.TimeStamp;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class StreamManager implements Listener<AudioPacket>
{
private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class);
private static final long MAXIMUM_RECORDER_LIFESPAN_MILLIS = 30000; //30 seconds
private static AtomicInteger sNextRecordingNumber = new AtomicInteger();
private Listener<AudioRecording> mAudioRecordingListener;
private BroadcastFormat mBroadcastFormat;
private Path mTempDirectory;
private Map<Integer,AudioRecorder> mStreamRecorders = new HashMap<>();
private Runnable mRecorderMonitor;
private ScheduledFuture<?> mRecorderMonitorFuture;
private AtomicBoolean mRunning = new AtomicBoolean();
/**
* Stream manager processes all incoming audio packets and reassembles individual audio streams, converts audio
* to desired output format and persists each stream to disc. Each recording is capped at a maximum length to
* ensure that recordings don't run too long before they are streamed out and to ensure that inactive recordings
* are closed in a timely fashion.
*
* Completed streamable audio recordings are nominated to the output listener (for broadcast) upon completion
*
* @param listener to receive completed audio recordings
* @param tempDirectory where to store temporary audio recordings
*/
public StreamManager(Listener<AudioRecording> listener, BroadcastFormat broadcastFormat, Path tempDirectory)
{
assert (tempDirectory != null && Files.isDirectory(tempDirectory));
mAudioRecordingListener = listener;
mBroadcastFormat = broadcastFormat;
mTempDirectory = tempDirectory;
}
/**
* Starts the stream manager. Schedules a processor to monitor for inactive temporary stream recorders
* every 2 seconds.
*/
public void start()
{
if(mRunning.compareAndSet(false, true))
{
if(mRecorderMonitor == null)
{
mRecorderMonitor = new RecorderMonitor();
}
mRecorderMonitorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mRecorderMonitor,
0,2, TimeUnit.SECONDS);
}
}
/**
* Stops the stream manager. Stops the inactive temporary stream recorder monitoring thread.
*/
public void stop()
{
if(mRunning.compareAndSet(true, false))
{
if(mRecorderMonitorFuture != null)
{
mRecorderMonitorFuture.cancel(true);
}
synchronized(mStreamRecorders)
{
List<Integer> streamKeys = new ArrayList<>(mStreamRecorders.keySet());
for(Integer streamKey : streamKeys)
{
removeRecorder(streamKey);
}
}
}
}
@Override
public void receive(AudioPacket audioPacket)
{
if(mRunning.get() && audioPacket.hasMetadata())
{
synchronized(mStreamRecorders)
{
int channelMetadataID = audioPacket.getMetadata().getMetadataID();
AudioPacket.Type type = audioPacket.getType();
if(type == AudioPacket.Type.AUDIO)
{
if(mStreamRecorders.containsKey(channelMetadataID))
{
AudioRecorder recorder = mStreamRecorders.get(channelMetadataID);
if(recorder != null)
{
recorder.receive(audioPacket);
}
}
else
{
AudioRecorder recorder = BroadcastFactory.getAudioRecorder(getTemporaryRecordingPath(),
mBroadcastFormat);
recorder.start();
recorder.receive(audioPacket);
mStreamRecorders.put(channelMetadataID, recorder);
}
}
else if(type == AudioPacket.Type.END)
{
removeRecorder(channelMetadataID);
}
else
{
mLog.info("Unrecognized Audio Packet Type: " + type);
}
}
}
}
/**
* Removes the recorder associated with the source channel ID.
*
* Note: this method invocation is not thread safe and must be invoked by a thread safe mechanism that protects the
* mStreamRecorders map.
*
* @param sourceChannelID identifying the recorder
*/
private void removeRecorder(Integer sourceChannelID)
{
if(mStreamRecorders.containsKey(sourceChannelID))
{
AudioRecorder recorder = mStreamRecorders.remove(sourceChannelID);
recorder.close(new Listener<AudioRecorder>()
{
@Override
public void receive(AudioRecorder audioRecorder)
{
AudioRecording audioRecording =
new AudioRecording(audioRecorder.getPath(), audioRecorder.getMetadata(),
audioRecorder.getTimeRecordingStart(), audioRecorder.getRecordingLength());
if(mAudioRecordingListener != null)
{
mAudioRecordingListener.receive(audioRecording);
}
}
});
}
}
/**
* Creates a temporary streaming recording file path
*/
private Path getTemporaryRecordingPath()
{
StringBuilder sb = new StringBuilder();
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
sb.append(sNextRecordingNumber.incrementAndGet()).append("_");
sb.append(TimeStamp.getLongTimeStamp("_"));
sb.append(mBroadcastFormat.getFileExtension());
Path temporaryRecordingPath = mTempDirectory.resolve(sb.toString());
return temporaryRecordingPath;
}
/**
* Monitors recorders to ensure they don't exceed the maximum life-span allowed for a recording.
*/
public class RecorderMonitor implements Runnable
{
@Override
public void run()
{
synchronized(mStreamRecorders)
{
long now = System.currentTimeMillis();
mStreamRecorders.entrySet().stream()
.filter(entry -> entry.getValue().getTimeRecordingStart() + MAXIMUM_RECORDER_LIFESPAN_MILLIS < now)
.forEach(entry ->
{
mLog.info("cycling recorder - max temporary streaming recording time limit reached [" +
entry.getValue().getPath().toString() + "]");
removeRecorder(entry.getKey());
});
}
}
}
}

View File

@@ -1,126 +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 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.
*
* 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];
}
public int getSize()
{
return mBuffer.length;
}
public float[] getBuffer()
{
return mBuffer;
}
/**
* 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];
put(newValue);
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

@@ -1,89 +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 buffer;
/**
* Circular sample buffer - allocates a buffer and stores samples in a circular
* fashion, overwriting older samples with newly arrived samples.
*/
public class ReverseFloatCircularBuffer
{
private float[] mBuffer;
private int mBufferPointer = 0;
public ReverseFloatCircularBuffer(int size)
{
mBuffer = new float[size];
mBufferPointer = mBuffer.length - 1;
}
/**
* Size of the buffer
* @return
*/
public int getSize()
{
return mBuffer.length;
}
public float[] getBuffer()
{
return mBuffer;
}
/**
* Adds the new value to this buffer.
*/
public void put(float newValue)
{
mBuffer[mBufferPointer--] = newValue;
if(mBufferPointer < 0)
{
mBufferPointer = mBuffer.length - 1;
}
}
/**
* Returns the sample at the specified index, where index 0 is the newest sample and index (length - 1) is the
* oldest 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 + 1 + 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 + "]");
}
}
}

View File

@@ -1,97 +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 channel.state;
import channel.metadata.Attribute;
import channel.metadata.AttributeChangeRequest;
import channel.state.DecoderStateEvent.Event;
import gui.SDRTrunk;
import message.Message;
import 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.
*/
public class AlwaysUnsquelchedDecoderState extends DecoderState
{
private final static Logger mLog = LoggerFactory.getLogger(AlwaysUnsquelchedDecoderState.class);
private DecoderType mDecoderType;
private String mChannelName;
public AlwaysUnsquelchedDecoderState(DecoderType decoderType, String channelName)
{
super(null);
mDecoderType = decoderType;
mChannelName = channelName;
}
@Override
public void reset()
{
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append("Activity Summary\n");
sb.append("\tDecoder:\t");
sb.append(mDecoderType);
sb.append("\n\n");
return sb.toString();
}
@Override
public void receive(Message t)
{
/* Not implemented */
}
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{
if(event.getEvent() == Event.RESET)
{
//Each time we're reset, set the PRIMARY TO attribute back to the channel name, otherwise we won't have
//a primary ID for any audio produced by this state.
broadcast(new AttributeChangeRequest<String>(Attribute.PRIMARY_ADDRESS_TO, mChannelName));
}
}
@Override
public DecoderType getDecoderType()
{
return mDecoderType;
}
@Override
public void start()
{
broadcast(new AttributeChangeRequest<String>(Attribute.PRIMARY_ADDRESS_TO, mChannelName));
broadcast(new DecoderStateEvent(this, Event.ALWAYS_UNSQUELCH, State.IDLE));
}
}

View File

@@ -1,639 +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 channel.state;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
import source.heartbeat.Heartbeat;
import source.heartbeat.IHeartbeatListener;
import channel.metadata.Attribute;
import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestListener;
import channel.metadata.MutableMetadata;
import channel.state.DecoderStateEvent.Event;
import channel.traffic.TrafficChannelManager;
import controller.channel.Channel.ChannelType;
import module.Module;
import module.decode.DecoderType;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.IOverflowListener;
import source.SourceEvent;
import source.ISourceEventListener;
import java.util.concurrent.ScheduledExecutorService;
import static source.SourceEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, ISourceEventListener, ISquelchStateProvider, IAttributeChangeRequestListener,
IOverflowListener, IHeartbeatListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
public static final long FADE_TIMEOUT_DELAY = 1200;
public static final long RESET_TIMEOUT_DELAY = 2000;
private MutableMetadata mMutableMetadata = new MutableMetadata();
private State mState = State.IDLE;
private Listener<CallEvent> mCallEventListener;
private Listener<DecoderStateEvent> mDecoderStateListener;
private Listener<SquelchState> mSquelchStateListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent;
private SourceChangeListener mSourceChangeListener;
private boolean mSquelchLocked = false;
private boolean mSelected = false;
private boolean mSourceOverflow = false;
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
private long mTrafficChannelFadeTimeout = DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
private long mFadeTimeout;
private long mEndTimeout;
/**
* Channel state tracks the overall state of all processing modules and decoders configured for the channel and
* provides squelch control and decoder state reset events.
*
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
* reset to IDLE.
*
* State Descriptions:
* IDLE: Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
* signal to the user that the call is ended, while continuing to display the call details for the user
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
*/
public ChannelState(ChannelType channelType)
{
mChannelType = channelType;
}
/**
* Metadata for this channel containing channel details, primary and secondary entities for call events and any
* decoded messages. This metadata is primarily intended to be used by graphical components to convey the
* details of the current channel state and to provide metadata backing any decoded audio packets produced by
* any audio streams for this channel.
*/
public MutableMetadata getMutableMetadata()
{
return mMutableMetadata;
}
/**
* Resets this channel state and prepares it for reuse.
*/
@Override
public void reset()
{
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
mState = State.IDLE;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
mSourceOverflow = false;
}
@Override
public void start()
{
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
{
setState(State.CALL);
}
}
@Override
public void stop()
{
mMutableMetadata.resetAllAttributes();
mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null)
{
mTrafficChannelCallEvent.end();
broadcast(mTrafficChannelCallEvent);
}
mTrafficChannelCallEvent = null;
mSquelchLocked = false;
}
public void dispose()
{
mCallEventListener = null;
mDecoderStateListener = null;
mSquelchStateListener = null;
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
if(mSourceChangeListener == null)
{
mSourceChangeListener = new SourceChangeListener();
}
return mSourceChangeListener;
}
private boolean isStandardChannel()
{
return mChannelType == ChannelType.STANDARD;
}
private boolean isTrafficChannel()
{
return mChannelType == ChannelType.TRAFFIC;
}
public void setStandardChannelTimeout(long milliseconds)
{
mStandardChannelFadeTimeout = milliseconds;
if(mChannelType == ChannelType.STANDARD)
{
mFadeTimeout = mStandardChannelFadeTimeout;
}
}
public void setTrafficChannelTimeout(long milliseconds)
{
mTrafficChannelFadeTimeout = milliseconds;
if(mChannelType == ChannelType.TRAFFIC)
{
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
}
}
public void setSelected(boolean selected)
{
mSelected = selected;
}
public boolean isSelected()
{
return mSelected;
}
public State getState()
{
return mState;
}
/**
* Updates the fade timeout threshold to the current time plus delay
*/
private void updateFadeTimeout()
{
if(isTrafficChannel())
{
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
}
else
{
mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout;
}
}
/**
* Updates the reset timeout threshold to the current time plus delay
*/
private void updateResetTimeout()
{
if(isTrafficChannel())
{
mEndTimeout = System.currentTimeMillis();
}
else
{
mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
}
}
/**
* Broadcasts the squelch state to the registered listener
*/
protected void broadcast(SquelchState state)
{
if(mSquelchStateListener != null && !mSquelchLocked)
{
mSquelchStateListener.receive(state);
}
}
/**
* Sets the squelch state listener
*/
@Override
public void setSquelchStateListener(Listener<SquelchState> listener)
{
mSquelchStateListener = listener;
}
/**
* Removes the squelch state listener
*/
@Override
public void removeSquelchStateListener()
{
mSquelchStateListener = null;
}
/**
* Sets the channel state to the specified state, or updates the timeout values so that the state monitor will not
* change state. Broadcasts a squelch event when the state changes and the audio squelch state should change. Also
* broadcasts changed attribute and decoder state events so that external processes can maintain sync with this
* channel state.
*/
protected void setState(State state)
{
if(state == mState)
{
if(State.CALL_STATES.contains(state))
{
updateFadeTimeout();
}
}
else if(mState.canChangeTo(state))
{
switch(state)
{
case CONTROL:
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
if(isStandardChannel())
{
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
}
break;
case DATA:
case ENCRYPTED:
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
break;
case CALL:
broadcast(SquelchState.UNSQUELCH);
updateFadeTimeout();
mState = state;
break;
case FADE:
processFadeState();
break;
case IDLE:
processIdleState();
break;
case TEARDOWN:
processTeardownState();
break;
case RESET:
mState = State.IDLE;
break;
default:
break;
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
}
}
/**
* Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages.
*/
@Override
public Listener<Heartbeat> getHeartbeatListener()
{
return mHeartbeatReceiver;
}
/**
* This method is invoked if the source buffer provider goes into overflow state. Since this is an external state,
* we use the mSourceOverflow variable to override the internal state reported to external listeners.
* @param overflow true to indicate an overflow state
*/
@Override
public void sourceOverflow(boolean overflow)
{
mSourceOverflow = overflow;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
}
/**
* Sets the state and processes related actions
*/
private void processFadeState()
{
updateResetTimeout();
mState = State.FADE;
broadcast(SquelchState.SQUELCH);
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
}
private void processIdleState()
{
broadcast(SquelchState.SQUELCH);
if(mState == State.FADE)
{
getMutableMetadata().resetTemporalAttributes();
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
}
mState = State.IDLE;
getMutableMetadata().receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
}
private void processTeardownState()
{
broadcast(SquelchState.SQUELCH);
getMutableMetadata().resetTemporalAttributes();
mState = State.TEARDOWN;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
{
mTrafficChannelEndListener.callEnd(mTrafficChannelCallEvent.getChannel());
}
}
/**
* Broadcasts the call event to the registered listener
*/
protected void broadcast(CallEvent event)
{
if(mCallEventListener != null)
{
mCallEventListener.receive(event);
}
}
@Override
public void addCallEventListener(Listener<CallEvent> listener)
{
mCallEventListener = listener;
}
@Override
public void removeCallEventListener(Listener<CallEvent> listener)
{
mCallEventListener = null;
}
//NEW:
@Override
public Listener<AttributeChangeRequest> getAttributeChangeRequestListener()
{
return mMutableMetadata;
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast(DecoderStateEvent event)
{
if(mDecoderStateListener != null)
{
mDecoderStateListener.receive(event);
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventReceiver;
}
/**
* Registers a listener to be notified when a traffic channel call event is
* completed, so that the listener can perform call tear-down
*/
public void configureAsTrafficChannel(TrafficChannelManager manager, CallEvent callEvent)
{
mTrafficChannelEndListener = manager;
mTrafficChannelCallEvent = callEvent;
/* Broadcast the call event details as metadata for the audio manager */
String channel = mTrafficChannelCallEvent.getChannel();
if(channel != null)
{
mMutableMetadata.receive(new AttributeChangeRequest<String>(Attribute.CHANNEL_FREQUENCY_LABEL, channel));
}
long frequency = mTrafficChannelCallEvent.getFrequency();
if(frequency > 0)
{
mMutableMetadata.receive(new AttributeChangeRequest<Long>(Attribute.CHANNEL_FREQUENCY, frequency));
}
mMutableMetadata.receive(new AttributeChangeRequest<DecoderType>(Attribute.PRIMARY_DECODER_TYPE,
mTrafficChannelCallEvent.getDecoderType()));
mMutableMetadata.receive(new AttributeChangeRequest<String>(Attribute.CHANNEL_CONFIGURATION_NAME, "TRAFFIC"));
String from = mTrafficChannelCallEvent.getFromID();
if(from != null)
{
mMutableMetadata.receive(new AttributeChangeRequest<String>(Attribute.PRIMARY_ADDRESS_FROM, from,
mTrafficChannelCallEvent.getFromIDAlias()));
}
String to = mTrafficChannelCallEvent.getToID();
if(to != null)
{
mMutableMetadata.receive(new AttributeChangeRequest<String>(Attribute.PRIMARY_ADDRESS_TO, to,
mTrafficChannelCallEvent.getToIDAlias()));
}
/* Rebroadcast the allocation event so that the internal decoder states
* can self-configure with the call event details */
broadcast(mTrafficChannelCallEvent);
}
/**
* Listener to receive frequency change events and rebroadcast them to the decoder states
*/
public class SourceChangeListener implements Listener<SourceEvent>
{
@Override
public void receive(SourceEvent sourceEvent)
{
if(sourceEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
{
long frequency = sourceEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
}
}
}
/**
* DecoderStateEvent receiver wrapper
*/
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
{
@Override
public void receive(DecoderStateEvent event)
{
if(event.getSource() != this)
{
switch(event.getEvent())
{
case ALWAYS_UNSQUELCH:
broadcast(SquelchState.UNSQUELCH);
mSquelchLocked = true;
break;
case CHANGE_CALL_TIMEOUT:
if(event instanceof ChangeChannelTimeoutEvent)
{
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent) event;
if(timeout.getChannelType() == ChannelType.STANDARD)
{
setStandardChannelTimeout(timeout.getCallTimeout());
}
else
{
setTrafficChannelTimeout(timeout.getCallTimeout());
}
}
case CONTINUATION:
case DECODE:
case START:
if(State.CALL_STATES.contains(event.getState()))
{
setState(event.getState());
}
break;
case END:
if(isTrafficChannel())
{
setState(State.TEARDOWN);
}
else
{
setState(State.FADE);
}
break;
case RESET:
/* Channel State does not respond to reset events */
break;
default:
break;
}
}
}
}
/**
* Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
* functions.
*
* Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to
* TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state.
*
* Provides a FADE transition state to allow for momentary decoding dropouts and to allow the user access to call
* details for a fade period upon call end.
*/
public class HeartbeatReceiver implements Listener<Heartbeat>
{
@Override
public void receive(Heartbeat heartbeat)
{
try
{
if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
{
processFadeState();
}
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
{
if(isTrafficChannel())
{
processTeardownState();
}
else
{
processIdleState();
}
}
}
catch(Throwable e)
{
mLog.error("An error occurred while state monitor was running " +
"- state [" + getState() +
"] current [" + System.currentTimeMillis() +
"] mResetTimeout [" + mEndTimeout +
"] mFadeTimeout [" + mFadeTimeout +
"]", e);
}
}
}
}

View File

@@ -1,222 +0,0 @@
package channel.state;
import alias.AliasList;
import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestProvider;
import message.IMessageListener;
import message.Message;
import module.Module;
import module.decode.DecoderType;
import module.decode.event.ActivitySummaryProvider;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import sample.Broadcaster;
import sample.Listener;
/**
* Channel state monitors the stream of decoded messages produced by the
* decoder and broadcasts call events as they occur within the decoded message activity.
*
* Provides access to a textual activity summary of events observed.
*/
public abstract class DecoderState extends Module implements ActivitySummaryProvider, Listener<Message>,
IAttributeChangeRequestProvider, ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, IMessageListener
{
protected String DIVIDER1 = "======================================================\n";
protected String DIVIDER2 = "------------------------------------------------------\n";
/* This has to be a broadcaster in order for references to persist */
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
private Broadcaster<AttributeChangeRequest> mAttributeChangeRequestBroadcaster = new Broadcaster<>();
private Listener<DecoderStateEvent> mDecoderStateListener;
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
protected CallEvent mCurrentCallEvent;
private AliasList mAliasList;
public DecoderState(AliasList aliasList)
{
mAliasList = aliasList;
}
public abstract DecoderType getDecoderType();
public AliasList getAliasList()
{
return mAliasList;
}
public boolean hasAliasList()
{
return mAliasList != null;
}
/**
* Provides subclass reference to the call event broadcaster
*/
protected Broadcaster<CallEvent> getCallEventBroadcaster()
{
return mCallEventBroadcaster;
}
/**
* Reset the decoder state to prepare for processing a different sample
* source
*/
public abstract void reset();
/**
* Allow the decoder to perform any setup actions
*/
public void init()
{
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
/**
* Implements the IDecoderStateEventListener interface to receive state
* reset events.
*/
public abstract void receiveDecoderStateEvent(DecoderStateEvent event);
/**
* Disposes any resources or pointers held by this instance to prepare for
* garbage collection
*/
public void dispose()
{
mCallEventBroadcaster.dispose();
mCallEventBroadcaster = null;
mDecoderStateListener = null;
mAliasList = null;
}
/**
* Activity Summary - textual summary of activity observed by the channel state.
*/
public abstract String getActivitySummary();
/**
* Broadcasts a call event to any registered listeners
*/
protected void broadcast(CallEvent event)
{
mCallEventBroadcaster.broadcast(event);
}
/**
* Adds a call event listener
*/
@Override
public void addCallEventListener(Listener<CallEvent> listener)
{
mCallEventBroadcaster.addListener(listener);
}
/**
* Removes the call event listener
*/
@Override
public void removeCallEventListener(Listener<CallEvent> listener)
{
mCallEventBroadcaster.removeListener(listener);
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventListener;
}
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
public void receive(DecoderStateEvent event)
{
receiveDecoderStateEvent(event);
}
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast(DecoderStateEvent event)
{
if(mDecoderStateListener != null)
{
mDecoderStateListener.receive(event);
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
@Override
public Listener<Message> getMessageListener()
{
return this;
}
/**
* Broadcasts the attribute change request to the registered listener
*/
protected void broadcast(AttributeChangeRequest<?> request)
{
mAttributeChangeRequestBroadcaster.broadcast(request);
}
/**
* Sets the listener to receive attribute change requests from this decoder state
*/
@Override
public void setAttributeChangeRequestListener(Listener<AttributeChangeRequest> listener)
{
mAttributeChangeRequestBroadcaster.addListener(listener);
}
/**
* Removes any listener from receiving attribute change requests
*/
@Override
public void removeAttributeChangeRequestListener(Listener<AttributeChangeRequest> listener)
{
mAttributeChangeRequestBroadcaster.removeListener(listener);
}
/**
* Broadaster so that Attribute monitors can broadcast attribute change requests
*/
protected Listener<AttributeChangeRequest> getAttributeChangeRequestListener()
{
return mAttributeChangeRequestBroadcaster;
}
}

View File

@@ -1,395 +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 channel.traffic;
import channel.state.DecoderStateEvent;
import channel.state.IDecoderStateEventListener;
import controller.channel.Channel;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEvent.Event;
import controller.channel.ChannelModel;
import controller.channel.TrafficChannelEvent;
import module.Module;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent;
import module.decode.event.CallEvent.CallEventType;
import module.decode.event.ICallEventProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.config.RecordConfiguration;
import sample.Listener;
import source.config.SourceConfigTuner;
import source.tuner.channel.TunerChannel;
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
{
private final static Logger mLog = LoggerFactory.getLogger(TrafficChannelManager.class);
public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED";
public static final String NO_TUNER_AVAILABLE = "NO TUNER AVAILABLE";
public static final String UNKNOWN_FREQUENCY = "UNKNOWN FREQUENCY";
private int mTrafficChannelPoolMaximumSize = DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private List<Channel> mTrafficChannelPool = new ArrayList<Channel>();
private Map<String,Channel> mTrafficChannelsInUse = new ConcurrentHashMap<String,Channel>();
private DecoderStateEventListener mEventListener = new DecoderStateEventListener();
private Listener<CallEvent> mCallEventListener;
private ChannelModel mChannelModel;
private DecodeConfiguration mDecodeConfiguration;
private RecordConfiguration mRecordConfiguration;
private String mSystem;
private String mSite;
private String mAliasListName;
/**
* Monitors call events and allocates traffic decoder channels in response
* to traffic channel allocation call events. Manages a pool of reusable
* traffic channel allocations.
*
* @param channelModel containing channels currently in use
* @param decodeConfiguration - decoder configuration to use for each
* traffic channel allocation.
* @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
* @param aliasListName designated for the channel
* @param trafficChannelPoolSize - maximum number of allocated traffic channels
* in the pool
*/
public TrafficChannelManager(ChannelModel channelModel,
DecodeConfiguration decodeConfiguration,
RecordConfiguration recordConfiguration,
String system,
String site,
String aliasListName,
int trafficChannelPoolSize)
{
mChannelModel = channelModel;
mDecodeConfiguration = decodeConfiguration;
mRecordConfiguration = recordConfiguration;
mSystem = system;
mSite = site;
mAliasListName = aliasListName;
mTrafficChannelPoolMaximumSize = trafficChannelPoolSize;
}
@Override
public void dispose()
{
for(Channel trafficChannel : mTrafficChannelPool)
{
mChannelModel.broadcast(new ChannelEvent(trafficChannel, Event.REQUEST_DISABLE));
}
mTrafficChannelPool.clear();
mTrafficChannelsInUse.clear();
mCallEventListener = null;
mDecodeConfiguration = null;
}
/**
* Provides a channel by either reusing an existing channel or constructing
* a new one, limited by the total number of constructed channels allowed.
*
* Note: you must enforce thread safety on the mTrafficChannelsInUse
* external to this method.
*
* @param channelNumber
* @param tunerChannel
* @return
*/
private Channel getChannel(String channelNumber, TunerChannel tunerChannel)
{
Channel channel = null;
if(!mTrafficChannelsInUse.containsKey(channelNumber))
{
for(Channel configuredChannel : mTrafficChannelPool)
{
if(!configuredChannel.getEnabled())
{
channel = configuredChannel;
break;
}
}
if(channel == null && mTrafficChannelPool.size() < mTrafficChannelPoolMaximumSize)
{
channel = new Channel("Traffic", ChannelType.TRAFFIC);
channel.setDecodeConfiguration(mDecodeConfiguration);
channel.setRecordConfiguration(mRecordConfiguration);
channel.setAliasListName(mAliasListName);
mChannelModel.addChannel(channel);
mTrafficChannelPool.add(channel);
}
/* If we have a configured channel, update metadata */
if(channel != null)
{
channel.setSourceConfiguration(new SourceConfigTuner(tunerChannel));
channel.setSystem(mSystem);
channel.setSite(mSite);
channel.setName(channelNumber);
mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_CONFIGURATION_CHANGE));
}
}
return channel;
}
/**
* Processes the event and creates a traffic channel is resources are
* available
*/
private void process(TrafficChannelAllocationEvent event)
{
CallEvent callEvent = event.getCallEvent();
/* Check for duplicate events and suppress */
synchronized(mTrafficChannelsInUse)
{
if(mTrafficChannelsInUse.containsKey(callEvent.getChannel()))
{
return;
}
long frequency = callEvent.getFrequency();
if(frequency > 0)
{
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(frequency,
mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
if(channel != null)
{
TrafficChannelEvent trafficChannelEvent =
new TrafficChannelEvent(this, channel, Event.REQUEST_ENABLE, callEvent);
//Request to enable the channel
mChannelModel.broadcast(trafficChannelEvent);
if(channel.getEnabled())
{
mTrafficChannelsInUse.put(callEvent.getChannel(), channel);
}
else
{
callEvent.setCallEventType(CallEventType.CALL_DETECT);
String details = callEvent.getDetails();
if(details == null || details.isEmpty())
{
callEvent.setDetails(CHANNEL_START_REJECTED);
}
else if(!details.contains(CHANNEL_START_REJECTED))
{
callEvent.setDetails(new StringBuilder(CHANNEL_START_REJECTED).append(" : ")
.append(callEvent.getDetails()).toString());
}
}
}
else
{
callEvent.setCallEventType(CallEventType.CALL_DETECT);
String details = callEvent.getDetails();
if(details == null || details.isEmpty())
{
callEvent.setDetails(NO_TUNER_AVAILABLE);
}
else if(!details.contains(NO_TUNER_AVAILABLE))
{
callEvent.setDetails(new StringBuilder(NO_TUNER_AVAILABLE).append(" : ")
.append(callEvent.getDetails()).toString());
}
}
}
else
{
callEvent.setCallEventType(CallEventType.CALL_DETECT);
String details = callEvent.getDetails();
if(details == null || details.isEmpty())
{
callEvent.setDetails(UNKNOWN_FREQUENCY);
}
else if(!details.contains(UNKNOWN_FREQUENCY))
{
callEvent.setDetails(new StringBuilder(UNKNOWN_FREQUENCY).append(" : ")
.append(callEvent.getDetails()).toString());
}
}
final Listener<CallEvent> listener = mCallEventListener;
if(listener != null)
{
listener.receive(callEvent);
}
}
}
/**
* Compares the call type, channel and to fields for equivalence and the
* from field for either both null, or equivalence.
*
* @param e1
* @param e2
* @return
*/
public static boolean isSameCallEvent(CallEvent e1, CallEvent e2)
{
if(e1 == null || e2 == null)
{
return false;
}
if(e1.getCallEventType() != e2.getCallEventType())
{
return false;
}
if(e1.getChannel() == null || e2.getChannel() == null || !e1.getChannel().contentEquals(e2.getChannel()))
{
return false;
}
if(e1.getToID() == null || e2.getToID() == null || !e1.getToID().contentEquals(e2.getToID()))
{
return false;
}
if(e1.getFromID() == null || e2.getFromID() == null)
{
return (e1.getFromID() == null && e2.getFromID() == null);
}
else if(e1.getFromID().contentEquals(e2.getFromID()))
{
return true;
}
return false;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mEventListener;
}
@Override
public void addCallEventListener(Listener<CallEvent> listener)
{
mCallEventListener = listener;
}
@Override
public void removeCallEventListener(Listener<CallEvent> listener)
{
mCallEventListener = null;
}
@Override
public void reset()
{
}
@Override
public void start()
{
}
@Override
public void stop()
{
if(!mTrafficChannelsInUse.isEmpty())
{
List<String> channels = new ArrayList<>();
/* Copy the keyset so we don't get concurrent modification of the map */
channels.addAll(mTrafficChannelsInUse.keySet());
for(String channel : channels)
{
callEnd(channel);
}
}
}
/**
* Callback used by the TrafficChannelStatusListener class to signal the
* end of a an allocated traffic channel call event
*
* @param channelNumber - channel number from the call event that signaled
* the start of a traffic channel allocation
*/
public void callEnd(String channelNumber)
{
synchronized(mTrafficChannelsInUse)
{
if(channelNumber != null && mTrafficChannelsInUse.containsKey(channelNumber))
{
Channel channel = mTrafficChannelsInUse.get(channelNumber);
mChannelModel.broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
mTrafficChannelsInUse.remove(channelNumber);
}
}
}
/**
* Wrapper class for the decoder state event listener interface to catch
* traffic channel allocation requests
*/
public class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
public void receive(DecoderStateEvent event)
{
switch(event.getEvent())
{
case TRAFFIC_CHANNEL_ALLOCATION:
process((TrafficChannelAllocationEvent) event);
break;
default:
break;
}
}
}
}

View File

@@ -1,478 +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 controller.channel;
import controller.config.Configuration;
import module.decode.DecoderFactory;
import module.decode.DecoderType;
import module.decode.config.AuxDecodeConfiguration;
import module.decode.config.DecodeConfiguration;
import module.log.EventLogType;
import module.log.config.EventLogConfiguration;
import record.RecorderType;
import record.config.RecordConfiguration;
import sample.Listener;
import source.SourceType;
import source.config.SourceConfigFactory;
import source.config.SourceConfigRecording;
import source.config.SourceConfigTuner;
import source.config.SourceConfiguration;
import source.tuner.channel.TunerChannel;
import source.SourceEvent;
import source.SourceEvent.Event;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
@XmlSeeAlso({Configuration.class})
@XmlRootElement(name = "channel")
public class Channel extends Configuration implements Listener<SourceEvent>
{
// Standard channels are persisted and traffic channels are temporary
public enum ChannelType
{
STANDARD, TRAFFIC
}
;
// Static unique channel identifier tracking
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 RecordConfiguration mRecordConfiguration = new RecordConfiguration();
private String mAliasListName;
private String mSystem = "System";
private String mSite = "Site";
private String mName = "Channel";
private boolean mEnabled;
private boolean mSelected;
private TunerChannel mTunerChannel = null;
private ChannelType mChannelType = ChannelType.STANDARD;
private int mChannelID;
private int mChannelFrequencyCorrection = 0;
/**
* Channel represents a complete set of configurations needed to setup and
* manage a processing chain and/or manage as a set of business objects that
* can be displayed in graphical components.
*
* @param channelName
* @param channelType
*/
public Channel(String channelName, ChannelType channelType)
{
this(channelName);
mChannelType = channelType;
}
/**
* Constructs a new standard channel with the specified channel name
*/
public Channel(String channelName)
{
this();
mName = channelName;
}
/**
* Constructs a new standard channel with a default name of "Channel"
*/
public Channel()
{
mChannelID = UNIQUE_ID++;
}
/**
* Creates a (new) deep copy of this channel
*/
public Channel copyOf()
{
Channel channel = new Channel(mName);
channel.setSystem(mSystem);
channel.setSite(mSite);
channel.setAliasListName(mAliasListName);
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
for(DecoderType auxType : aux.getAuxDecoders())
{
aux.addAuxDecoder(auxType);
}
channel.setAuxDecodeConfiguration(aux);
channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
EventLogConfiguration log = new EventLogConfiguration();
for(EventLogType logType : mEventLogConfiguration.getLoggers())
{
log.addLogger(logType);
}
channel.setEventLogConfiguration(log);
RecordConfiguration record = new RecordConfiguration();
for(RecorderType recordType : mRecordConfiguration.getRecorders())
{
record.addRecorder(recordType);
}
channel.setRecordConfiguration(record);
channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
return channel;
}
/**
* Unique identifier for this channel. Value is transient (not persisted).
*/
@XmlTransient
public int getChannelID()
{
return mChannelID;
}
/**
* Indicates if this channel has been selected for prioritized audio output
* and display of processing chain products
*/
@XmlTransient
public boolean isSelected()
{
return mSelected;
}
/**
* Sets selection status of the channel.
*
* NOTE: this method is package private so that only the ChannelSelectionManager
* can toggle the selection state. Use the channel model to broadcast a
* request select/deselect channel event.
*
* @param selected
*/
void setSelected(boolean selected)
{
mSelected = selected;
}
@XmlTransient
public ChannelType getChannelType()
{
return mChannelType;
}
/**
* Returns the owning system for this channel.
*/
@XmlAttribute(name = "system")
public String getSystem()
{
return mSystem;
}
public void setSystem(String system)
{
mSystem = system;
}
public boolean hasSystem()
{
return mSystem != null;
}
/**
* Returns the owning site for this channel.
*/
@XmlAttribute(name = "site")
public String getSite()
{
return mSite;
}
public void setSite(String site)
{
mSite = site;
}
public boolean hasSite()
{
return mSite != null;
}
/**
* Returns the name of this channel.
*/
@XmlAttribute(name = "name")
public String getName()
{
return mName;
}
/**
* Sets the name of this channel.
*/
public void setName(String name)
{
mName = name;
}
/**
* Default display string for this channel: SYSTEM_SITE_NAME
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(hasSystem() ? getSystem() : "SYSTEM");
sb.append("_");
sb.append(hasSite() ? getSite() : "SITE");
sb.append("_");
sb.append(getName());
return sb.toString();
}
/**
* Indicates if this channel is enabled for processing.
*/
@XmlAttribute(name = "enabled")
public boolean getEnabled()
{
return mEnabled;
}
/**
* Indicates if this channel will automatically start processing on
* application startup or if this channel is currently processing after
* application startup.
*
* Note: this method is package private and is intended to be managed
* by a co-package central processing manager
*
* @see controller.channel.ChannelProcessingManager
*/
void setEnabled(boolean enabled)
{
mEnabled = enabled;
}
/**
* Returns the alias list that is used by this channel for looking up alias
* values for the various identifiers produced by the decoder(s).
*/
@XmlElement(name = "alias_list_name")
public String getAliasListName()
{
return mAliasListName;
}
/**
* Sets the alias list to be used for looking up the alias values for the
* various identifiers produced by the decoder
*/
public void setAliasListName(String name)
{
mAliasListName = name;
}
/**
* Gets the primary decoder configuration used by this channel
*/
@XmlElement(name = "decode_configuration")
public DecodeConfiguration getDecodeConfiguration()
{
return mDecodeConfiguration;
}
/**
* Sets the primary decoder configuration used by this channel
*/
public void setDecodeConfiguration(DecodeConfiguration config)
{
mDecodeConfiguration = config;
}
/**
* Gets the aux decoder configuration used by this channel. Aux decoders
* operate on demodulated audio
*/
@XmlElement(name = "aux_decode_configuration")
public AuxDecodeConfiguration getAuxDecodeConfiguration()
{
return mAuxDecodeConfiguration;
}
/**
* Sets the decoder configuration used by this channel
*/
public void setAuxDecodeConfiguration(AuxDecodeConfiguration config)
{
mAuxDecodeConfiguration = config;
}
/**
* Returns the source configuration for this channel. A channel source
* identifies where the processing chain will get source sample data from.
*/
@XmlElement(name = "source_configuration")
public SourceConfiguration getSourceConfiguration()
{
return mSourceConfiguration;
}
/**
* Sets the source configuration for this channel.
*/
public void setSourceConfiguration(SourceConfiguration config)
{
mSourceConfiguration = config;
//Clear the tune channel object so that it can be recreated if the
//source configuration changes
mTunerChannel = null;
}
/**
* Returns the event logger configuration for this channel.
*/
@XmlElement(name = "event_log_configuration")
public EventLogConfiguration getEventLogConfiguration()
{
return mEventLogConfiguration;
}
/**
* Sets the event logger configuration for this channel.
*/
public void setEventLogConfiguration(EventLogConfiguration config)
{
mEventLogConfiguration = config;
}
/**
* Returns the recorder configuration for this channel.
*/
@XmlElement(name = "record_configuration")
public RecordConfiguration getRecordConfiguration()
{
return mRecordConfiguration;
}
/**
* Sets the recorder configuration for this channel.
*/
public void setRecordConfiguration(RecordConfiguration config)
{
mRecordConfiguration = config;
}
/**
* Convenience method to construct a tuner channel object representing a
* tuner or recording source frequency and bandwidth that can be used by
* application components for graphically representing this channel.
*
* If the source configuration is not a tuner or recording, this method
* returns null.
*/
@XmlTransient
public TunerChannel getTunerChannel()
{
if(mTunerChannel == null)
{
if(mSourceConfiguration.getSourceType() == SourceType.TUNER)
{
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
}
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
{
SourceConfigRecording config = (SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
}
}
return mTunerChannel;
}
/**
* Convenience method to indicate if any part of this channel is contained
* within the minimum and maximum frequency values.
*/
public boolean isWithin(long minimum, long maximum)
{
TunerChannel tunerChannel = getTunerChannel();
return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
}
/**
* Convenience method to make the current channel frequency correction value
* available for use outside of a constructed processing chain while maintaining
* linkage to the source channel. This hack allows the correction value to
* be used to visually show in the spectral display.
*/
@Override
public void receive(SourceEvent event)
{
if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
{
mChannelFrequencyCorrection = event.getValue().intValue();
}
}
/**
* Current channel frequency correction value (when this channel is processing)
* @return
*/
@XmlTransient
public int getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Resets frequency correction value to 0
*/
public void resetFrequencyCorrection()
{
mChannelFrequencyCorrection = 0;
}
}

View File

@@ -1,910 +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 dsp.filter;
import dsp.filter.Window.WindowType;
import dsp.filter.cic.ComplexPrimeCICDecimate;
import dsp.filter.design.FilterDesignException;
import dsp.filter.fir.FIRFilterSpecification;
import dsp.filter.fir.remez.RemezFIRFilterDesigner;
import dsp.filter.fir.remez.RemezFIRFilterDesignerWithLagrange;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 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
*
* @param sampleRate - hertz
* @param frequency - cutoff in hertz
* @param length - filter length
* @param window - to apply against the coefficients
* @return
*/
public static float[] getSinc(int sampleRate, long frequency, int length, WindowType window)
{
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, 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(evenLength);
idft.realInverseFull(frequencyResponse, true);
//Transfer the IDFT results to the return array
float[] coefficients = new float[length];
int middleCoefficient = (int)(length / 2);
if(length % 2 == 0) //Even length
{
//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);
return coefficients;
}
/**
* Normalizes all filter coefficients to achieve unity (1) gain, by ensuring
* that the sum of the absolute value of all coefficients adds up to 1.
*
* @param coefficients
* @return
*/
public static float[] normalize(float[] coefficients)
{
float accumulator = 0;
for(int x = 0; x < coefficients.length; x++)
{
accumulator += Math.abs(coefficients[x]);
}
for(int x = 0; x < coefficients.length; x++)
{
coefficients[x] = coefficients[x] / accumulator;
}
return coefficients;
}
/**
* Constructs an array of unity (1) response values representing the
* desired (pre-windowing) frequency response, used by the sync function
*
* Returns an array twice the length, filled with unity (1) response values
* in the desired pass-band with the positive frequency response starting at
* the lower end of the array, and the negative frequency response at the
* higher end of the first half of the array. The remaining zero-valued
* indexes in the second half will store the results of the JTransforms
* inverse DFT operation
*
* @param sampleRate
* @param frequency
* @param length
* @return
*/
public static float[] getUnityResponseArray(int sampleRate, long frequency, int length)
{
float[] unityArray = new float[length * 2];
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;
unityArray[length - 1 - x] = 1.0f;
}
}
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++)
{
unityArray[x] = 1.0f;
unityArray[length - x] = 1.0f;
}
}
return unityArray;
}
/**
* Applies a repeating sequence of 1, -1 to the coefficients to invert
* the frequency response of the filter. Used in converting a low-pass
* filter to a high-pass filter.
*
* Inverts the sign of all odd index coefficients ( 1, 3, 5, etc.)
* returning:
* Index 0: same
* Index 1: inverted
* Index 2: same
* ...
* Index length - 1: same
*/
public static float[] invert(float[] coefficients)
{
for(int x = 1; x < coefficients.length; x += 2)
{
coefficients[x] = -coefficients[x];
}
return coefficients;
}
/**
* Generates filter coefficients for a unity-gain, odd-length, windowed,
* low pass filter with passband from 0-hertz to the cutoff frequency.
*
* @param sampleRate - hertz
* @param cutoff - frequency in hertz
* @param filterLength - odd filter length
* @param windowType - window to apply against the generated coefficients
* @return
*/
public static float[] getLowPass(int sampleRate, long cutoff, int filterLength, WindowType windowType)
{
return getSinc(sampleRate, cutoff, filterLength, windowType);
}
/**
* Creates a low-pass filter with ~ 0.1 db ripple in the pass band
*
* Note: stop frequency - pass frequency defines the transition band.
*/
/**
* Creates a low-pass filter with ~0.1 db ripple in the pass band. The
* transition region is defined by the stop frequency minus the pass
* frequency.
*
* Requires:
* - passFrequency < stopFrequency
* - stopFrequency <= sampleRate/2
*/
public static float[] getLowPass(int sampleRate, int passFrequency, int stopFrequency, int attenuation,
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 + "]");
}
int tapCount = getTapCount(sampleRate, passFrequency, stopFrequency, attenuation);
if(forceOddLength)
{
if(tapCount % 2 == 0)
{
tapCount--;
}
}
return getLowPass(sampleRate, passFrequency, tapCount, windowType);
}
/**
* Generates filter coefficients for a unity-gain, odd-length, windowed,
* high pass filter with passband from cutoff frequency to half the sample
* rate.
*
* @param sampleRate - hertz
* @param cutoff - frequency in hertz
* @param filterLength - odd filter length
* @param windowType - window to apply against the generated coefficients
* @return
*/
public static float[] getHighPass(int sampleRate, long cutoff, int filterLength, 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));
}
public static float[] getHighPass(int sampleRate, long stopFrequency, long passFrequency, int attenuation,
WindowType windowType, boolean forceOddLength)
{
/* reverse the stop and pass frequency to get the low pass variant */
int tapCount = getTapCount(sampleRate, stopFrequency, passFrequency, attenuation);
if(forceOddLength)
{
if(tapCount % 2 == 0)
{
tapCount--;
}
}
return invert(getLowPass(sampleRate, stopFrequency, tapCount, windowType));
}
/**
* Utility to log the arrays of doubles with line breaks
*/
public static String arrayToString(float[] array, boolean breaks)
{
StringBuilder sb = new StringBuilder();
for(int x = 0; x < array.length; x++)
{
sb.append(x + ": " + array[x]);
if(breaks)
{
sb.append("\n");
}
else
{
if(x % 8 == 7)
{
sb.append("\n");
}
else
{
sb.append("\t");
}
}
}
return sb.toString();
}
/**
* 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.
*
* @param sampleRate in hertz
* @param pass pass frequency in hertz
* @param stop stop frequency in hertz
* @param attenuation in dB
* @return
*/
public static int getTapCount(int sampleRate, long pass, long stop, int attenuation)
{
double frequency = ((double)stop - (double)pass) / (double)sampleRate;
return (int)(Math.round((double)attenuation / (22.0d * frequency)));
}
public static ComplexPrimeCICDecimate getDecimationFilter(int sampleRate, int decimatedRate, int order,
int passFrequency, int attenuation, WindowType windowType)
{
int decimationRate = (int)(sampleRate / decimatedRate);
return new ComplexPrimeCICDecimate(decimationRate, order,
passFrequency, attenuation, windowType);
}
/**
* Determines decimation rate(s) for a polyphase decimation filter.
*
* @param sampleRate - starting sample rate
* @param decimatedRate - final (decimated) output rate
* @return - set of integer decimation rates for a single or multi-stage
* 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)
{
int[] rates;
if(sampleRate % decimatedRate != 0)
{
throw new IllegalArgumentException("Decimated rate must be an "
+ "integer multiple of sample rate");
}
int decimation = (int)(sampleRate / decimatedRate);
//Decimation rates below 20 will use single stage polyphase filter
if(decimation < 20)
{
rates = new int[1];
rates[0] = decimation;
return rates;
}
else
{
int optimalStage1 =
getOptimalStageOneRate(sampleRate, decimation, passFrequency, stopFrequency);
Set<Integer> factors = getFactors(decimation);
int stage1 = findClosest(optimalStage1, factors);
if(stage1 == decimation || stage1 == 1)
{
rates = new int[1];
rates[0] = decimation;
return rates;
}
else
{
rates = new int[2];
int stage2 = (int)(decimation / stage1);
if(stage1 > stage2)
{
rates[0] = stage1;
rates[1] = stage2;
}
else
{
rates[0] = stage2;
rates[1] = stage1;
}
return rates;
}
}
}
/**
* 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)
{
int bestFactor = 1;
int bestDelta = desiredFactor;
for(Integer factor : factors)
{
int testDelta = Math.abs(desiredFactor - factor);
if(testDelta < bestDelta)
{
bestDelta = testDelta;
bestFactor = factor;
}
}
return bestFactor;
}
/**
* 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
*/
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);
if(remainder * x == value)
{
factors.add(x);
}
}
return factors;
}
/**
* 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.
*
* Implements the algorithm described in Lyons, Understanding Digital Signal Processing, 3e, section 10.2.1, page 511.
*
* @param sampleRate
* @param decimation
* @param passFrequency frequency of the pass band
* @return optimum integer decimation rate for the first stage decimation
* filter
*/
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 denominator = 2.0d - (ratio * (decimation + 1.0d));
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.
*/
private static double getBandwidthRatio(long passFrequency, long stopFrequency)
{
assert (passFrequency < stopFrequency);
return ((double)(stopFrequency - passFrequency) /
(double)stopFrequency);
}
/**
* Assumes that the pass band is 1/4 of the output sample rate.
*
* Assumes the stop band is: pass + (pass * .25).
*
* @param outputSampleRate
* @return
*/
public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, WindowType window)
{
int taps = getTapCount(outputSampleRate, passFrequency, passFrequency + 1500,
attenuation);
/* Make tap count odd */
if(taps % 2 == 0)
{
taps++;
}
float[] frequencyResponse =
getCICResponseArray(outputSampleRate, passFrequency, taps);
//Apply Inverse DFT against frequency response unity values, leaving the
//IDFT bin results in the frequency response array
FloatFFT_1D idft = new FloatFFT_1D(taps);
idft.realInverseFull(frequencyResponse, false);
//Transfer the IDFT results to the odd length return array
float[] coefficients = new float[taps];
int middleCoefficient = (int)(taps / 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++)
{
coefficients[middleCoefficient + x] = frequencyResponse[2 * x];
coefficients[middleCoefficient - x] = frequencyResponse[2 * x];
}
//Apply the window against the coefficients
// coefficients = Window.apply( window, coefficients );
normalize(coefficients);
return coefficients;
}
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)));
cicArray[0] = 1.0f;
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)));
cicArray[x] = compensated;
cicArray[length - x] = compensated;
}
return cicArray;
}
/**
* 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:
* 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 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 symbolCount, float alpha)
{
int taps = samplesPerSymbol * symbolCount;
float scale = 0;
float[] coefficients = new float[taps];
for(int x = 0; x < taps; x++)
{
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 x3 = x2 * x2 - 1.0f;
float numerator, denominator;
if(Math.abs(x3) >= 0.000001)
{
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);
}
else
{
numerator = (float)Math.cos((1.0f + alpha) * x1) + (1.0f - alpha) * (float)Math.PI / (4.0f * alpha);
}
denominator = x3 * (float)Math.PI;
}
else
{
if(alpha == 1.0f)
{
coefficients[x] = -1.0f;
continue;
}
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.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);
}
coefficients[x] = 4.0f * alpha * numerator / denominator;
scale += coefficients[x];
}
/**
* Normalize (scale) coefficients to 1.0 sum and apply gain
*/
for(int x = 0; x < taps; x++)
{
coefficients[x] = coefficients[x] / scale;
}
return coefficients;
}
/**
* Creates a filter from the filter specification using the remez exchange design algorithm
*
* @param specification
* @return filter coefficients
* @throws FilterDesignException if the filter cannot be designed
*/
public static float[] getTaps(FIRFilterSpecification specification) throws FilterDesignException
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
return designer.getImpulseResponse();
}
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,
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, 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,439 +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 dsp.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
/**
* Window factory and corresponding utility methods.
*/
public class Window
{
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 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);
}
}
/**
* 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];
Arrays.fill(coefficients, 1.0d);
return coefficients;
}
/**
* 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];
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;
}
/**
* 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 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;
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)
{
for(int x = 0; x < coefficients.length; x++)
{
samples[x] = (float)(samples[x] * coefficients[x]);
}
return 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);
}
/**
* 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("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");
private String mLabel;
WindowType(String label)
{
mLabel = label;
}
public String toString()
{
return mLabel;
}
}
}

View File

@@ -1,165 +0,0 @@
package dsp.filter.design;
import gui.control.CurveFittedAreaChart;
import javafx.collections.ObservableList;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FilterView extends BorderPane
{
private final static Logger mLog = LoggerFactory.getLogger(FilterView.class);
private CurveFittedAreaChart mImpulseChart;
private NumberAxis mImpulseXAxis;
private NumberAxis mImpulseYAxis;
private CurveFittedAreaChart mDFTChart;
private NumberAxis mDFTXAxis;
private NumberAxis mDFTYAxis;
private static final int FFT_SIZE = 2048;
private FloatFFT_1D mFFT = new FloatFFT_1D(FFT_SIZE);
public FilterView(float[] taps)
{
init();
if(taps != null)
{
setImpulseTaps(taps);
float[] dft = new float[FFT_SIZE];
System.arraycopy(taps, 0, dft, 0, taps.length);
mFFT.realForward(dft);
setDFTTaps(convertDFTToDecibel(dft, taps.length));
}
}
private void init()
{
HBox hbox = new HBox();
mImpulseXAxis = new NumberAxis(0.0, 0.5, 1.0);
mImpulseYAxis = new NumberAxis(-0.2, 1.2, .1);
mImpulseChart = new CurveFittedAreaChart(mImpulseXAxis, mImpulseYAxis);
mImpulseChart.setLegendVisible(false);
mImpulseChart.setHorizontalGridLinesVisible(false);
mImpulseChart.setVerticalGridLinesVisible(false);
mImpulseChart.setAlternativeColumnFillVisible(false);
mImpulseChart.setAlternativeRowFillVisible(false);
String css = FilterView.class.getResource("FilterView.css").toExternalForm();
mImpulseChart.getStylesheets().add(css);
hbox.getChildren().add(mImpulseChart);
HBox.setHgrow(mImpulseChart, Priority.ALWAYS);
mDFTXAxis = new NumberAxis(0.0, FFT_SIZE / 2, FFT_SIZE / 8);
mDFTYAxis = new NumberAxis(-200.0, 50.0, 10.0);
mDFTChart = new CurveFittedAreaChart(mDFTXAxis, mDFTYAxis);
mDFTChart.setLegendVisible(false);
mDFTChart.setHorizontalGridLinesVisible(false);
mDFTChart.setVerticalGridLinesVisible(false);
mDFTChart.setAlternativeColumnFillVisible(false);
mDFTChart.setAlternativeRowFillVisible(false);
mDFTChart.getStylesheets().add(css);
hbox.getChildren().add(mDFTChart);
HBox.setHgrow(mDFTChart, Priority.ALWAYS);
setCenter(hbox);
}
public void setImpulseTaps(double[] taps)
{
float[] convertedTaps = new float[taps.length];
for(int x = 0; x < taps.length; x++)
{
convertedTaps[x] = (float)taps[x];
}
setImpulseTaps(convertedTaps);
}
public void setImpulseTaps(float[] taps)
{
double smallest = 0.0;
double largest = 0.0;
final XYChart.Series<Number,Number> series = new XYChart.Series<>();
for(int x = 0; x < taps.length; x++)
{
if(taps[x] < smallest)
{
smallest = taps[x];
}
if(taps[x] > largest)
{
largest = taps[x];
}
series.getData().add(new XYChart.Data<Number,Number>(x, taps[x]));
}
mImpulseXAxis.setLowerBound(0.0);
mImpulseXAxis.setUpperBound(taps.length - 1);
mImpulseXAxis.setTickUnit(1.0);
double padding = Math.abs(largest - smallest) * .1;
mImpulseYAxis.setLowerBound(smallest - padding);
mImpulseYAxis.setUpperBound(largest + padding);
mImpulseYAxis.setTickUnit(padding);
ObservableList<Series<Number,Number>> list = mImpulseChart.getData();
list.add(series);
}
public void setDFTTaps(float[] taps)
{
double smallest = 0.0;
final XYChart.Series<Number,Number> series = new XYChart.Series<>();
for(int x = 0; x < taps.length; x++)
{
if(taps[x] < smallest)
{
smallest = taps[x];
}
series.getData().add(new XYChart.Data<Number,Number>(x, taps[x]));
}
ObservableList<Series<Number,Number>> list = mDFTChart.getData();
list.add(series);
}
public float[] convertDFTToDecibel(float[] dft, int tapLength)
{
float[] decibels = new float[dft.length / 2];
int index = 0;
for(int x = 0; x < decibels.length; x++)
{
index = x * 2;
double magnitude = Math.sqrt(Math.pow(dft[index], 2.0) + Math.pow(dft[index + 1], 2.0));
decibels[x] = (float)(20.0 * Math.log10(magnitude));
}
return decibels;
}
}

View File

@@ -1,109 +0,0 @@
package dsp.filter.design;
import dsp.filter.FilterFactory;
import dsp.filter.Window;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FilterViewer extends Application
{
private final static Logger mLog = LoggerFactory.getLogger(FilterViewer.class);
/**
* Developer tool to visualize filter designs
*/
public FilterViewer()
{
}
@Override
public void start(Stage primaryStage) throws Exception
{
float[] taps = getFilter();
if(taps != null)
{
Scene scene = new Scene(new FilterView(taps));
primaryStage.setTitle("Filter Viewer");
primaryStage.setScene(scene);
primaryStage.show();
}
else
{
throw new Exception("Couldn't start application - filter error");
}
}
public static void main(String[] args)
{
launch(args);
}
/**
* Provides the filter to visualize. Modify this method to visualize your filter design.
*/
private float[] getFilter()
{
float[] taps = null;
// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
// .sampleRate(1000)
// .gridDensity(16)
// .passBandAmplitude(1.0)
// .passBandCutoff(100)
// .passBandRipple(0.01)
// .stopBandAmplitude(0.0)
// .stopBandRipple(0.0001)
// .stopBandStart(150)
// .build();
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
// .sampleRate( 24000 )
// .stopBandCutoff( 800 )
// .stopBandAmplitude( 0.0 )
// .stopBandRipple( 0.03 )
// .passBandStart( 1000 )
// .passBandAmplitude( 1.0 )
// .passBandRipple( 0.08 )
// .build();
// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
// .sampleRate( 48000 )
// .stopFrequency1( 200 )
// .passFrequencyBegin( 400 )
// .passFrequencyEnd( 3200 )
// .stopFrequency2( 3400 )
// .stopRipple( 0.0003 )
// .passRipple( 0.008)
// .build();
// try
// {
// RemezFIRFilterDesignerWithLagrange remez = new RemezFIRFilterDesignerWithLagrange(specification);
// taps = remez.getImpulseResponse();
// }
// catch(Exception e)
// {
// mLog.error("Error designing filter", e);
// }
// taps = FilterFactory.getSinc(0.1, 51, Window.WindowType.BLACKMAN_HARRIS_7);
try
{
// taps = FilterFactory.getSincChannelizer(12500, 50, 14,
// Window.WindowType.BLACKMAN_HARRIS_7, true);
taps = FilterFactory.getRemezChannelizer(12500, 4, 22, 0.4,
0.01, 0.0001);
}
catch(Exception e)
{
mLog.error("Couldn't design filter", e);
}
return taps;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,672 +0,0 @@
package dsp.filter.fir.remez;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dsp.filter.design.FilterDesignException;
import dsp.filter.fir.FIRFilterSpecification;
public class RemezFIRFilterDesigner
{
private final static Logger mLog = LoggerFactory.getLogger( RemezFIRFilterDesigner.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 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 RemezFIRFilterDesigner( FIRFilterSpecification specification )
{
mSpecification = specification;
design();
}
/**
* Designs the filter according to the specification
*/
private void design()
{
mGrid = new Grid( mSpecification );
mExtremalIndices = getInitialExtremalIndices();
int iterationCount = 0;
try
{
do
{
calculateGridFrequencyResponse();
calculateGridError();
findExtremalIndices();
checkConvergence();
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
*/
private double getFrequencyResponse( double 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()
{
double[] b = calculateB();
calculateDelta( b );
calculateC();
calculateD( b );
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 double[] calculateB()
{
int length = mExtremalIndices.size();
double[] b = new double[ length ];
for( int k = 0; k < length; k++ )
{
b[ k ] = 1.0;
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 ] *= 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( double[] b )
{
double numerator = 0.0;
double denominator = 0.0;
double sign = 1.0;
for( int k = 0; k < b.length; k++ )
{
if( k < mExtremalIndices.size() )
{
int extremalIndex = mExtremalIndices.get( k );
numerator += ( b[ k ] * mGrid.getDesiredResponse()[ extremalIndex ] );
denominator += b[ k ] * sign / mGrid.getWeight()[ extremalIndex ];
sign = -sign;
}
else
{
mLog.error( "Something went wrong -- the length of b exceeds the set of extremal indices" );
}
}
mDelta = numerator / denominator;
}
/**
* 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( double[] b )
{
int length = mExtremalIndices.size() - 1;
mD = new double[ 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] * ( 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

@@ -1,224 +0,0 @@
package dsp.gain;
import java.util.concurrent.atomic.AtomicBoolean;
import buffer.DoubleCircularBuffer;
import buffer.FloatCircularBuffer;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2011 Alex Csete
* Copyright (C) 2010 Moe Wheatley
*
* 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/>
*
* ---------------------------------------------------
* Ported from Alex Csete's gqrx source code at:
* https://github.com/csete/gqrx/blob/74ba2742c89b0dfb66854fc9f72d19e5c6e355b6/dsp/agc_impl.cpp
*
* This Software is released under the "Simplified BSD License" + + +
* Copyright 2010 Moe Wheatley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL Moe Wheatley OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of Moe Wheatley.
******************************************************************************/
public class AutomaticGainControl
{
private static final double SAMPLE_RATE = 48000;
/* Signal delay line - time delay in seconds */
// private static final double DELAY_TIME_CONSTANT = 0.015;
private static final double DELAY_TIME_CONSTANT = 0.01;
/* Peak detector window - time delay in seconds */
private static final double WINDOW_TIME_CONSTANT = 0.018;
/* Attack time constants in seconds */
private static final double ATTACK_RISE_TIME_CONSTANT = 0.002;
private static final double ATTACK_FALL_TIME_CONSTANT = 0.005;
private static final double ATTACK_RISE_ALPHA = 1.0 -
Math.exp( -1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT );
private static final double ATTACK_FALL_ALPHA = 1.0 -
Math.exp( -1.0 / SAMPLE_RATE * ATTACK_FALL_TIME_CONSTANT );
/* AGC decay value in milliseconds (20 to 5000) */
private static final double DECAY = 200;
/* Ratio between rise and fall times of decay time constants - adjust for
* best action with SSB */
private static final double DECAY_RISEFALL_RATIO = 0.3;
private static final double DECAY_RISE_ALPHA = 1.0 -
Math.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO ) );
private static final double DECAY_FALL_ALPHA = 1.0 -
Math.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 ) );
/* Hang timer release decay time constant in seconds */
@SuppressWarnings( "unused" )
private static final double RELEASE_TIME_CONSTANT = 0.05;
/* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
private static final double THRESHOLD = -100;
/* Limit output to about 3db of maximum */
private static final double AGC_OUT_SCALE = 0.7;
/* Keep max input and output the same */
private static final double MAX_AMPLITUDE = 32767.0; //1.0;
private static final double MAX_MANUAL_AMPLITUDE = 32767.0; //1.0;
/* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */
private static final double MANUAL_GAIN = 0.0;
private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE *
Math.pow( 10.0, MANUAL_GAIN / 20.0 );
/* Specifies dB reduction in output at knee from max output level (0 - 10dB) */
private static final double SLOPE_FACTOR = 2.0;
private static final double KNEE = THRESHOLD / 20.0;
private static final double GAIN_SLOPE = SLOPE_FACTOR / 100.0;
private static final double FIXED_GAIN = AGC_OUT_SCALE *
Math.pow( 10.0, KNEE * ( GAIN_SLOPE - 1.0 ) );
/* Constant for calc log() so that a value of 0 magnitude = -8 */
private static final double MIN_CONSTANT = 3.2767E-4;
private AtomicBoolean mAGCEnabled = new AtomicBoolean( true );
private double mPeakMagnitude = 0.0;
private double mAttackAverage = 0.0;
private double mDecayAverage = 0.0;
private FloatCircularBuffer mDelayBuffer =
new FloatCircularBuffer( (int)( SAMPLE_RATE * DELAY_TIME_CONSTANT ) );
private DoubleCircularBuffer mMagnitudeBuffer =
new DoubleCircularBuffer( (int)( SAMPLE_RATE * WINDOW_TIME_CONSTANT ) );
public AutomaticGainControl()
{
}
public float process( float currentSample )
{
float delayedSample = mDelayBuffer.putAndGet( currentSample );
double gain = MANUAL_AGC_GAIN;
if( mAGCEnabled.get() )
{
double currentMagnitude = Math.log10( Math.abs( currentSample ) +
MIN_CONSTANT ) - Math.log10( MAX_AMPLITUDE );
double delayedMagnitude = mMagnitudeBuffer.get( currentMagnitude );
if( currentMagnitude > mPeakMagnitude )
{
/* Use current magnitude as peak if it's larger */
mPeakMagnitude = currentMagnitude;
}
else if( delayedMagnitude == mPeakMagnitude )
{
/* If delayed magnitude is the current peak, then find a new peak */
mPeakMagnitude = mMagnitudeBuffer.max();
}
/* Exponential decay mode */
if( mPeakMagnitude > mAttackAverage )
{
mAttackAverage = ( ( 1.0 - ATTACK_RISE_ALPHA ) * mAttackAverage ) +
( ATTACK_RISE_ALPHA * mPeakMagnitude );
}
else
{
mAttackAverage = ( ( 1.0 - ATTACK_FALL_ALPHA ) * mAttackAverage ) +
( ATTACK_FALL_ALPHA * mPeakMagnitude );
}
if( mPeakMagnitude > mDecayAverage )
{
mDecayAverage = ( ( 1.0 - DECAY_RISE_ALPHA ) * mDecayAverage ) +
( DECAY_RISE_ALPHA * mPeakMagnitude );
}
else
{
mDecayAverage = ( ( 1.0 - DECAY_FALL_ALPHA ) * mDecayAverage ) +
( DECAY_RISE_ALPHA * mPeakMagnitude );
}
double magnitude = ( mAttackAverage > mDecayAverage ) ?
mAttackAverage : mDecayAverage;
if( magnitude < KNEE )
{
gain = FIXED_GAIN;
}
else
{
gain = AGC_OUT_SCALE * Math.pow( 10.0,
magnitude * ( GAIN_SLOPE - 1.0 ) );
}
}
return (float)( delayedSample * gain );
}
/**
* Enables or disables Automatic Gain Control (AGC).
*/
public void setAGCEnabled( boolean enabled )
{
mAGCEnabled.set( enabled );
}
/**
* Indicates if AGC is enabled
* @return true=AGC, false=MANUAL GAIN
*/
public boolean isAGCEnabled()
{
return mAGCEnabled.get();
}
}

View File

@@ -1,112 +0,0 @@
package dsp.gain;
import sample.complex.Complex;
import sample.complex.ComplexSampleListener;
import buffer.FloatCircularBuffer;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* This class is modeled using concepts detailed in the GNURadio
* implementation of feed forward AGC class located at:
* https://github.com/gnuradio/gnuradio/blob/master/gr-analog/lib/
* feedforward_agc_cc_impl.cc
*
******************************************************************************/
public class ComplexFeedForwardGainControl implements ComplexSampleListener
{
public static final float OBJECTIVE_ENVELOPE = 1.0f;
public static final float MINIMUM_ENVELOPE = 0.0001f;
private ComplexSampleListener mListener;
private FloatCircularBuffer mEnvelopeHistory;
private float mMaxEnvelope = 0.0f;
private float mGain = 1.0f;
/**
* Dynamic gain control for incoming sample stream to amplify or attenuate
* all samples toward an objective unity)gain, using the maximum envelope
* value detected in the stream history window.
*
* Uses the specified damping factor to limit gain swings. Damping factor
* is applied against the delta between current gain value and a recalculated
* gain value to limit how quickly the gain value will increase or decrease.
*
* @param window - history size to use in detecting maximum envelope value
* @param damping - a value between 0 < damping <= 1.0;
*/
public ComplexFeedForwardGainControl( int window )
{
mEnvelopeHistory = new FloatCircularBuffer( window );
}
public void dispose()
{
mListener = null;
}
@Override
public void receive( float inphase, float quadrature )
{
float envelope = Complex.envelope( inphase, quadrature );
if( envelope > mMaxEnvelope )
{
mMaxEnvelope = envelope;
adjustGain();
}
/* Replace oldest envelope value with current envelope value */
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 */
if( mMaxEnvelope == oldestEnvelope && mMaxEnvelope != envelope )
{
mMaxEnvelope = MINIMUM_ENVELOPE;
for( float value: mEnvelopeHistory.getBuffer() )
{
if( value > mMaxEnvelope )
{
mMaxEnvelope = value;
}
}
adjustGain();
}
/* Apply current gain value to the sample and send to the listener */
if( mListener != null )
{
mListener.receive( inphase *= mGain, quadrature *= mGain );
}
}
private void adjustGain()
{
mGain = OBJECTIVE_ENVELOPE / mMaxEnvelope;
}
public void setListener( ComplexSampleListener listener )
{
mListener = listener;
}
}

View File

@@ -1,152 +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 dsp.mixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.Complex;
import sample.complex.ComplexBuffer;
public class Oscillator
{
private final static Logger mLog = LoggerFactory.getLogger(Oscillator.class);
private boolean mEnabled = true;
private double mFrequency;
private double mSampleRate;
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, double sampleRate)
{
mEnabled = frequency != 0;
mSampleRate = sampleRate;
mFrequency = (double)frequency;
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;
}
/**
* Updates the internal values after a frequency or sample rate change
*/
private void update()
{
float anglePerSample =
(float)(2.0d * Math.PI * mFrequency / mSampleRate);
mAnglePerSample = Complex.fromAngle(anglePerSample);
}
/**
* 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

@@ -1,589 +0,0 @@
package dsp.psk;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014, 2015 Dennis Sheirer
*
* Gardner detector, Costas loop and Interpolator derived from:
*
* OP25 - gardner_costas_cc_impl.cc
* Copyright 2010, 2011, 2012, 2013 KA1RBI (gardner_costas_cc_impl.cc)
*
* OP25 - scope.py
* Copyright 2008-2011 Steve Glass
* Copyright 2011, 2012, 2013 KA1RBI
*
* GNURadio - control_loop and mpsk_receiver_cc
* Copyright 2011,2013 Free Software Foundation, Inc.
*
* 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/>
******************************************************************************/
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.TapGroup;
import instrument.tap.stream.EyeDiagramData;
import instrument.tap.stream.EyeDiagramDataTap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import sample.Listener;
import sample.complex.Complex;
import sample.complex.ComplexSampleListener;
import source.ISourceEventListener;
import buffer.FloatAveragingBuffer;
import dsp.filter.interpolator.RealInterpolator;
import dsp.symbol.Dibit;
/**
* Implements a LSM (Pi/4) demodulator using a Gardner Detector to determine
* optimal sampling timing and a Costas Loop as a phase locked loop synchronized
* with the incoming signal carrier frequency.
*
* Sample Rate: 48000
* Symbol Rate: 4800
*/
public class LSMDemodulator implements Instrumentable, ComplexSampleListener
{
private List<TapGroup> mAvailableTaps;
/* 45 degree rotation angle */
public static final float THETA = (float)( Math.PI / 4.0d );
/* 45 degree point */
public static final Complex POINT_45_DEGREES =
new Complex( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
private Listener<Complex> mSymbolListener;
private GardnerSymbolTiming mGardnerDetector = new GardnerSymbolTiming();
private CostasLoop mCostasLoop = new CostasLoop();
private ISourceEventListener mFrequencyChangeListener;
private EyeDiagramDataTap mEyeDiagramDataTap;
public LSMDemodulator()
{
}
public void dispose()
{
mSymbolListener = null;
mFrequencyChangeListener = null;
}
public void setSymbolListener( Listener<Complex> listener )
{
mSymbolListener = listener;
}
public void removeSymbolListener()
{
mSymbolListener = null;
}
@Override
public void receive( float inphase, float quadrature )
{
mGardnerDetector.receive( inphase, quadrature );
}
/**
* Applies a phase correction value to the costas loop to correct when a
* phase lock error is detected in the binary output stream.
*
* @param correction - value in radians
*/
public void correctPhaseError( double correction )
{
mCostasLoop.correctPhaseError( correction );
}
/**
* Constrains value to the range of ( -maximum <> maximum )
*/
public static float clip( float value, float maximum )
{
if( value > maximum )
{
return maximum;
}
else if( value < -maximum )
{
return -maximum;
}
return value;
}
/**
* Constrains timing error to +/- the maximum value and corrects any
* floating point invalid numbers
*/
private float normalize( float error, float maximum )
{
if( Float.isNaN( error ) )
{
return 0.0f;
}
else
{
return clip( error, maximum );
}
}
public void addListener( ISourceEventListener listener )
{
mFrequencyChangeListener = listener;
}
public void removeListener( ISourceEventListener listener )
{
mFrequencyChangeListener = null;
}
/**
* Processes the current loop frequency of the costas loop and broadcasts
* tuner frequency offset adjustments 10 times a second as needed to keep
* the frequency offset below half of the maximum frequency
*/
public class FrequencyControl
{
/* Set the trigger threshold at half of the maximum costas loop control
* frequency */
private static final float CORRECTION_THRESHOLD_FREQUENCY =
( 2.0f * (float)Math.PI * 600.0f ) / 48000.0f;
private FloatAveragingBuffer mBuffer = new FloatAveragingBuffer( 40 );
private long mFrequencyError = 0;
private int mCounter = 0;
public void receive( float frequency )
{
// mCounter++;
//
// if( mCounter >= 480 )
// {
// float average = mBuffer.get( frequency );
// mLog.debug( "Avg:" + average + " Threshold:" + CORRECTION_THRESHOLD_FREQUENCY);
//
// boolean correctionNeeded = false;
//
// if( average > CORRECTION_THRESHOLD_FREQUENCY )
// {
// mFrequencyError--;
//
// correctionNeeded = true;
// }
// else if( average < -CORRECTION_THRESHOLD_FREQUENCY )
// {
// mFrequencyError++;
//
// correctionNeeded = true;
// }
//
// if( correctionNeeded && mFrequencyChangeListener != null )
// {
// mLog.debug( "Issuing Frequency Correction: " + mFrequencyError );
// mFrequencyChangeListener.process(
// new SourceEvent( Attribute.FREQUENCY_ERROR,
// mFrequencyError ) );
// }
//
// mCounter = 0;
// }
// else
// {
// mBuffer.get( frequency );
// }
}
}
/**
* Gardner symbol timing detector
*/
public class GardnerSymbolTiming implements ComplexSampleListener
{
public static final int HALF_SAMPLES_PER_SYMBOL = 5;
public static final int SAMPLES_PER_SYMBOL = 10;
public static final int TWICE_SAMPLES_PER_SYMBOL = 20;
private float[] mDelayLineInphase = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineQuadrature = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private int mDelayLinePointer = 0;
/* Sampling point */
private float mMu = 10.0f;
private float mGainMu = 0.05f;
/* Samples per symbol */
private float mOmega = 10.0f;
private float mGainOmega = 0.1f * mGainMu * mGainMu;
private float mOmegaRel = 0.005f;
private float mOmegaMid = 10.0f;
private RealInterpolator mInterpolator = new RealInterpolator( 1.0f );
private Complex mPreviousSample = new Complex( 0, 0 );
private Complex mPreviousMiddleSample = new Complex( 0, 0 );
private Complex mPreviousSymbol = new Complex( 0, 0 );
/**
* Provides symbol sampling timing control
*/
public GardnerSymbolTiming()
{
}
@Override
public void receive( float inphase, float quadrature )
{
/* Count down samples per symbol until we calculate the symbol */
mMu--;
mCostasLoop.increment();
/* Mix incoming sample with costas loop to remove any rotation
* that is present from a mis-tuned carrier frequency */
Complex costas = mCostasLoop.getCurrentVector();
float derotatedInphase = Complex.multiplyInphase( inphase, quadrature, costas.inphase(), costas.quadrature() );
float derotatedQuadrature = Complex.multiplyQuadrature( inphase, quadrature, costas.inphase(), costas.quadrature() );
/* Fill up the delay line to use with the interpolator */
mDelayLineInphase[ mDelayLinePointer ] = derotatedInphase;
mDelayLineInphase[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedInphase;
mDelayLineQuadrature[ mDelayLinePointer ] = derotatedQuadrature;
mDelayLineQuadrature[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedQuadrature;
/* Increment pointer and keep pointer in bounds */
mDelayLinePointer = ( mDelayLinePointer + 1 ) % TWICE_SAMPLES_PER_SYMBOL;
/* Calculate the symbol once we've stored enough samples */
if( mMu <= 1.0f )
{
float half_omega = mOmega / 2.0f;
int half_sps = (int)Math.floor( half_omega );
float half_mu = mMu + half_omega - (float)half_sps;
if( half_mu > 1.0 )
{
half_mu -= 1.0;
half_sps += 1;
}
/* Calculate interpolated middle sample and current sample */
float middleSampleInphase = mInterpolator.filter(
mDelayLineInphase, mDelayLinePointer, mMu );
float middleSampleQuadrature = mInterpolator.filter(
mDelayLineQuadrature, mDelayLinePointer, mMu );
int index = mDelayLinePointer + half_sps;
float currentSampleInphase = mInterpolator.filter(
mDelayLineInphase, index, half_mu );
float currentSampleQuadrature = mInterpolator.filter(
mDelayLineQuadrature, index, half_mu );
/* Multiply current sample and conjugate (negated quadrature) of
* previous sample to get symbols to use for gardner error feedback */
Complex middleSymbol = Complex.multiply( middleSampleInphase,
middleSampleQuadrature, mPreviousMiddleSample.conjugate() );
Complex currentSymbol = Complex.multiply( currentSampleInphase,
currentSampleQuadrature, mPreviousSample.conjugate() );
/* Set gain to unity */
middleSymbol.normalize();
currentSymbol.normalize();
/* Gardner timing error calculations */
float errorInphase = ( mPreviousSymbol.inphase() -
currentSymbol.inphase() ) * middleSymbol.inphase();
float errorQuadrature = ( mPreviousSymbol.quadrature() -
currentSymbol.quadrature() ) * middleSymbol.quadrature();
float gardnerError = normalize( errorInphase + errorQuadrature, 1.0f );
if( mEyeDiagramDataTap != null )
{
mEyeDiagramDataTap.receive(
new EyeDiagramData(
Arrays.copyOfRange( mDelayLineInphase, 0, 20 ),
Arrays.copyOfRange( mDelayLineQuadrature, 0, 20 ),
mMu, (float)half_sps + half_mu, gardnerError ) );
}
/* mOmega is samples per symbol and is constrained to floating
* between +/- .005 of the nominal 10.0 samples per symbol */
mOmega = mOmega + mGainOmega * gardnerError;
mOmega = mOmegaMid + clip( mOmega - mOmegaMid, mOmegaRel );
/* Adjust sample timing based on error of current sample */
mMu += mOmega + ( mGainMu * gardnerError );
/* Store current samples/symbols to use for the next period */
mPreviousSample.setValues( currentSampleInphase, currentSampleQuadrature );
mPreviousMiddleSample.setValues( middleSampleInphase, middleSampleQuadrature );
mPreviousSymbol = currentSymbol;
/* Update costas loop using phase error present in current
* symbol. The symbol is rotated from star orientation to polar
* orientation to simplify error calculation */
mCostasLoop.receive( currentSymbol );
// mCostasLoop.receive(
// ComplexSample.multiply( currentSymbol, POINT_45_DEGREES ) );
/* Dispatch the differentiated symbol to the registered listener */
if( mSymbolListener != null )
{
mSymbolListener.receive( currentSymbol );
}
}
}
}
/**
* Costas Loop - phase locked loop synchronized to the frequency offset of
* the incoming signal to enable the signal to be mixed down to zero offset
* for proper synchronization. The mFrequency value indicates the detected
* frequency offset. We attempt to keep that value close to zero by issuing
* frequency adjustments to the tuner channel source.
*
* Most of the costas loop code was ported from gnuradio/control_loop and
* gnuradio/mpsk_receiver_cc using initialization values from KA1RBI's
* OP25/cqpsk.py
*/
public class CostasLoop implements Listener<Complex>
{
public static final double TWO_PI = 2.0 * Math.PI;
private static final float MAXIMUM_FREQUENCY =
( 2400.0f * (float)TWO_PI ) / 48000.0f;
/* http://www.trondeau.com/blog/2011/8/13/control-loop-gain-values.html */
private float mDamping = (float)Math.sqrt( 2.0 ) / 2.0f;
/* Use denominator between 100 (most) and 400 (least) to adjust costas
* loop control level */
private float mLoopBandwidth = (float)( TWO_PI / 400.0d );
private float mAlphaGain = ( 4.0f * mDamping * mLoopBandwidth ) /
( 1.0f + ( 2.0f * mDamping * mLoopBandwidth ) +
( mLoopBandwidth * mLoopBandwidth ) );
private float mBetaGain = ( 4.0f * mLoopBandwidth * mLoopBandwidth ) /
( 1.0f + ( 2.0f * mDamping * mLoopBandwidth ) +
( mLoopBandwidth * mLoopBandwidth ) );
private float mLoopPhase = 0.0f;
private float mLoopFrequency = 0.0f;
private FrequencyControl mFrequencyControl = new FrequencyControl();
public CostasLoop()
{
}
/**
* Applies a phase correction value to the current phasor. Use this
* method to apply correction when a +/- 90, or 180 degree phase error
* is detected in the binary output stream.
*
* If the supplied correction value places the loop frequency outside
* of the max frequency, then the frequency will be corrected 360
* degrees in the opposite direction to maintain within the max
* frequency bounds.
*
* @param correction - correction value in radians
*/
public void correctPhaseError( double correction )
{
mLoopFrequency += correction;
if( mLoopFrequency > MAXIMUM_FREQUENCY )
{
mLoopFrequency -= 2.0d * MAXIMUM_FREQUENCY;
}
if( mLoopFrequency < -MAXIMUM_FREQUENCY )
{
mLoopFrequency += 2.0d * MAXIMUM_FREQUENCY;
}
}
/**
* Increments the phase of the loop
*/
public void increment()
{
mLoopPhase += mLoopFrequency;
/* Keep the loop phase in bounds */
unwrapPhase();
}
private void unwrapPhase()
{
while( mLoopPhase > TWO_PI )
{
mLoopPhase -= TWO_PI;
}
while( mLoopPhase < -TWO_PI )
{
mLoopPhase += TWO_PI;
}
}
/**
* Current vector of the loop rotated by THETA degrees.
*/
public Complex getCurrentVector()
{
// return ComplexSample.fromAngle( mLoopPhase + THETA );
return Complex.fromAngle( mLoopPhase );
}
@Override
public void receive( Complex complex )
{
/* Calculate phase error */
float phaseError = getPhaseError( complex );
Dibit d = QPSKPolarSlicer.decide( complex );
// mLog.debug( "Dibit: " + d.name() + " Error: " + phaseError );
/* Adjust for phase error */
adjust( phaseError );
}
/**
* Updates the costas loop frequency and phase to adjust for the phase
* error value
*
* @param phase_error - (-)= late and (+)= early
*/
private void adjust( float phase_error )
{
mLoopFrequency += mBetaGain * phase_error;
mLoopPhase += mLoopFrequency + mAlphaGain * phase_error;
/* Maintain phase between +/- 2 * PI */
unwrapPhase();
/* Limit frequency to +/- maximum loop frequency */
limitFrequency();
}
/**
* Constrains the frequency within the bounds of +/- loop frequency
*/
private void limitFrequency()
{
/* Check for and issue tuner offset correction */
mFrequencyControl.receive( mLoopFrequency );
if( mLoopFrequency > MAXIMUM_FREQUENCY )
{
mLoopFrequency = MAXIMUM_FREQUENCY;
}
if( mLoopFrequency < -MAXIMUM_FREQUENCY )
{
mLoopFrequency = -MAXIMUM_FREQUENCY;
}
}
/**
* Detects rotational error present when the costas loop is not aligned
* with the carrier frequency. Provides error feedback to adjust
* mixer frequency.
*/
public float getPhaseError( Complex complex )
{
float phase_error = 0;
if( Math.abs( complex.inphase() ) > Math.abs( complex.quadrature() ) )
{
if( complex.inphase() > 0 )
{
phase_error = -complex.quadrature();
}
else
{
phase_error = complex.quadrature();
}
}
else
{
if( complex.quadrature() > 0 )
{
phase_error = complex.inphase();
}
else
{
phase_error = -complex.inphase();
}
}
return phase_error;
}
}
@Override
public List<TapGroup> getTapGroups()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<>();
TapGroup group = new TapGroup( "LSM Demodulator" );
group.add( new EyeDiagramDataTap( "Eye Diagram", 0, 4800 ) );
mAvailableTaps.add( group );
}
return mAvailableTaps;
}
@Override
public void registerTap( Tap tap )
{
if( tap instanceof EyeDiagramDataTap )
{
mEyeDiagramDataTap = (EyeDiagramDataTap)tap;
}
}
@Override
public void unregisterTap( Tap tap )
{
if( tap instanceof EyeDiagramDataTap )
{
mEyeDiagramDataTap = null;
}
}
}

View File

@@ -1,506 +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 gui.control;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import source.ISourceEventProcessor;
import source.InvalidFrequencyException;
import source.SourceEvent;
import source.SourceEvent.Event;
import source.SourceException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
private Color mHighlightColor = Color.YELLOW;
private long mFrequency;
private Cursor mBlankCursor;
private HashMap<Integer,Digit> mDigits = new HashMap<Integer,Digit>();
public JFrequencyControl()
{
init();
}
public JFrequencyControl(long value)
{
this();
mFrequency = value;
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "[]0[]", ""));
Font font = new Font(Font.MONOSPACED, Font.BOLD, 30);
for(int x = 9; x >= 0; x--)
{
Digit digit = null;
try
{
digit = new Digit(x);
}
catch(ParseException e)
{
mLog.error("JFrequencyControl - parse exception "
+ "constructing a digit - " + e);
}
if(digit != null)
{
mDigits.put(x, digit);
add(digit);
digit.setFont(font);
if(x == 6)
{
JLabel period = new JLabel(".");
add(period);
}
}
}
add(new JLabel(" MHz"));
/**
* Create a blank cursor to use when the mouse is over the digits
*/
//Create an empty byte array
byte[] imageByte = new byte[0];
//Create image for cursor using empty array
Image cursorImage = Toolkit.getDefaultToolkit().createImage(imageByte);
mBlankCursor = Toolkit.getDefaultToolkit()
.createCustomCursor(cursorImage, new Point(0, 0), "cursor");
revalidate();
}
@Override
public void setEnabled(boolean enabled)
{
super.setEnabled(enabled);
for(Digit digit : mDigits.values())
{
digit.setEnabled(enabled);
}
}
/**
* Receives a frequency change event invoked by another control. We don't
* rebroadcast this event, just set the control to indicate the new frequency.
*/
@Override
public void process(SourceEvent event)
{
if(event.getEvent() == Event.NOTIFICATION_FREQUENCY_CHANGE)
{
setFrequency(event.getValue().longValue(), false);
}
}
/**
* Loads the frequency into the display and optionally fires a change event
* to all registered listeners
*/
public void setFrequency(long frequency, boolean fireChangeEvent)
{
mFrequency = frequency;
for(Digit digit : mDigits.values())
{
digit.setFrequency(frequency, fireChangeEvent);
}
}
public long getFrequency()
{
return mFrequency;
}
private void updateFrequency()
{
long frequency = 0;
for(Digit digit : mDigits.values())
{
frequency += digit.getFrequency();
}
mFrequency = frequency;
}
private void fireSourceChanged() throws SourceException
{
updateFrequency();
Iterator<ISourceEventProcessor> it = mProcessors.iterator();
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
while(it.hasNext())
{
it.next().process(event);
}
}
public void addListener(ISourceEventProcessor processor)
{
mProcessors.add(processor);
}
public void removeListener(ISourceEventProcessor processor)
{
mProcessors.remove(processor);
}
public class Digit extends JTextField
{
private static final long serialVersionUID = 1L;
private int mPower = 0;
private long mValue = 0;
private Digit(int position) throws ParseException
{
super("0");
mPower = position;
Listener listener = new Listener();
this.addKeyListener(listener);
this.addMouseListener(listener);
this.addMouseWheelListener(listener);
}
/**
* Sets this digit to the value of the column in frequency that corresponds
* to the power (ie column) set for this digit. Optionally, fires a
* value change event to all listeners.
*/
public void setFrequency(long frequency, boolean fireChangeEvent)
{
//Strip the digits higher than this one
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)));
set(value, fireChangeEvent);
}
public long getFrequency()
{
return mValue * (long)Math.pow(10, mPower);
}
public void increment()
{
increment(true);
}
public void increment(boolean fireChangeEvent)
{
if(isEnabled())
{
set(mValue + 1, fireChangeEvent);
}
}
public void decrement()
{
decrement(true);
}
public void decrement(boolean fireChangeEvent)
{
if(isEnabled())
{
set(mValue - 1, fireChangeEvent);
}
}
/**
* Convenience wrapper to change amount and fire change event
*/
private void set(long amount)
{
set(amount, true);
}
/**
* Changes the value and optionally fires change event to listeners
*/
private void set(long amount, boolean fireChangeEvent)
{
long previous = mValue;
boolean higherDigitIncremented = false;
boolean higherDigitDecremented = false;
mValue = amount;
while(mValue < 0)
{
mValue += 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
{
nextHigherDigit.decrement(false);
higherDigitDecremented = true;
}
}
while(mValue > 9)
{
mValue -= 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
{
nextHigherDigit.increment(false);
higherDigitIncremented = true;
}
}
if(fireChangeEvent)
{
try
{
fireSourceChanged();
}
catch(SourceException se)
{
mValue = previous;
if(higherDigitIncremented)
{
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
{
nextHigherDigit.decrement(false);
}
}
if(higherDigitDecremented)
{
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
{
nextHigherDigit.increment(false);
}
}
if(se instanceof InvalidFrequencyException)
{
InvalidFrequencyException ife = (InvalidFrequencyException)se;
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
}
}
}
setText(String.valueOf(mValue));
repaint();
}
private class Listener implements KeyListener,
MouseListener,
MouseWheelListener
{
@Override
public void keyReleased(KeyEvent e)
{
int key = e.getKeyCode();
switch(key)
{
case KeyEvent.VK_0:
case KeyEvent.VK_NUMPAD0:
set(0);
Digit.this.transferFocus();
break;
case KeyEvent.VK_1:
case KeyEvent.VK_NUMPAD1:
set(1);
Digit.this.transferFocus();
break;
case KeyEvent.VK_2:
case KeyEvent.VK_NUMPAD2:
set(2);
Digit.this.transferFocus();
break;
case KeyEvent.VK_3:
case KeyEvent.VK_NUMPAD3:
set(3);
Digit.this.transferFocus();
break;
case KeyEvent.VK_4:
case KeyEvent.VK_NUMPAD4:
set(4);
Digit.this.transferFocus();
break;
case KeyEvent.VK_5:
case KeyEvent.VK_NUMPAD5:
set(5);
Digit.this.transferFocus();
break;
case KeyEvent.VK_6:
case KeyEvent.VK_NUMPAD6:
set(6);
Digit.this.transferFocus();
break;
case KeyEvent.VK_7:
case KeyEvent.VK_NUMPAD7:
set(7);
Digit.this.transferFocus();
break;
case KeyEvent.VK_8:
case KeyEvent.VK_NUMPAD8:
set(8);
Digit.this.transferFocus();
break;
case KeyEvent.VK_9:
case KeyEvent.VK_NUMPAD9:
set(9);
Digit.this.transferFocus();
break;
case KeyEvent.VK_LEFT:
Digit.this.transferFocusBackward();
break;
case KeyEvent.VK_RIGHT:
Digit.this.transferFocus();
break;
case KeyEvent.VK_UP:
increment();
break;
case KeyEvent.VK_DOWN:
decrement();
break;
case KeyEvent.VK_TAB:
break;
default:
set(mValue);
break;
}
repaint();
}
@Override
public void keyPressed(KeyEvent e)
{
}
@Override
public void keyTyped(KeyEvent e)
{
}
@Override
public void mouseClicked(MouseEvent e)
{
switch(e.getButton())
{
case MouseEvent.BUTTON1:
increment();
break;
case MouseEvent.BUTTON2:
break;
case MouseEvent.BUTTON3:
decrement();
break;
}
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
@Override
public void mouseEntered(MouseEvent e)
{
Digit.this.setBackground(mHighlightColor);
setCursor(mBlankCursor);
repaint();
}
@Override
public void mouseExited(MouseEvent e)
{
Digit.this.setBackground(Color.WHITE);
setCursor(Cursor.getDefaultCursor());
repaint();
}
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
set(mValue - e.getWheelRotation());
}
}
}
}

View File

@@ -1,156 +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 instrument.tap.stream;
import instrument.gui.SampleModel;
import instrument.tap.Tap;
import instrument.tap.TapViewPanel;
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;
import java.util.Observable;
public class BinaryTapViewPanel extends TapViewPanel
{
private static final long serialVersionUID = 1L;
private Tap mTap;
private List<Boolean> mSamples;
private int mSampleCount;
public BinaryTapViewPanel( BinaryTap tap )
{
super( new SampleModel<Boolean>(), tap.getName() );
mTap = tap;
mTap.addListener( getModel() );
getModel().setSampleCount(
(int)( getModel().getSampleCount() * tap.getSampleRateRatio() ) );
getModel().setDelay( tap.getDelay() );
}
@Override
public void update( Observable arg0, Object arg1 )
{
mSamples = (List<Boolean>)getModel().getSamples();
mSampleCount = getModel().getSampleCount();
repaint();
}
public void paintComponent( Graphics g )
{
super.paintComponent( g );
Graphics2D g2 = (Graphics2D)g;
int height = getHeight();
int width = getWidth();
g2.setColor( Color.DARK_GRAY );
float middle = (float)height / 2.0f;
g2.drawLine( 0, (int)middle, width, (int)middle );
if( mSamples != null && mSamples.size() > 0 )
{
g2.setColor( getForeground() );
g2.drawString( mLabel, 5 , 15 );
for( int x = 1; x < mSamples.size(); x++ )
{
paintBit( g, x, mSamples.get( x ) );
}
}
g2.dispose();
}
/**
* Paints a polyline representing a boolean value with a 0 or 1 label in
* the middle of it
*
* @param graphics object
* @param index - index of the sample int the sample buffer
* @param value - bit value, 0 or 1
*/
private void paintBit( Graphics graphics, int index, boolean value )
{
Graphics2D g2 = (Graphics2D)graphics;
Path2D.Float polyline =
new Path2D.Float( Path2D.Float.WIND_EVEN_ODD, mSamples.size() );
float middleY = ( (float)getHeight() / 2.0f );
float indexWidth = (float)( getWidth() ) / (float)mSampleCount;
float startX = (float)index * indexWidth;
float stopX = (float)(index + 1 ) * indexWidth;
float fivePercent = indexWidth * .05f;
float leftX = startX + fivePercent;
float rightX = stopX - fivePercent;
/* Start */
polyline.moveTo( startX, middleY );
/* Left horizonal segment */
polyline.lineTo( leftX, middleY );
float y = ( value ? ( middleY - ( middleY * 0.5f) ) :
( middleY + ( middleY * 0.5f) ) );
/* Left vertical segment */
polyline.lineTo( leftX, y );
/* Middle horizontal segment */
polyline.lineTo( rightX, y );
/* Right vertical segment */
polyline.lineTo( rightX, middleY );
/* Right horizontal segment */
polyline.lineTo( stopX, middleY );
g2.draw( polyline );
/* Draw the bit value label */
String bit = value ? "1" : "0";
FontMetrics fontMetrics = graphics.getFontMetrics( this.getFont() );
Rectangle2D label = fontMetrics.getStringBounds( bit, graphics );
float offsetX = (float)label.getWidth() / 2.0f;
float offsetY = (float)label.getWidth() / 2.0f;
float middleX = startX + ( indexWidth / 2.0f );
graphics.drawString( bit,
(int)( middleX - offsetX ),
(int)( middleY - offsetY ) );
}
}

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

@@ -1,31 +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.channel.heartbeat;
public class Heartbeat
{
/**
* Periodic pulse that is broadcast to subscribing modules within the demodulating chain so that
* monitoring of state and other attributes can occur on the primary demodulation and decoding
* thread.
*/
public Heartbeat()
{
}
}

View File

@@ -1,28 +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.channel.heartbeat;
import io.github.dsheirer.sample.Listener;
public interface IHeartbeatProvider
{
public void setHeartbeatListener(Listener<Heartbeat> listener);
public void removeHeartbeatListener();
}

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

@@ -16,16 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.filter.channelizer;
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 sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexBuffer;
import source.ISourceEventListener;
import source.SourceEvent;
import source.tuner.channel.polyphase.PolyphaseChannelSource;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

View File

@@ -1,26 +1,24 @@
/*******************************************************************************
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* 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 source.tuner.channel.polyphase;
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import source.tuner.channel.TunerChannel;
import java.text.DecimalFormat;
import java.util.ArrayList;

View File

@@ -1,46 +1,49 @@
/*******************************************************************************
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* 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 source.tuner.channel.polyphase;
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 sample.Listener;
import sample.SampleType;
import sample.complex.ComplexBuffer;
import settings.SettingsManager;
import source.ISourceEventProcessor;
import source.SourceEvent;
import source.SourceException;
import source.tuner.channel.TunerChannel;
import source.tuner.channel.TunerChannelSource;
import source.tuner.configuration.TunerConfigurationModel;
import source.tuner.test.TestTuner;
import spectrum.DFTProcessor;
import spectrum.DFTSize;
import spectrum.SpectrumPanel;
import spectrum.converter.ComplexDecibelConverter;
import javax.swing.*;
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.*;
import java.awt.Dimension;
import java.awt.EventQueue;
public class ChannelizerViewer extends JFrame
{
@@ -70,10 +73,10 @@ public class ChannelizerViewer extends JFrame
{
mTestTuner = new TestTuner();
mChannelCount = (int)(mTestTuner.getTunerController().getFrequencyController().getSampleRate() / CHANNEL_BANDWIDTH);
mChannelCount = (int)(mTestTuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH);
mChannelsPerRow = channelsPerRow;
mToneFrequency = mTestTuner.getTunerController().getFrequencyController().getFrequency() + 19200;
mToneFrequency = mTestTuner.getTunerController().getFrequency() + 19200;
mTestTuner.getTunerController().setToneFrequency(mToneFrequency);
@@ -109,7 +112,7 @@ public class ChannelizerViewer extends JFrame
if(mPrimarySpectrumPanel == null)
{
mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager,
mTestTuner.getTunerController().getFrequencyController().getSampleRate());
mTestTuner.getTunerController().getSampleRate());
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1000, 200));
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
mTestTuner.getSourceManager().addComplexBufferListener(mPrimarySpectrumPanel);
@@ -126,9 +129,9 @@ public class ChannelizerViewer extends JFrame
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
mControlPanel.add(new JLabel("Tone:"), "align left");
int maximumFrequencyOffset = (int)(mTestTuner.getTunerController().getFrequencyController().getSampleRate() / 2);
long minimumFrequency = mTestTuner.getTunerController().getFrequencyController().getFrequency() - maximumFrequencyOffset;
long maximumFrequency = mTestTuner.getTunerController().getFrequencyController().getFrequency() + maximumFrequencyOffset;
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 );
@@ -148,11 +151,9 @@ public class ChannelizerViewer extends JFrame
mControlPanel.add(spinner);
mControlPanel.add(new JLabel("Hz"));
mControlPanel.add(new JLabel("Sample Rate: " +
mTestTuner.getTunerController().getFrequencyController().getSampleRate() + " Hz"), "push,align right");
mControlPanel.add(new JLabel("Sample Rate: " + mTestTuner.getTunerController().getSampleRate() + " Hz"), "push,align right");
mControlPanel.add(new JLabel("Max: " +
(mTestTuner.getTunerController().getFrequencyController().getSampleRate() / 2) + " 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");
}
@@ -186,7 +187,7 @@ public class ChannelizerViewer extends JFrame
{
setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill"));
double spectralBandwidth = mTestTuner.getTunerController().getFrequencyController().getSampleRate();
double spectralBandwidth = mTestTuner.getTunerController().getSampleRate();
double halfSpectralBandwidth = spectralBandwidth / 2.0;
int channelToLog = -1;
@@ -194,7 +195,7 @@ public class ChannelizerViewer extends JFrame
for(int x = 0; x < mChannelCount; x++)
{
//place the channels left and right of 10.0 MHz
long frequency = (long)(mTestTuner.getTunerController().getFrequencyController().getFrequency() +
long frequency = (long)(mTestTuner.getTunerController().getFrequency() +
((x * CHANNEL_BANDWIDTH) - halfSpectralBandwidth) + (CHANNEL_BANDWIDTH / 2));
mLog.debug("Channel " + x + " Frequency: " + frequency);
@@ -340,4 +341,4 @@ public class ChannelizerViewer extends JFrame
});
}
}
}

View File

@@ -16,16 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.filter.channelizer;
package io.github.dsheirer.dsp.filter.channelizer;
import dsp.filter.FilterFactory;
import dsp.filter.Window;
import dsp.filter.fir.real.RealFIRFilter;
import dsp.mixer.Oscillator;
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;
import sample.complex.ComplexBuffer;
public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannelizer
{

View File

@@ -16,17 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.filter.channelizer;
package io.github.dsheirer.dsp.filter.channelizer;
import dsp.filter.FilterFactory;
import dsp.filter.Window;
import dsp.filter.design.FilterDesignException;
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;
import sample.complex.ComplexBuffer;
import java.text.DecimalFormat;
public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChannelizer
{
@@ -83,7 +81,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
super(sampleRate, (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH));
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
tapsPerFilter, Window.WindowType.BLACKMAN_HARRIS_7, true);
tapsPerFilter, WindowType.BLACKMAN_HARRIS_7, true);
init(filterTaps);
}
@@ -101,7 +99,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
super.setSampleRate(sampleRate);
float[] filterTaps = FilterFactory.getSincChannelizer(getChannelSampleRate(), getChannelCount(),
15, Window.WindowType.BLACKMAN_HARRIS_7, true);
15, WindowType.BLACKMAN_HARRIS_7, true);
init(filterTaps);
}

View File

@@ -1,43 +1,37 @@
/*******************************************************************************
* 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 source.tuner.channel.polyphase;
package io.github.dsheirer.dsp.filter.channelizer;
import dsp.filter.channelizer.ComplexPolyphaseChannelizerM2;
import dsp.filter.design.FilterDesignException;
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 sample.Listener;
import sample.complex.IComplexBufferProvider;
import source.SourceEvent;
import source.SourceException;
import source.tuner.channel.ChannelManager;
import source.tuner.channel.TunerChannel;
import source.tuner.channel.TunerChannelSource;
import source.tuner.channel.polyphase.output.IPolyphaseChannelOutputProcessor;
import source.tuner.channel.polyphase.output.OneChannelOutputProcessor;
import source.tuner.channel.polyphase.output.TwoChannelOutputProcessor;
import source.tuner.channel.FrequencyController;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class PolyphaseChannelManager extends ChannelManager
public class PolyphaseChannelManager implements Listener<SourceEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final int CHANNEL_BANDWIDTH = 12500;
@@ -63,13 +57,12 @@ public class PolyphaseChannelManager extends ChannelManager
* 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 frequencyController that can be controlled by this channel manager
* @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(FrequencyController frequencyController, IComplexBufferProvider complexBufferProvider)
public PolyphaseChannelManager(IComplexBufferProvider complexBufferProvider, long frequency, int sampleRate)
{
super(frequencyController);
if(complexBufferProvider == null)
{
throw new IllegalArgumentException("Complex buffer provider argument cannot be null");
@@ -78,37 +71,28 @@ public class PolyphaseChannelManager extends ChannelManager
mComplexBufferProvider = complexBufferProvider;
//Ensure that sample rate is a multiple of channel sample rate since polyphase channelizer is M2
if(getFrequencyController().getSampleRate() % (CHANNEL_SAMPLE_RATE) != 0)
if(sampleRate % (CHANNEL_SAMPLE_RATE) != 0)
{
throw new IllegalArgumentException("Tuner sample rate [" + getFrequencyController().getSampleRate() +
"] must be an even multiple of " + CHANNEL_SAMPLE_RATE + " Hz");
throw new IllegalArgumentException("Tuner sample rate [" + sampleRate + "] must be an even multiple of " +
CHANNEL_SAMPLE_RATE + " Hz");
}
int channelCount = (int)(getFrequencyController().getSampleRate() / CHANNEL_BANDWIDTH);
int channelCount = sampleRate / CHANNEL_BANDWIDTH;
mChannelCalculator = new ChannelCalculator(getFrequencyController().getSampleRate(), channelCount,
getFrequencyController().getFrequency(), CHANNEL_OVERSAMPLING);
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
mBufferProcessor = new ScheduledBufferProcessor(500, 100, 50, 50);
updateChannelizer(getFrequencyController().getSampleRate(), null);
}
@Override
protected long calculateCenterFrequency()
{
return 0;
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.
*/
@Override
protected TunerChannelSource getTunerChannelSource(TunerChannel tunerChannel)
public Source getChannel(TunerChannel tunerChannel)
{
PolyphaseChannelSource channelSource = null;
@@ -123,8 +107,6 @@ public class PolyphaseChannelManager extends ChannelManager
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
CHANNEL_SAMPLE_RATE);
channelSource.setFrequency(mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes));
mChannelSources.add(channelSource);
}
}
@@ -141,7 +123,6 @@ public class PolyphaseChannelManager extends ChannelManager
/**
* 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
*/
@@ -190,8 +171,6 @@ public class PolyphaseChannelManager extends ChannelManager
{
synchronized(mBufferProcessor)
{
super.releaseSource(channelSource);
mChannelSources.remove(channelSource);
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
@@ -231,7 +210,7 @@ public class PolyphaseChannelManager extends ChannelManager
* 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(double sampleRate, SourceEvent sourceEvent)
private void updateChannelizer(int sampleRate, SourceEvent sourceEvent)
{
mChannelCalculator.setSampleRate(sampleRate);
@@ -288,7 +267,7 @@ public class PolyphaseChannelManager extends ChannelManager
*/
private void updateOutputProcessors(SourceEvent sourceEvent)
{
for(PolyphaseChannelSource channelSource : mChannelSources)
for(PolyphaseChannelSource channelSource: mChannelSources)
{
updateOutputProcessor(channelSource);
@@ -381,4 +360,4 @@ public class PolyphaseChannelManager extends ChannelManager
}
}
}
}
}

View File

@@ -1,32 +1,30 @@
/*******************************************************************************
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* 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 source.tuner.channel.polyphase;
package io.github.dsheirer.dsp.filter.channelizer;
import source.tuner.channel.polyphase.output.IPolyphaseChannelOutputProcessor;
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;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexToComplexBufferAssembler;
import source.SourceEvent;
import source.tuner.channel.TunerChannel;
import source.tuner.channel.TunerChannelSource;
public class PolyphaseChannelSource extends TunerChannelSource
{

View File

@@ -1,29 +1,26 @@
/*******************************************************************************
* 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 source.tuner.channel.polyphase;
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 sample.Listener;
import sample.OverflowableTransferQueue;
import sample.real.IOverflowListener;
import util.ThreadPool;
import java.util.ArrayList;
import java.util.List;

View File

@@ -16,16 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package source.tuner.channel.polyphase.output;
package io.github.dsheirer.dsp.filter.channelizer.output;
import dsp.mixer.Oscillator;
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 sample.OverflowableTransferQueue;
import sample.complex.Complex;
import sample.complex.ComplexSampleListener;
import sample.real.IOverflowListener;
import source.Source;
import java.util.ArrayList;
import java.util.List;

View File

@@ -16,10 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package source.tuner.channel.polyphase.output;
package io.github.dsheirer.dsp.filter.channelizer.output;
import sample.complex.ComplexSampleListener;
import source.Source;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import io.github.dsheirer.source.Source;
import java.util.List;

View File

@@ -16,11 +16,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package source.tuner.channel.polyphase.output;
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.ComplexSampleListener;
import java.util.List;

View File

@@ -16,11 +16,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package source.tuner.channel.polyphase.output;
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.ComplexSampleListener;
import java.util.List;

View File

@@ -16,9 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.filter.correction;
package io.github.dsheirer.dsp.filter.correction;
import sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.ComplexBuffer;
public class IQCorrectionFilter
{

View File

@@ -16,9 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.filter.fir.remez;
package io.github.dsheirer.dsp.filter.fir.remez;
import dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
public class PolyphaseChannelizerDesigner extends FIRFilterSpecification
{

View File

@@ -16,9 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.filter.fir.remez;
package io.github.dsheirer.dsp.filter.fir.remez;
import dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

@@ -16,10 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.filter.fir.remez;
package io.github.dsheirer.dsp.filter.fir.remez;
import dsp.filter.design.FilterDesignException;
import dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -16,10 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.filter.fir.remez;
package io.github.dsheirer.dsp.filter.fir.remez;
import dsp.filter.design.FilterDesignException;
import dsp.filter.fir.FIRFilterSpecification;
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;

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

@@ -16,13 +16,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package dsp.mixer;
package io.github.dsheirer.dsp.mixer;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.ComplexBuffer;
import java.util.Arrays;
public class LowPhaseNoiseOscillator
{

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

@@ -16,9 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package sample.complex;
package io.github.dsheirer.sample.complex;
import sample.Listener;
import io.github.dsheirer.sample.Listener;
public interface IComplexBufferProvider
{

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;
}
}

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