diff --git a/src/main/java/io/github/dsheirer/audio/squelch/ManualSquelchController.java b/src/main/java/io/github/dsheirer/audio/squelch/ManualSquelchController.java
deleted file mode 100644
index b0e93ac4..00000000
--- a/src/main/java/io/github/dsheirer/audio/squelch/ManualSquelchController.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*******************************************************************************
- * 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
- ******************************************************************************/
-
-package io.github.dsheirer.audio.squelch;
-
-
-/**
- * Provides squelch control based on presense of decoded audio samples.
- *
- * NOTE: this class is not yet fully implemented with audio sample processing
- * and currently only provides user driven squelch control
- */
-public class ManualSquelchController extends SquelchController
-{
- public ManualSquelchController()
- {
- mSquelchMode = SquelchMode.NONE;
- }
-
- @Override
- public void setSquelchMode( SquelchMode mode )
- {
- //TODO: add functionality
- }
-}
diff --git a/src/main/java/io/github/dsheirer/audio/squelch/SquelchController.java b/src/main/java/io/github/dsheirer/audio/squelch/SquelchController.java
deleted file mode 100644
index 43c88005..00000000
--- a/src/main/java/io/github/dsheirer/audio/squelch/SquelchController.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*******************************************************************************
- * 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
- ******************************************************************************/
-
-package io.github.dsheirer.audio.squelch;
-
-import io.github.dsheirer.sample.Broadcaster;
-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.RealToRealBufferAssembler;
-
-/**
- * Squelch control - receives single audio samples or real audio buffers and
- * adjusts squelch state either based on the audio samples or based on external
- * control inputs.
- *
- * Assembles received single audio samples into real sample buffers for
- * consumption by an external audio processor, like the AudioPacketProcessor
- * class.
- */
-public abstract class SquelchController
- implements Listener, RealSampleListener
-{
- private Broadcaster mSquelchStateBroadcaster = new Broadcaster<>();
- private Listener mAudioListener;
- protected SquelchState mSquelchState = SquelchState.UNSQUELCH;
- protected SquelchMode mSquelchMode = SquelchMode.MANUAL;
-
- private RealToRealBufferAssembler mSampleAssembler = new RealToRealBufferAssembler( 512 );
-
- public SquelchController()
- {
- mSampleAssembler.setListener( this );
- }
-
- @Override
- public void receive( float sample )
- {
- mSampleAssembler.receive( sample );
- }
-
- @Override
- public void receive( RealBuffer buffer )
- {
- if( mAudioListener != null )
- {
- mAudioListener.receive( buffer );
- }
- }
-
- public abstract void setSquelchMode( SquelchMode mode );
-
- public SquelchMode getSquelchMode()
- {
- return mSquelchMode;
- }
-
- public void setSquelchState( SquelchState state )
- {
- mSquelchState = state;
- }
-
- public SquelchState getSquelchState()
- {
- return mSquelchState;
- }
-
- public boolean isSquelched()
- {
- switch( mSquelchMode )
- {
- case AUTOMATIC:
- case MANUAL:
- return mSquelchState == SquelchState.SQUELCH;
- case NONE:
- return false;
- default:
- return false;
- }
- }
-
- public void addSquelchStateListener( Listener listener )
- {
- if( listener != null )
- {
- mSquelchStateBroadcaster.addListener( listener );
-
- /* Send current squelch state to the listener */
- listener.receive( mSquelchState );
- }
- }
-
- public void removeSquelchStateListener( Listener listener )
- {
- mSquelchStateBroadcaster.removeListener( listener );
- }
-
- public void setAudioListener( Listener listener )
- {
- mAudioListener = listener;
- }
-
- public void removeAudioListener( Listener listener )
- {
- mAudioListener = null;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/audio/squelch/TrunkingSquelchController.java b/src/main/java/io/github/dsheirer/audio/squelch/TrunkingSquelchController.java
deleted file mode 100644
index fbb3e843..00000000
--- a/src/main/java/io/github/dsheirer/audio/squelch/TrunkingSquelchController.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*******************************************************************************
- * 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
- ******************************************************************************/
-
-package io.github.dsheirer.audio.squelch;
-
-public class TrunkingSquelchController extends SquelchController
-{
- private boolean mAllowSquelchOverride = true;
-
- public TrunkingSquelchController()
- {
- }
-
- /**
- * Provides control over audio stream as directed by an external trunking
- * channel state.
- *
- * Override automatic squelch control by setting the squelch mode to none.
- *
- * Disable automatic control override by setting allowManualOverride to false;
- *
- * Note: this control does not respond to squelch mode manual.
- */
- public TrunkingSquelchController( boolean allowSquelchOverride )
- {
- mAllowSquelchOverride = allowSquelchOverride;
-
- mSquelchMode = SquelchMode.AUTOMATIC;
- }
-
- @Override
- public void setSquelchMode( SquelchMode mode )
- {
- if( mode == SquelchMode.AUTOMATIC ||
- ( mode == SquelchMode.NONE && mAllowSquelchOverride ) )
- {
- mSquelchMode = mode;
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataViewer.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataViewer.java
index f121508e..d0f3fd23 100644
--- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataViewer.java
+++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataViewer.java
@@ -26,11 +26,10 @@ import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.module.decode.event.CallEventPanel;
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
import io.github.dsheirer.settings.SettingsManager;
-import io.github.dsheirer.spectrum.ChannelSpectrumPanel;
import net.miginfocom.swing.MigLayout;
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.JPanel;
+import java.awt.Color;
public class ChannelMetadataViewer extends JPanel
{
@@ -39,7 +38,6 @@ public class ChannelMetadataViewer extends JPanel
private ChannelDetailPanel mChannelDetailPanel;
private CallEventPanel mCallEventPanel;
private MessageActivityPanel mMessageActivityPanel;
- private ChannelSpectrumPanel mChannelSpectrumPanel;
/**
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
@@ -51,7 +49,6 @@ public class ChannelMetadataViewer extends JPanel
mChannelDetailPanel = new ChannelDetailPanel(channelProcessingManager);
mCallEventPanel = new CallEventPanel(iconManager);
mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager);
-// mChannelSpectrumPanel = new ChannelSpectrumPanel(settingsManager, channelProcessingManager);
mChannelMetadataPanel = new ChannelMetadataPanel(channelProcessingManager, iconManager);
init();
diff --git a/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java
index 045d748f..029d4c3e 100644
--- a/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java
+++ b/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java
@@ -1,22 +1,83 @@
+/*******************************************************************************
+ * sdr-trunk
+ * Copyright (C) 2014-2018 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see
+ *
+ ******************************************************************************/
package io.github.dsheirer.dsp.am;
-public abstract class AMDemodulator
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
+import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
+
+/**
+ * Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
+ */
+public class AMDemodulator
{
- private float mGain;
-
- public AMDemodulator( float gain )
- {
- mGain = gain;
- }
-
- public float demodulate( float inphase, float quadrature )
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("AMDemodulator");
+ private int mOutputBufferPointer;
+ private float mGain;
+
+ /**
+ * Constructs this demodulator where the specified gain is applied to demodulated output samples.
+ *
+ * @param gain to apply to demodulated output samples.
+ */
+ public AMDemodulator(float gain)
{
- return (float)Math.sqrt( ( inphase * inphase ) +
- ( quadrature * quadrature ) ) * mGain;
+ mGain = gain;
+ }
+
+ /**
+ * Sets the gain to the specified level.
+ */
+ public void setGain(float gain)
+ {
+ mGain = gain;
+ }
+
+ /**
+ * Demodulates the comples I/Q sample and returns the demodulated output sample.
+ *
+ * @param inphase sample
+ * @param quadrature sample
+ * @return AM demodulated sample
+ */
+ public float demodulate(float inphase, float quadrature)
+ {
+ return (float)Math.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
+ }
+
+ /**
+ * Demodulates the reusable comples sample buffer and returns a demodulated audio buffer.
+ * @param complexBuffer to demodulate
+ * @return demodulated audio buffer.
+ */
+ public ReusableBuffer demodulate(ReusableComplexBuffer complexBuffer)
+ {
+ ReusableBuffer reusableBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
+ float[] input = complexBuffer.getSamples();
+ float[] output = reusableBuffer.getSamples();
+ mOutputBufferPointer = 0;
+
+ for(int x= 0; x < complexBuffer.getSamples().length; x += 2)
+ {
+ output[mOutputBufferPointer++] = demodulate(input[x], input[x + 1]);
+ }
+
+ complexBuffer.decrementUserCount();
+ reusableBuffer.incrementUserCount();
+
+ return reusableBuffer;
}
-
- public void setGain( float gain )
- {
- mGain = gain;
- }
}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java
index 3f044177..5e5e9a47 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java
@@ -58,6 +58,16 @@ public class DCRemovalFilter
return filtered;
}
+ public float[] filter( float[] samples )
+ {
+ for( int x = 0; x < samples.length; x++ )
+ {
+ samples[ x ] = filter( samples[ x ] );
+ }
+
+ return samples;
+ }
+
/**
* Filters the buffer samples and returns a new reusable buffer with the filtered samples.
*
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter_RB.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter_RB.java
deleted file mode 100644
index 74892dae..00000000
--- a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter_RB.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package io.github.dsheirer.dsp.filter.dc;
-
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.RealBuffer;
-
-public class DCRemovalFilter_RB extends DCRemovalFilter
- implements Listener
-{
- protected Listener mListener;
-
- public DCRemovalFilter_RB( float ratio )
- {
- super( ratio );
- }
-
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-
- public void removeListener()
- {
- mListener = null;
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- public float[] filter( float[] samples )
- {
- for( int x = 0; x < samples.length; x++ )
- {
- samples[ x ] = filter( samples[ x ] );
- }
-
- return samples;
- }
-
- public RealBuffer filter( RealBuffer buffer )
- {
- float[] samples = buffer.getSamples();
-
- for( int x = 0; x < samples.length; x++ )
- {
- samples[ x ] = filter( samples[ x ] );
- }
-
- return buffer;
- }
-
- @Override
- public void receive( RealBuffer buffer )
- {
- if( mListener != null )
- {
- mListener.receive( filter( buffer ) );
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter_RealBuffer.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter_RealBuffer.java
deleted file mode 100644
index f401aaf4..00000000
--- a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter_RealBuffer.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package io.github.dsheirer.dsp.filter.dc;
-
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.RealBuffer;
-
-public class DCRemovalFilter_RealBuffer implements Listener
-{
- private Listener mListener;
- /**
- * Removes DC bias present across the samples in the buffer by calculating
- * the mean DC component and subtracting that component from each of the
- * samples.
- */
- public DCRemovalFilter_RealBuffer()
- {
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- public RealBuffer filter( RealBuffer buffer )
- {
- double accumulator = 0.0d;
-
- for( float sample: buffer.getSamples() )
- {
- accumulator += sample;
- }
-
- float mean = (float)( accumulator / (double)buffer.getSamples().length );
-
- for( float sample: buffer.getSamples() )
- {
- sample -= mean;
- }
-
- return buffer;
- }
-
- @Override
- public void receive( RealBuffer buffer )
- {
- if( mListener != null )
- {
- mListener.receive( filter( buffer ) );
- }
- }
-
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-
- public void removeListener()
- {
- mListener = null;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/polyphase/FilterStage.java b/src/main/java/io/github/dsheirer/dsp/filter/polyphase/FilterStage.java
deleted file mode 100644
index ffb1a411..00000000
--- a/src/main/java/io/github/dsheirer/dsp/filter/polyphase/FilterStage.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2016 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
-package io.github.dsheirer.dsp.filter.polyphase;
-
-public class FilterStage
-{
- private float[] mBuffer;
- private int mBufferSize;
- private int mBufferPointer;
- private int[][] mIndexMap;
- private float[] mCoefficients;
-
- /**
- * Single stage of a polyphase filter implemented with a circular sample buffer that uses an index map to ensure
- * correct alignment of samples to filter taps.
- *
- * @param coefficients
- */
- public FilterStage(float[] coefficients)
- {
- mCoefficients = coefficients;
-
- mBufferSize = mCoefficients.length;
-
- mBuffer = new float[mBufferSize];
-
- //Note: buffer pointer starts at an illegal index but is decremented before first usage
- mBufferPointer = mBufferSize;
-
- generateIndexMap(mBufferSize);
- }
-
- /**
- * Coefficients used by this filter stage.
- */
- public float[] getCoefficients()
- {
- return mCoefficients;
- }
-
- /**
- * Cleanup prior to disposal
- */
- public void dispose()
- {
- mCoefficients = null;
- mIndexMap = null;
- mBuffer = null;
- }
-
- /**
- * Loads the sample into the sample buffer
- */
- private void load(float sample)
- {
- mBufferPointer--;
-
- if (mBufferPointer < 0)
- {
- mBufferPointer += mBufferSize;
- }
-
- mBuffer[mBufferPointer] = sample;
- }
-
- /**
- * Applies convolution against the currently loaded buffer samples.
- */
- public float filter(float sample)
- {
- load(sample);
-
- float accumulator = 0.0f;
-
- for (int x = 0; x < mBufferSize; x++)
- {
- accumulator += mCoefficients[x] * mBuffer[mIndexMap[mBufferPointer][x]];
- }
-
- return accumulator;
- }
-
- /**
- * Generates a circular buffer index map to support lookup of the translated index based on the current buffer
- * pointer and the desired sample index.
- */
- private void generateIndexMap(int size)
- {
- mIndexMap = new int[size][size];
-
- for (int x = 0; x < size; x++)
- {
- for (int y = 0; y < size; y++)
- {
- int z = x + y;
-
- mIndexMap[x][y] = z < size ? z : z - size;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java b/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java
deleted file mode 100644
index 34a12cb7..00000000
--- a/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2016 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
-package io.github.dsheirer.dsp.filter.polyphase;
-
-import io.github.dsheirer.dsp.filter.design.FilterDesignException;
-import io.github.dsheirer.dsp.filter.fir.FIRFilter;
-import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
-import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
-import io.github.dsheirer.dsp.mixer.IOscillator;
-import io.github.dsheirer.dsp.mixer.Oscillator;
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.RealBuffer;
-import io.github.dsheirer.sample.real.RealToRealBufferAssembler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class PolyphaseFIRDecimatingFilter_RB extends FIRFilter implements Listener
-{
- private final static Logger mLog = LoggerFactory.getLogger( PolyphaseFIRDecimatingFilter_RB.class );
-
- private List mFilterStages = new ArrayList<>();
- private int mFilterStagePointer;
- private Listener mListener;
- private RealToRealBufferAssembler mAssembler;
- private int mDecimationRatio;
- private float mAccumulator = 0.0f;
- private float mGain = 1.0f;
-
- /**
- * Polyphase filter stage for decimating real-valued sample buffers by the decimation ratio. Creates a number of
- * polyphase filter arms/stages equal to the decimation ratio.
- *
- * Note: output real-valued buffer sizes are determined by the first input buffer that is presented to the filter
- * where the output buffer size is equal to the floor of the input buffer size divided by the decimation ratio.
- *
- * @param coefficients of a low pass filter designed for the input sample rate with a cutoff frequency that conforms
- * to the nyquist frequency for an output sample rate equal to the input sample rate divided by the decimation ratio.
- *
- * @param decimationRatio to decimate the input sample rate by.
- * @param gain to apply to the filtered output. Use a value of 1.0 to apply no gain.
- */
- public PolyphaseFIRDecimatingFilter_RB(float[] coefficients, int decimationRatio, float gain)
- {
- mDecimationRatio = decimationRatio;
- mGain = gain;
-
- createFilterStages(coefficients, mDecimationRatio);
- mFilterStagePointer = mFilterStages.size() - 1;
- }
-
- /**
- * Creates polyphase filter stages (arms) from the set of coefficients.
- *
- * @param coefficients for the low-pass filter
- * @param decimationRatio to determine the number of polyphase filter stages.
- */
- private void createFilterStages(float[] coefficients, int decimationRatio)
- {
- int tapSize = (int)Math.ceil((double)coefficients.length / (double)decimationRatio);
-
- float[][] coefficientSets = new float[decimationRatio][tapSize];
-
- int stagePointer = 0;
- int coefficientPointer = 0;
-
- //Split the coefficients up into polyphase stage coefficient sets
- for(int x = 0; x < coefficients.length; x++)
- {
- coefficientSets[stagePointer++][coefficientPointer] = coefficients[x];
-
- if(stagePointer >= decimationRatio)
- {
- stagePointer = 0;
- coefficientPointer++;
- }
- }
-
- //Create the stages
- for(int x = 0; x < decimationRatio; x++)
- {
- mFilterStages.add(new FilterStage(coefficientSets[x]));
- }
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- @Override
- public void receive(RealBuffer buffer)
- {
- if(mAssembler == null)
- {
- mAssembler = new RealToRealBufferAssembler(buffer.getSamples().length / mDecimationRatio);
- mAssembler.setListener(mListener);
- }
-
- for(float sample: buffer.getSamples())
- {
- filter(sample);
- }
-
- int a = 0;
- }
-
- private void filter(float sample)
- {
- FilterStage stage = mFilterStages.get(mFilterStagePointer--);
-
- mAccumulator += stage.filter(sample);
-
- if(mFilterStagePointer < 0)
- {
- mAssembler.receive(mAccumulator * mGain);
- mAccumulator = 0.0f;
-
- mFilterStagePointer = mFilterStages.size() - 1;
- }
- }
-
- public void setListener(Listener listener)
- {
- mListener = listener;
-
- if(mAssembler != null)
- {
- mAssembler.setListener(mListener);
- }
- }
-
- public void removeListener()
- {
- mListener = null;
-
- if(mAssembler != null)
- {
- mAssembler.setListener(null);
- }
- }
-
- public static void main(String[] args)
- {
- FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
- .sampleRate( 48000 )
- .gridDensity( 16 )
- .passBandCutoff( 3000 )
- .passBandAmplitude( 1.0 )
- .passBandRipple( 0.01 )
- .stopBandStart( 4000 )
- .stopBandAmplitude( 0.0 )
- .stopBandRipple( 0.027 )
- .build();
-
- RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
-
- try
- {
- if(!designer.isValid())
- {
- throw new FilterDesignException("Couldn't design the filter");
- }
-
- float[] coefficients = designer.getImpulseResponse();
- PolyphaseFIRDecimatingFilter_RB filter = new PolyphaseFIRDecimatingFilter_RB(coefficients, 6, 1.0f);
- filter.setListener(new Listener()
- {
- @Override
- public void receive(RealBuffer realBuffer)
- {
- mLog.info("Filtered: " + Arrays.toString(realBuffer.getSamples()));
- }
- });
-
- IOscillator oscillator = new Oscillator(3400, 48000);
-
- float[] samples = oscillator.generateReal(500);
- RealBuffer buffer = new RealBuffer(samples);
- filter.receive(buffer);
- }
- catch(FilterDesignException e)
- {
- mLog.debug("Error designing filter", e);
- }
-
- }
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRInterpolatingFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRInterpolatingFilter.java
deleted file mode 100644
index 73d84f06..00000000
--- a/src/main/java/io/github/dsheirer/dsp/filter/polyphase/PolyphaseFIRInterpolatingFilter.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2016 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- *
- ******************************************************************************/
-package io.github.dsheirer.dsp.filter.polyphase;
-
-import org.apache.commons.lang3.Validate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Polyphase interpolating FIR filter with an internal circular buffer as
- * described by the dspGuru at:
- *
- * http://www.dspguru.com/dsp/faqs/multirate/interpolation
- * See sections 3.4.2 and 3.4.3 )
- */
-public class PolyphaseFIRInterpolatingFilter
-{
- private final static Logger mLog =
- LoggerFactory.getLogger( PolyphaseFIRInterpolatingFilter.class );
-
- private double[] mBuffer;
- private int mBufferPointer = 0;
- private double[] mTaps;
- private Phase[] mPhases;
-
- /**
- * Constructs the interpolating filter. Coefficients length must be an
- * integer multiple of the interpolation value. Each sample processed by
- * the filter will generate a quantity of samples equal to the interpolation
- * value.
- *
- * @param coefficients - low pass filter coefficients sized for the desired
- * interpolated sample rate with a cutoff frequency of no more than half of
- * the original sample rate.
- *
- * @param interpolation - integral interpolation quantity
- */
- public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation )
- {
- /* Ensure coefficients are an integral multiple of the interpolation factor */
- Validate.isTrue(coefficients.length % interpolation == 0);
-
- mTaps = coefficients;
-
- mPhases = new Phase[ interpolation ];
-
- int phaseSize = coefficients.length / interpolation;
-
- mBuffer = new double[ phaseSize ];
-
- for( int x = 0; x < interpolation; x++ )
- {
- int coefficientIndex = 0;
-
- int[] indexes = new int[ phaseSize ];
-
- for( int y = x; y < coefficients.length; y += interpolation )
- {
- indexes[ coefficientIndex++ ] = y;
- }
-
- /* Create a phase using the tap indexes and the interpolation value
- * for the gain value, to offset the loss induced in upsampling from
- * one sample to (interpolation) samples. */
- mPhases[ interpolation - x - 1 ] = new Phase( indexes, interpolation );
- }
- }
-
- /**
- * Inserts the sample into the filter and returns (interpolation) number
- * of interpolated samples in the return array.
- *
- * @param sample - float sample
- * @return - array of interpolated samples, size = interpolation quantity
- */
- public float[] interpolate( float sample )
- {
- mBuffer[ mBufferPointer ] = (double)sample;
-
- mBufferPointer++;
- mBufferPointer %= mBuffer.length;
-
- float[] samples = new float[ mPhases.length ];
-
- for( int x = 0; x < mPhases.length; x++ )
- {
- samples[ x ] = mPhases[ x ].filter();
- }
-
- return samples;
- }
-
- /**
- * Upsamples and interpolates the array of samples, returning an array that
- * is upsampled and low-pass filtered by the interpolation quantity.
- *
- * @param samples - array of float samples
- * @return - array of interpolated samples, size = interpolation * samples size
- */
- public float[] interpolate( float[] samples )
- {
- int pointer = 0;
- float[] interpolated = new float[ samples.length * mPhases.length ];
-
- for( float sample: samples )
- {
- mBuffer[ mBufferPointer ] = (double)sample;
-
- mBufferPointer++;
- mBufferPointer %= mBuffer.length;
-
- for( int x = 0; x < mPhases.length; x++ )
- {
- interpolated[ pointer ] = mPhases[ x ].filter();
-
- pointer++;
- }
- }
-
- return interpolated;
- }
-
- /**
- * Single phase element of a polyphase interpolating FIR filter. Each phase
- * element shares a common buffer. The tap indexes argument defines the
- * filter taps used by this phase element to generate an interpolated value
- * from the shared circular buffer of samples.
- */
- public class Phase
- {
- private int[] mIndexes;
- private double mGain;
-
- public Phase( int[] tapIndexes, int gain )
- {
- mIndexes = tapIndexes;
- mGain = gain;
- }
-
- public float filter()
- {
- double accumulator = 0;
-
- for( int x = 0; x < mIndexes.length; x++ )
- {
- accumulator += mTaps[ mIndexes[ x ] ] *
- mBuffer[ ( x + mBufferPointer ) % mIndexes.length ];
- }
-
- return (float)( accumulator * mGain );
- }
- }
-
-// public static void main( String[] args )
-// {
-// int interpolation = 6;
-//
-// PolyphaseFIRInterpolatingFilter p =
-// new PolyphaseFIRInterpolatingFilter( IMBESynthesizer.INTERPOLATION_TAPS, interpolation );
-//
-// Oscillator o = new Oscillator( 500, 8000 );
-//
-// int iterations = 40;
-//
-// float[] in = new float[ iterations ];
-// float[] out = new float[ iterations * interpolation ];
-//
-// int inIndex = 0;
-// int outIndex = 0;
-//
-// for( int x = 0; x < iterations; x++ )
-// {
-// float next = o.nextFloat();
-//
-// in[ inIndex++ ] = next;
-//
-// float[] samples = p.interpolate( next );
-//
-// System.arraycopy( samples, 0, out, outIndex, samples.length );
-//
-// outIndex += interpolation;
-// }
-//
-// mLog.debug( "In: " + Arrays.toString( in ) );
-// mLog.debug( "Out: " + Arrays.toString( out ) );
-// }
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java
index 4d2ba36d..1ce76e71 100644
--- a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java
+++ b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java
@@ -2,32 +2,32 @@ package io.github.dsheirer.dsp.gain;
import io.github.dsheirer.buffer.DoubleCircularBuffer;
import io.github.dsheirer.buffer.RealCircularBuffer;
-
-import java.util.concurrent.atomic.AtomicBoolean;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
+ * Copyright (C) 2014 - 2018 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
- *
+ *
* ---------------------------------------------------
* 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.
*
@@ -60,165 +60,189 @@ import java.util.concurrent.atomic.AtomicBoolean;
******************************************************************************/
public class AutomaticGainControl
{
- private static final double SAMPLE_RATE = 48000;
-
- /* Signal delay line - time delay in seconds */
+ 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;
+ 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;
+ /* 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;
+ /* 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_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 );
+ 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;
+ /* AGC decay value in milliseconds (20 to 5000) */
+ private static final double DECAY = 200;
- private static final double DECAY_RISE_ALPHA = 1.0 -
- Math.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO ) );
+ /* 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_FALL_ALPHA = 1.0 -
- Math.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 ) );
+ private static final double DECAY_RISE_ALPHA = 1.0 -
+ Math.exp(-1.0 / (SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO));
- /* Hang timer release decay time constant in seconds */
- @SuppressWarnings( "unused" )
- private static final double RELEASE_TIME_CONSTANT = 0.05;
+ private static final double DECAY_FALL_ALPHA = 1.0 -
+ Math.exp(-1.0 / (SAMPLE_RATE * DECAY * .001));
- /* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
- private static final double THRESHOLD = -100;
+ /* Hang timer release decay time constant in seconds */
+ @SuppressWarnings("unused")
+ private static final double RELEASE_TIME_CONSTANT = 0.05;
- /* Limit output to about 3db of maximum */
- private static final double AGC_OUT_SCALE = 0.7;
+ /* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
+ private static final double THRESHOLD = -100;
- /* 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;
+ /* Limit output to about 3db of maximum */
+ private static final double AGC_OUT_SCALE = 0.7;
- private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE *
- Math.pow( 10.0, MANUAL_GAIN / 20.0 );
+ /* 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 dB reduction in output at knee from max output level (0 - 10dB) */
- private static final double SLOPE_FACTOR = 2.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 KNEE = THRESHOLD / 20.0;
+ private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE *
+ Math.pow(10.0, MANUAL_GAIN / 20.0);
- private static final double GAIN_SLOPE = SLOPE_FACTOR / 100.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 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 RealCircularBuffer mDelayBuffer =
- new RealCircularBuffer( (int)( SAMPLE_RATE * DELAY_TIME_CONSTANT ) );
- private DoubleCircularBuffer mMagnitudeBuffer =
- new DoubleCircularBuffer( (int)( SAMPLE_RATE * WINDOW_TIME_CONSTANT ) );
-
- public AutomaticGainControl()
- {
- }
+ private static final double KNEE = THRESHOLD / 20.0;
- public float process( float currentSample )
+ 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 boolean mAGCEnabled = true;
+
+ private double mPeakMagnitude = 0.0;
+ private double mAttackAverage = 0.0;
+ private double mDecayAverage = 0.0;
+
+ private RealCircularBuffer mDelayBuffer = new RealCircularBuffer((int)(SAMPLE_RATE * DELAY_TIME_CONSTANT));
+ private DoubleCircularBuffer mMagnitudeBuffer = new DoubleCircularBuffer((int)(SAMPLE_RATE * WINDOW_TIME_CONSTANT));
+
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("AutomaticGainControl");
+
+ public AutomaticGainControl()
{
- 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();
- }
+ /**
+ * Applies gain to the samples contained in the input buffer and returns a new buffer containing the
+ * processed samples.
+ *
+ * @param input samples to process
+ * @return output samples with gain applied
+ */
+ public ReusableBuffer process(ReusableBuffer input)
+ {
+ ReusableBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount());
+
+ float[] inputSamples = input.getSamples();
+ float[] outputSamples = output.getSamples();
+
+ for(int x = 0; x < inputSamples.length; x++)
+ {
+ outputSamples[x] = process(inputSamples[x]);
+ }
+
+ input.decrementUserCount();
+ output.incrementUserCount();
+
+ return output;
+ }
+
+ /**
+ * Processes the input sample and applies gain
+ *
+ * @param currentSample to process
+ * @return gain applied sample
+ */
+ public float process(float currentSample)
+ {
+ float delayedSample = mDelayBuffer.putAndGet(currentSample);
+
+ double gain = MANUAL_AGC_GAIN;
+
+ if(mAGCEnabled)
+ {
+ 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 = enabled;
+ }
+
+ /**
+ * Indicates if AGC is enabled
+ *
+ * @return true=AGC, false=MANUAL GAIN
+ */
+ public boolean isAGCEnabled()
+ {
+ return mAGCEnabled;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl_RB.java b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl_RB.java
deleted file mode 100644
index 46152564..00000000
--- a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl_RB.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package io.github.dsheirer.dsp.gain;
-
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.RealBuffer;
-
-public class AutomaticGainControl_RB extends AutomaticGainControl
- implements Listener
-{
- private Listener mListener;
-
- public AutomaticGainControl_RB()
- {
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- /**
- * Applies AGC to a buffer of real (float) samples
- */
- public RealBuffer process( RealBuffer buffer )
- {
- float[] samples = buffer.getSamples();
-
- for( int x = 0; x < samples.length; x++ )
- {
- samples[ x ] = process( samples[ x ] );
- }
-
- return buffer;
- }
-
- @Override
- public void receive( RealBuffer buffer )
- {
- if( mListener != null )
- {
- mListener.receive( process( buffer ) );
- }
- }
-
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-
- public void removeListener()
- {
- mListener = null;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java
index 99fa02a3..11de7e21 100644
--- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java
+++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java
@@ -45,7 +45,6 @@ import io.github.dsheirer.module.decode.event.ICallEventProvider;
import io.github.dsheirer.module.decode.event.MessageActivityModel;
import io.github.dsheirer.module.log.EventLogger;
import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder;
-import io.github.dsheirer.record.wave.RealBufferWaveRecorder;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
@@ -709,7 +708,7 @@ public class ProcessingChain implements IChannelEventListener
for(Module module : mModules)
{
- if(module instanceof RealBufferWaveRecorder || module instanceof ComplexBufferWaveRecorder)
+ if(module instanceof ComplexBufferWaveRecorder)
{
recordingModules.add(module);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
index c5d6a81b..1aa7ce99 100644
--- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
@@ -87,7 +87,6 @@ import io.github.dsheirer.module.decode.passport.PassportMessageFilter;
import io.github.dsheirer.module.decode.tait.Tait1200Decoder;
import io.github.dsheirer.module.decode.tait.Tait1200DecoderState;
import io.github.dsheirer.module.demodulate.am.AMDemodulatorModule;
-import io.github.dsheirer.module.demodulate.audio.DemodulatedAudioFilterModule;
import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule;
import io.github.dsheirer.source.SourceType;
import org.slf4j.Logger;
@@ -100,7 +99,8 @@ public class DecoderFactory
{
private final static Logger mLog = LoggerFactory.getLogger(DecoderFactory.class);
- private static final double CHANNEL_BANDWIDTH = 12500.0;
+ private static final double AM_CHANNEL_BANDWIDTH = 3000.0;
+ private static final double FM_CHANNEL_BANDWIDTH = 12500.0;
private static final double DEMODULATED_AUDIO_SAMPLE_RATE = 8000.0;
/**
@@ -148,16 +148,13 @@ public class DecoderFactory
switch(decodeConfig.getDecoderType())
{
case AM:
- if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
- {
- modules.add(new AMDemodulatorModule());
- //TODO: update AM demodulator to produce demodulated 8 kHz audio and delete the following filter module
- modules.add(new DemodulatedAudioFilterModule(4000, 6000));
- }
-
modules.add(new AMDecoder(decodeConfig));
modules.add(new AlwaysUnsquelchedDecoderState(DecoderType.AM, channel.getName()));
modules.add(new AudioModule(metadata));
+ if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
+ {
+ modules.add(new AMDemodulatorModule(AM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ }
break;
case NBFM:
modules.add(new NBFMDecoder(decodeConfig));
@@ -165,7 +162,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
- modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case LTR_STANDARD:
@@ -175,7 +172,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
- modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case LTR_NET:
@@ -184,7 +181,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
- modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case MPT1327:
@@ -196,7 +193,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
- modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
if(channelType == ChannelType.STANDARD)
@@ -212,7 +209,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
- modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case P25_PHASE1:
diff --git a/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java b/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java
index f155c890..e216fd11 100644
--- a/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java
+++ b/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java
@@ -15,68 +15,144 @@
******************************************************************************/
package io.github.dsheirer.module.demodulate.am;
-import io.github.dsheirer.dsp.am.AMDemodulator_CB;
+import io.github.dsheirer.dsp.am.AMDemodulator;
import io.github.dsheirer.dsp.filter.FilterFactory;
+import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.Window.WindowType;
-import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
-import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter_RB_RB;
-import io.github.dsheirer.dsp.gain.AutomaticGainControl_RB;
+import io.github.dsheirer.dsp.filter.design.FilterDesignException;
+import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
+import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
+import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
+import io.github.dsheirer.dsp.filter.resample.RealResampler;
+import io.github.dsheirer.dsp.gain.AutomaticGainControl;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
-import io.github.dsheirer.sample.complex.ComplexBuffer;
-import io.github.dsheirer.sample.real.IFilteredRealBufferProvider;
-import io.github.dsheirer.sample.real.RealBuffer;
+import io.github.dsheirer.source.ISourceEventListener;
+import io.github.dsheirer.source.SourceEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-public class AMDemodulatorModule extends Module implements IReusableComplexBufferListener, IFilteredRealBufferProvider
+/**
+ * AM Demodulator Module for demodulating complex baseband channel sample buffers and producing demodulated audio
+ * at a specified output sample rate, normally 8 kHz.
+ *
+ * This module requires a sample rate SourceEvent prior to processing baseband sample buffers in order to configure
+ * internal filters and the resampler.
+ */
+public class AMDemodulatorModule extends Module implements ISourceEventListener, IReusableComplexBufferListener,
+ IReusableBufferProvider, Listener
{
- 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;
- private ReusableBufferListener mReusableBufferListener = new ReusableBufferListener();
+ private final static Logger mLog = LoggerFactory.getLogger(AMDemodulatorModule.class);
+ private static float[] mLowPassFilterTaps;
+ private ComplexFIRFilter2 mIQFilter;
+ private AMDemodulator mDemodulator;
+ private RealFIRFilter2 mLowPassFilter;
+ private AutomaticGainControl mAGC = new AutomaticGainControl();
+ private double mChannelBandwidth;
+ private double mOutputSampleRate;
+ private RealResampler mResampler;
+ private Listener mResampledReusableBufferListener;
+ private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
/**
- * AM Demodulator Module
+ * Constructs an AM demodulator module
+ *
+ * @param channelBandwidth to use in filtering the baseband input buffers
+ * @param outputSampleRate specifies the resampled output sample rate for demodulated audio
*/
- public AMDemodulatorModule()
+ public AMDemodulatorModule(double channelBandwidth, double outputSampleRate)
{
- mIQFilter = new ComplexFIRFilter_CB_CB(FilterFactory.getLowPass(
- SAMPLE_RATE, 5000, 73, WindowType.HAMMING), 1.0f);
+ mChannelBandwidth = channelBandwidth;
+ mOutputSampleRate = outputSampleRate;
- mDemodulator = new AMDemodulator_CB(500.0f);
- mIQFilter.setListener(mDemodulator);
+ mDemodulator = new AMDemodulator(500.0f);
- mLowPassFilter = new RealFIRFilter_RB_RB(
- FilterFactory.getLowPass(48000, 3000, 31, WindowType.COSINE), 1.0f);
- mDemodulator.setListener(mLowPassFilter);
+ if(mLowPassFilterTaps == null)
+ {
+ FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
+ .sampleRate(mOutputSampleRate)
+ .gridDensity(16)
+ .oddLength(true)
+ .passBandCutoff(3000)
+ .passBandAmplitude(1.0)
+ .passBandRipple(0.01)
+ .stopBandStart(3500)
+ .stopBandAmplitude(0.0)
+ .stopBandRipple(0.028)
+ .build();
- mAGC = new AutomaticGainControl_RB();
- mLowPassFilter.setListener(mAGC);
+ try
+ {
+ mLowPassFilterTaps = FilterFactory.getTaps(specification);
+ }
+ catch(FilterDesignException fde)
+ {
+ mLog.error("Couldn't design AM demodulator remez filter for sample rate [" + mOutputSampleRate +
+ "] pass frequency [3000] and stop frequency [3500] - using sinc filter");
+ }
+
+ if(mLowPassFilterTaps == null)
+ {
+ mLowPassFilterTaps = FilterFactory.getLowPass(mOutputSampleRate, 3000, 3500,
+ 60, WindowType.HAMMING, true);
+ }
+ }
+
+ mLowPassFilter = new RealFIRFilter2(mLowPassFilterTaps);
+ }
+
+ @Override
+ public void receive(ReusableComplexBuffer basebandBuffer)
+ {
+ ReusableComplexBuffer filteredBuffer = mIQFilter.filter(basebandBuffer);
+ ReusableBuffer demodulated = mDemodulator.demodulate(filteredBuffer);
+ mResampler.resample(demodulated);
+ }
+
+ /**
+ * Processes resampled audio buffers produced by the resampler.
+ * @param resampledAudio buffer to process.
+ */
+ private void processResampledAudio(ReusableBuffer resampledAudio)
+ {
+ if(mResampledReusableBufferListener != null)
+ {
+ ReusableBuffer filteredAudio = mLowPassFilter.filter(resampledAudio);
+ ReusableBuffer gainApplied = mAGC.process(filteredAudio);
+ mResampledReusableBufferListener.receive(gainApplied);
+ }
+ else
+ {
+ resampledAudio.decrementUserCount();
+ }
}
@Override
public Listener getReusableComplexBufferListener()
{
- return mReusableBufferListener;
+ return this;
}
@Override
public void dispose()
{
- mIQFilter.dispose();
- mIQFilter = null;
+ if(mIQFilter != null)
+ {
+ mIQFilter.dispose();
+ mIQFilter = null;
+ }
+
+ if(mLowPassFilter != null)
+ {
+ mLowPassFilter.dispose();
+ mLowPassFilter = null;
+ }
- mDemodulator.dispose();
mDemodulator = null;
-
- mLowPassFilter.dispose();
- mLowPassFilter = null;
-
- mAGC.dispose();
mAGC = null;
}
@@ -85,18 +161,6 @@ public class AMDemodulatorModule extends Module implements IReusableComplexBuffe
{
}
- @Override
- public void setFilteredRealBufferListener(Listener listener)
- {
- mAGC.setListener(listener);
- }
-
- @Override
- public void removeFilteredRealBufferListener()
- {
- mAGC.removeListener();
- }
-
@Override
public void start()
{
@@ -107,18 +171,97 @@ public class AMDemodulatorModule extends Module implements IReusableComplexBuffe
{
}
+ @Override
+ public void setBufferListener(Listener listener)
+ {
+ mResampledReusableBufferListener = listener;
+ }
- public class ReusableBufferListener implements Listener
+ @Override
+ public void removeBufferListener()
+ {
+ mResampledReusableBufferListener = null;
+ }
+
+ @Override
+ public Listener getSourceEventListener()
+ {
+ return mSourceEventProcessor;
+ }
+
+ /**
+ * Monitors sample rate change source event(s) to setup the initial I/Q filter and resampler
+ */
+ public class SourceEventProcessor implements Listener
{
@Override
- public void receive(ReusableComplexBuffer reusableComplexBuffer)
+ public void receive(SourceEvent sourceEvent)
{
- float[] samples = reusableComplexBuffer.getSamplesCopy();
+ if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
+ {
+ if(mIQFilter != null)
+ {
+ mIQFilter.dispose();
+ mIQFilter = null;
+ }
- //TODO: redesign the filter chain so that we can simply pass a float array ...
+ double sampleRate = sourceEvent.getValue().doubleValue();
- mIQFilter.receive(new ComplexBuffer(samples));
- reusableComplexBuffer.decrementUserCount();
+ if((sampleRate < (2.0 * mChannelBandwidth)))
+ {
+ throw new IllegalStateException("AM Demodulator with channel bandwidth [" + mChannelBandwidth +
+ "] requires a channel sample rate of [" + (2.0 * mChannelBandwidth + "] - sample rate of [" +
+ sampleRate + "] is not supported"));
+ }
+
+ double cutoff = sampleRate / 4.0;
+ int passBandStop = (int)cutoff - 500;
+ int stopBandStart = (int)cutoff + 500;
+
+ float[] filterTaps = null;
+
+ FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
+ .sampleRate(sampleRate)
+ .gridDensity(16)
+ .oddLength(true)
+ .passBandCutoff(passBandStop)
+ .passBandAmplitude(1.0)
+ .passBandRipple(0.01)
+ .stopBandStart(stopBandStart)
+ .stopBandAmplitude(0.0)
+ .stopBandRipple(0.028) //Approximately 60 dB attenuation
+ .build();
+
+ try
+ {
+ filterTaps = FilterFactory.getTaps(specification);
+ }
+ catch(FilterDesignException fde)
+ {
+ mLog.error("Couldn't design FM demodulator remez filter for sample rate [" + sampleRate +
+ "] pass frequency [" + passBandStop + "] and stop frequency [" + stopBandStart +
+ "] - using sinc filter");
+ }
+
+ if(filterTaps == null)
+ {
+ filterTaps = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60,
+ Window.WindowType.HAMMING, true);
+ }
+
+ mIQFilter = new ComplexFIRFilter2(filterTaps);
+
+ mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000);
+
+ mResampler.setListener(new Listener()
+ {
+ @Override
+ public void receive(ReusableBuffer resampledAudioBuffer)
+ {
+ processResampledAudio(resampledAudioBuffer);
+ }
+ });
+ }
}
}
}
diff --git a/src/main/java/io/github/dsheirer/module/demodulate/audio/DemodulatedAudioFilterModule.java b/src/main/java/io/github/dsheirer/module/demodulate/audio/DemodulatedAudioFilterModule.java
deleted file mode 100644
index 57082c6f..00000000
--- a/src/main/java/io/github/dsheirer/module/demodulate/audio/DemodulatedAudioFilterModule.java
+++ /dev/null
@@ -1,120 +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
- *
- ******************************************************************************/
-package io.github.dsheirer.module.demodulate.audio;
-
-import io.github.dsheirer.dsp.filter.FilterFactory;
-import io.github.dsheirer.dsp.filter.Window.WindowType;
-import io.github.dsheirer.dsp.filter.dc.DCRemovalFilter_RealBuffer;
-import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter_RB_RB;
-import io.github.dsheirer.module.Module;
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.IFilteredRealBufferProvider;
-import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener;
-import io.github.dsheirer.sample.real.RealBuffer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Deprecated //Remove once polyphase channelizer enhancement is complete
-public class DemodulatedAudioFilterModule extends Module implements IUnFilteredRealBufferListener, IFilteredRealBufferProvider
-{
- private final static Logger mLog = LoggerFactory.getLogger(DemodulatedAudioFilterModule.class);
-
- private DCRemovalFilter_RealBuffer mDCFilter;
- private RealFIRFilter_RB_RB mBandPassFilter;
-
- /**
- * Filters demodulated audio and removes any DC component from the buffer
- *
- * @param pass - audio pass band pass frequency
- * @param stop - audio pass band stop frequency
- */
- public DemodulatedAudioFilterModule(int pass, int stop)
- {
- assert (stop > pass);
-
- mDCFilter = new DCRemovalFilter_RealBuffer();
-
- //TODO: change this to a band pass filter
- mBandPassFilter = new RealFIRFilter_RB_RB(FilterFactory.getLowPass(48000, pass, stop, 60,
- WindowType.HANN, true), 1.0f);
-
- mDCFilter.setListener(mBandPassFilter);
- }
-
- /**
- * Constructs a demodulated audio filter using the filter taps and gain provided
- */
- public DemodulatedAudioFilterModule(float[] taps, float gain)
- {
- mBandPassFilter = new RealFIRFilter_RB_RB(taps, gain);
- }
-
- @Override
- public void dispose()
- {
- mBandPassFilter.dispose();
- mBandPassFilter = null;
-
- if(mDCFilter != null)
- {
- mDCFilter.dispose();
- mDCFilter = null;
- }
- }
-
- @Override
- public void reset()
- {
- }
-
- @Override
- public void setFilteredRealBufferListener(Listener listener)
- {
- mBandPassFilter.setListener(listener);
- }
-
- @Override
- public void removeFilteredRealBufferListener()
- {
- mBandPassFilter.removeListener();
- }
-
- @Override
- public void start()
- {
- }
-
- @Override
- public void stop()
- {
- }
-
- @Override
- public Listener getUnFilteredRealBufferListener()
- {
- if(mDCFilter != null)
- {
- return mDCFilter;
- }
- else
- {
- return mBandPassFilter;
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/record/wave/MonoWaveReader.java b/src/main/java/io/github/dsheirer/record/wave/MonoWaveReader.java
deleted file mode 100644
index da5d60e8..00000000
--- a/src/main/java/io/github/dsheirer/record/wave/MonoWaveReader.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2016 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- *
- ******************************************************************************/
-package io.github.dsheirer.record.wave;
-
-import io.github.dsheirer.sample.ConversionUtils;
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.RealBuffer;
-import org.apache.commons.lang3.Validate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.ShortBuffer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.util.Arrays;
-import java.util.regex.Pattern;
-
-public class MonoWaveReader implements AutoCloseable
-{
- private final static Logger mLog = LoggerFactory.getLogger( MonoWaveReader.class);
-
- private static final Pattern FILENAME_PATTERN = Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
-
- private Path mPath;
- private boolean mRealTime;
- private InputStream mInputStream;
- private int mDataByteSize = 0;
- private Listener mListener;
- private byte[] mDataBuffer = new byte[8000];
-
- /**
- * Wave file reader for PCM 8kHz, 16-bit little-endian format.
- *
- * @param path of the wave file
- * @param realTime to force replay to real-time. Note: you should run this reader on a separate thread if you
- * choose real time since this reader will sleep the calling thread as needed to effect real-time playback.
- */
- public MonoWaveReader(Path path, boolean realTime) throws IOException
- {
- Validate.isTrue(path != null);
- mPath = path;
- mRealTime = realTime;
-
- open();
- }
-
- /**
- * Opens the file
- */
- private void open() throws IOException
- {
- if(!Files.exists(mPath))
- {
- throw new IOException("File not found");
- }
-
- mInputStream = Files.newInputStream(mPath, StandardOpenOption.READ);
-
- //Check for RIFF header
- byte[] buffer = new byte[4];
- mInputStream.read(buffer);
-
- if(!Arrays.equals(buffer, WaveUtils.RIFF_CHUNK))
- {
- throw new IOException("File is not .wav format - missing RIFF chunk");
- }
-
- //Get file size
- mInputStream.read(buffer);
- int fileSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
-
- //Check for WAVE format
- mInputStream.read(buffer);
-
- if(!Arrays.equals(buffer, WaveUtils.WAV_FORMAT))
- {
- throw new IOException("File is not .wav format - missing WAVE format");
- }
-
- //Check for format chunk
- mInputStream.read(buffer);
-
- if(!Arrays.equals(buffer, WaveUtils.CHUNK_FORMAT))
- {
- throw new IOException("File is not .wav format - missing format chunk");
- }
-
- //Get chunk size
- mInputStream.read(buffer);
- int chunkSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
-
- //Get format
- mInputStream.read(buffer);
-
- ShortBuffer shortBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
- short format = shortBuffer.get();
- if(format != WaveUtils.PCM_FORMAT)
- {
- throw new IOException("File format not supported - expecting PCM format");
- }
-
- //Get number of channels
- short channels = shortBuffer.get();
- if(channels != 1)
- {
- throw new IOException("Unsupported channel count - mono audio only");
- }
-
- //Get samples per second
- mInputStream.read(buffer);
- int sampleRate = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
-
- //Get bytes per second
- mInputStream.read(buffer);
- int bytesPerSecond = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
-
- mInputStream.read(buffer);
-
- //Get frame size
- shortBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
- short frameSize = shortBuffer.get();
- if(frameSize != 2)
- {
- throw new IOException("PCM frame size not supported - expecting 2 bytes per frame");
- }
-
- //Get bits per sample
- short bitsPerSample = shortBuffer.get();
- if(bitsPerSample != 16)
- {
- throw new IOException("PCM sample size not supported - expecting 16 bits per sample");
- }
-
- mInputStream.read(buffer);
-
- if(!Arrays.equals(buffer, WaveUtils.CHUNK_DATA))
- {
- throw new IOException("Unexpected chunk - expecting data chunk");
- }
-
- //Get data chunk size
- mInputStream.read(buffer);
- mDataByteSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
- }
-
- public void read() throws IOException
- {
- long start = System.currentTimeMillis();
- long samplesRead = 0;
-
- int totalBytesRead = 0;
-
- while(totalBytesRead < mDataByteSize)
- {
- int bytesRead = mInputStream.read(mDataBuffer);
-
- totalBytesRead += bytesRead;
-
- float[] samples = ConversionUtils.convertFromSigned16BitSamples(mDataBuffer);
-
- if(bytesRead != mDataBuffer.length)
- {
- samples = Arrays.copyOf(samples, bytesRead / 2);
- }
-
- if(mRealTime)
- {
- samplesRead += samples.length;
-
- long actualElapsed = System.currentTimeMillis() - start;
-
- long audioElapsed = (long)(((double)samplesRead / 8000.0) * 1000.0);
-
- if(audioElapsed > actualElapsed)
- {
- try
- {
- Thread.sleep(audioElapsed - actualElapsed);
- }
- catch (InterruptedException e)
- {
- }
- }
- }
-
- if(mListener != null)
- {
- mListener.receive(new RealBuffer(samples));
- }
- }
-
- close();
- }
-
- public void setListener(Listener listener)
- {
- mListener = listener;
- }
-
- /**
- * Closes the file
- */
- public void close() throws IOException
- {
- if(mInputStream != null)
- {
- mInputStream.close();
- }
- }
-
- public static void main(String[] args)
- {
- Path path = Paths.get("/home/denny/Music/PCM.wav");
- mLog.debug("Opening: " + path.toString());
-
- try
- {
- MonoWaveReader reader = new MonoWaveReader(path, true);
- reader.setListener(new Listener()
- {
- @Override
- public void receive(RealBuffer realBuffer)
- {
- mLog.debug("Received buffer");
- }
- });
- reader.read();
- }
- catch(IOException e)
- {
- mLog.error("Error", e);
- }
-
- mLog.debug("Finished");
- }
-}
diff --git a/src/main/java/io/github/dsheirer/record/wave/RealBufferWaveRecorder.java b/src/main/java/io/github/dsheirer/record/wave/RealBufferWaveRecorder.java
deleted file mode 100644
index 28c56db5..00000000
--- a/src/main/java/io/github/dsheirer/record/wave/RealBufferWaveRecorder.java
+++ /dev/null
@@ -1,210 +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
- *
- ******************************************************************************/
-package io.github.dsheirer.record.wave;
-
-import io.github.dsheirer.module.Module;
-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;
-
-import javax.sound.sampled.AudioFormat;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-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
-{
- private final static Logger mLog = LoggerFactory.getLogger(RealBufferWaveRecorder.class);
-
- private WaveWriter mWriter;
- private String mFilePrefix;
- private Path mFile;
- private AudioFormat mAudioFormat;
-
- private BufferProcessor mBufferProcessor;
- private ScheduledFuture> mProcessorHandle;
- private LinkedBlockingQueue mBuffers = new LinkedBlockingQueue<>(500);
- private long mLastBufferReceived;
-
- private AtomicBoolean mRunning = new AtomicBoolean();
-
- public RealBufferWaveRecorder(int sampleRate, String filePrefix)
- {
- mAudioFormat = new AudioFormat(sampleRate, //SampleRate
- 16, //Sample Size
- 1, //Channels
- true, //Signed
- false); //Little Endian
-
- mFilePrefix = filePrefix;
- }
-
- /**
- * Indicates if the recorder is currently running.
- */
- public boolean isRunning()
- {
- return mRunning.get();
- }
-
- /**
- * Timestamp of when the latest buffer was received by this recorder
- */
- public long getLastBufferReceived()
- {
- return mLastBufferReceived;
- }
-
- public Path getFile()
- {
- return mFile;
- }
-
- public void start()
- {
- if(mRunning.compareAndSet(false, true))
- {
- if(mBufferProcessor == null)
- {
- mBufferProcessor = new BufferProcessor();
- }
-
- try
- {
- StringBuilder sb = new StringBuilder();
- sb.append(mFilePrefix);
- sb.append("_");
- sb.append(TimeStamp.getLongTimeStamp("_"));
- sb.append(".wav");
-
- mFile = Paths.get(sb.toString());
-
- mWriter = new WaveWriter(mAudioFormat, mFile);
-
- /* Schedule the processor to run every 500 milliseconds */
- mProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
- }
- catch(IOException io)
- {
- mLog.error("Error starting real buffer recorder", io);
- }
- }
- }
-
- public void stop()
- {
- if(mRunning.compareAndSet(true, false))
- {
- try
- {
- write();
-
- if(mWriter != null)
- {
- mWriter.close();
- mWriter = null;
- }
- }
- catch(IOException ioe)
- {
- mLog.error("Error writing final audio buffers to recording during shutdown", ioe);
- }
- }
- }
-
- @Override
- public void receive(RealBuffer buffer)
- {
- if(mRunning.get())
- {
- boolean success = mBuffers.offer(buffer);
-
- if(!success)
- {
- mLog.error("recorder buffer overflow - purging [" + mFile.toFile().getAbsolutePath() + "]");
- mBuffers.clear();
- }
-
- mLastBufferReceived = System.currentTimeMillis();
- }
- }
-
- @Override
- public Listener getFilteredRealBufferListener()
- {
- return this;
- }
-
- @Override
- public void dispose()
- {
- stop();
- }
-
- @Override
- public void reset()
- {
- }
-
- /**
- * Writes all audio currently in the queue to the file
- * @throws IOException if there are any errors writing the audio
- */
- private void write() throws IOException
- {
- RealBuffer buffer = mBuffers.poll();
-
- while(buffer != null)
- {
- mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(buffer));
- buffer = mBuffers.poll();
- }
- }
-
- public class BufferProcessor implements Runnable
- {
- public void run()
- {
- try
- {
- write();
- }
- catch(IOException ioe)
- {
- /* Stop this module if/when we get an IO exception */
- mBuffers.clear();
- stop();
-
- mLog.error("IO Exception while trying to write to the wave writer", ioe);
- }
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/sample/real/RateCounter_RB.java b/src/main/java/io/github/dsheirer/sample/real/RateCounter_RB.java
deleted file mode 100644
index 287f80ed..00000000
--- a/src/main/java/io/github/dsheirer/sample/real/RateCounter_RB.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package io.github.dsheirer.sample.real;
-
-import io.github.dsheirer.sample.Listener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class RateCounter_RB implements Listener
-{
- private final static Logger mLog =
- LoggerFactory.getLogger( RateCounter_RB.class );
-
- private Listener mListener;
- private long mCount;
- private long mStartTime = -1;
-
- private String mName;
-
- public RateCounter_RB()
- {
- mName = "UNNAMED";
- }
-
- public RateCounter_RB( String name )
- {
- mName = name;
- }
-
- @Override
- public void receive( RealBuffer buffer )
- {
- if( mStartTime == -1 )
- {
- mStartTime = System.currentTimeMillis();
- }
-
- mCount += buffer.getSamples().length;
-
- long elapsed = System.currentTimeMillis() - mStartTime;
-
- if( elapsed != 0 )
- {
- mLog.debug( "[" + mName + "] Sample Rate: " + ( mCount / elapsed ) + " kHz" );
- }
-
- if( mListener != null )
- {
- mListener.receive( buffer );
- }
- }
-
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/sample/real/RealBufferToStreamConverter.java b/src/main/java/io/github/dsheirer/sample/real/RealBufferToStreamConverter.java
deleted file mode 100644
index a7495db8..00000000
--- a/src/main/java/io/github/dsheirer/sample/real/RealBufferToStreamConverter.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package io.github.dsheirer.sample.real;
-
-import io.github.dsheirer.sample.Listener;
-
-/**
- * Real sample buffer to stream converter
- */
-public class RealBufferToStreamConverter implements Listener
-{
- private RealSampleListener mListener;
-
- @Override
- public void receive( RealBuffer buffer )
- {
- if( mListener != null )
- {
- for( float sample: buffer.getSamples() )
- {
- mListener.receive( sample );
- }
- }
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- public void setListener( RealSampleListener listener )
- {
- mListener = listener;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java
index 799d4e84..eb40a07a 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java
@@ -1,24 +1,21 @@
/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2017 Dennis Sheirer
+ * sdr-trunk
+ * Copyright (C) 2014-2018 Dennis Sheirer
*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see
*
******************************************************************************/
package io.github.dsheirer.source.tuner.airspy;
-import io.github.dsheirer.dsp.filter.dc.DCRemovalFilter_RB;
+import io.github.dsheirer.dsp.filter.dc.DCRemovalFilter;
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter;
@@ -30,7 +27,7 @@ public class AirspySampleConverter extends NativeBufferConverter
{
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
- private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
+ private DCRemovalFilter mDCFilter = new DCRemovalFilter(0.01f);
private HilbertTransform mHilbertTransform = new HilbertTransform();
private boolean mSamplePacking = false;
private FloatBuffer mFloatBuffer;
diff --git a/src/main/java/io/github/dsheirer/source/tuner/frequency/DCTrackingFrequencyControl.java b/src/main/java/io/github/dsheirer/source/tuner/frequency/DCTrackingFrequencyControl.java
deleted file mode 100644
index ff92b14a..00000000
--- a/src/main/java/io/github/dsheirer/source/tuner/frequency/DCTrackingFrequencyControl.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package io.github.dsheirer.source.tuner.frequency;
-/*******************************************************************************
- * 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
- *
- ******************************************************************************/
-
-import io.github.dsheirer.module.Module;
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.RealBuffer;
-import io.github.dsheirer.source.ISourceEventProvider;
-import io.github.dsheirer.source.SourceEvent;
-
-public class DCTrackingFrequencyControl extends Module implements Listener, ISourceEventProvider
-{
- private static float COARSE_THRESHOLD = 0.1f;
- private static float LOOP_GAIN = 0.1f;
- private static float COARSE_GAIN = 1000.0f;
- private static float FINE_GAIN = 100.0f;
- private int mErrorCorrection = 0;
- private int mMaximumFrequencyCorrection = 0;
- private Listener mListener;
-
- /**
- * DC offset Automatic Frequency Control. Monitors DC bias present in an FM
- * demodulated audio stream and issues automatic frequency corrections to
- * maintain the DC bias close to zero.
- *
- * @param maximum - maximum allowable (+/-) frequency correction value.
- */
- public DCTrackingFrequencyControl(int maximum)
- {
- mMaximumFrequencyCorrection = maximum;
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- @Override
- public void receive(RealBuffer buffer)
- {
- double sum = 0.0d;
-
- for(float sample : buffer.getSamples())
- {
- sum += sample;
- }
-
- float mean = (float)(sum / (double)buffer.getSamples().length);
-
- float adjustment = 0.0f;
-
- if(mean > COARSE_THRESHOLD || mean < -COARSE_THRESHOLD)
- {
- adjustment = mean * COARSE_GAIN;
- }
- else
- {
- adjustment = mean * FINE_GAIN;
- }
-
- setErrorCorrection(mErrorCorrection + (int)((float)adjustment * LOOP_GAIN));
- }
-
- private void setErrorCorrection(int correction)
- {
- if(mListener != null)
- {
- if(correction > mMaximumFrequencyCorrection)
- {
- correction = mMaximumFrequencyCorrection;
- }
- else if(correction < -mMaximumFrequencyCorrection)
- {
- correction = -mMaximumFrequencyCorrection;
- }
-
- if(correction != mErrorCorrection)
- {
- mListener.receive(SourceEvent.channelFrequencyCorrectionRequest(correction));
- }
- }
- }
-
- @Override
- public void setSourceEventListener(Listener listener)
- {
- mListener = listener;
- }
-
- @Override
- public void removeSourceEventListener()
- {
- mListener = null;
- }
-
- @Override
- public void start()
- {
- }
-
- @Override
- public void stop()
- {
- }
-
- @Override
- public void reset()
- {
- setErrorCorrection(0);
- }
-}
diff --git a/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java b/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java
index 4cc781df..fb565cf1 100644
--- a/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java
+++ b/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java
@@ -26,9 +26,8 @@ import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
-import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
-import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
import io.github.dsheirer.settings.ColorSettingMenuItem;
import io.github.dsheirer.settings.Setting;
@@ -60,7 +59,7 @@ import java.awt.event.MouseMotionListener;
import java.util.concurrent.atomic.AtomicBoolean;
public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener,
- Listener, SettingChangeListener, SpectralDisplayAdjuster
+ Listener, SettingChangeListener, SpectralDisplayAdjuster
{
private static final long serialVersionUID = 1L;
@@ -164,7 +163,6 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
}
@Override
- @SuppressWarnings("incomplete-switch")
public void channelChanged(ChannelEvent event)
{
switch(event.getEvent())
@@ -217,7 +215,7 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
if(processingChain != null)
{
- processingChain.addRealBufferListener(this);
+// processingChain.addRealBufferListener(this);
mDFTProcessor.start();
}
@@ -233,7 +231,7 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
if(processingChain != null)
{
- processingChain.removeRealBufferListener(this);
+// processingChain.removeRealBufferListener(this);
}
}
@@ -270,16 +268,17 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener
}
@Override
- public void receive(RealBuffer buffer)
+ public void receive(ReusableBuffer buffer)
{
- RealBuffer decimated = mDecimatingFilter.filter(buffer);
-
- //Hack: we're placing real samples in a complex buffer that the DFT
- //processor is expecting.
- ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(decimated.getSamples().length);
- reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis());
- reusableComplexBuffer.incrementUserCount();
- mDFTProcessor.receive(reusableComplexBuffer);
+ throw new IllegalStateException("This must be updated to work with reusable buffers");
+// RealBuffer decimated = mDecimatingFilter.filter(buffer);
+//
+// //Hack: we're placing real samples in a complex buffer that the DFT
+// //processor is expecting.
+// ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(decimated.getSamples().length);
+// reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis());
+// reusableComplexBuffer.incrementUserCount();
+// mDFTProcessor.receive(reusableComplexBuffer);
}
/**