mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Merges from master branch.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ) );
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
{
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ) );
|
||||
// }
|
||||
//
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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
Reference in New Issue
Block a user