diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml
index 67e551ce..31ec5bc1 100644
--- a/.idea/dictionaries/denny.xml
+++ b/.idea/dictionaries/denny.xml
@@ -4,6 +4,7 @@
esnid
fleetsync
ident
+ polyphase
talkgroup
tgid
diff --git a/.idea/libraries/jmbe_interface_0_3_3.xml b/.idea/libraries/jmbe_interface_0_3_3.xml
new file mode 100644
index 00000000..1b73f0a9
--- /dev/null
+++ b/.idea/libraries/jmbe_interface_0_3_3.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/imports/jmbe-interface-0.3.2.jar b/imports/jmbe-interface-0.3.3.jar
similarity index 88%
rename from imports/jmbe-interface-0.3.2.jar
rename to imports/jmbe-interface-0.3.3.jar
index 29100a3f..73fac286 100644
Binary files a/imports/jmbe-interface-0.3.2.jar and b/imports/jmbe-interface-0.3.3.jar differ
diff --git a/sdrtrunk.iml b/sdrtrunk.iml
index 740623a3..4ceeed69 100644
--- a/sdrtrunk.iml
+++ b/sdrtrunk.iml
@@ -4,6 +4,9 @@
+
+
+
@@ -11,11 +14,12 @@
-
+
+
\ No newline at end of file
diff --git a/src/audio/AudioFormats.java b/src/audio/AudioFormats.java
index f7c58198..ac859f54 100644
--- a/src/audio/AudioFormats.java
+++ b/src/audio/AudioFormats.java
@@ -35,7 +35,7 @@ public class AudioFormats
IMBE_FRAME_RATE,
LITTLE_ENDIAN );
- public static AudioFormat PCM_SIGNED_8KHZ_16BITS =
+ public static AudioFormat PCM_SIGNED_8KHZ_16BITS_MONO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
@@ -43,7 +43,16 @@ public class AudioFormats
PCM_FRAME_SIZE_BYTES_MONO,
PCM_8KHZ_RATE,
LITTLE_ENDIAN );
-
+
+ public static AudioFormat PCM_SIGNED_8KHZ_16BITS_STEREO =
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_8KHZ_RATE,
+ PCM_SAMPLE_SIZE_BITS,
+ TWO_CHANNELS,
+ PCM_FRAME_SIZE_BYTES_STEREO,
+ PCM_8KHZ_RATE,
+ LITTLE_ENDIAN );
+
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_48KHZ_RATE,
@@ -66,11 +75,11 @@ public class AudioFormats
* Source Data Line Info for a 48 kHz, 16-bits signed PCM one channel
*/
public static final Line.Info MONO_SOURCE_DATALINE_INFO =
- new DataLine.Info( SourceDataLine.class, PCM_SIGNED_48KHZ_16BITS_MONO );
+ new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_MONO);
/**
* Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
*/
public static final Line.Info STEREO_SOURCE_DATALINE_INFO =
- new DataLine.Info( SourceDataLine.class, PCM_SIGNED_48KHZ_16BITS_STEREO );
+ new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_STEREO);
}
diff --git a/src/audio/AudioModule.java b/src/audio/AudioModule.java
index 01ad02d4..4a02298f 100644
--- a/src/audio/AudioModule.java
+++ b/src/audio/AudioModule.java
@@ -3,6 +3,10 @@ package audio;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
+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;
@@ -22,171 +26,209 @@ import dsp.filter.Filters;
import dsp.filter.fir.real.RealFIRFilter_R_R;
/**
- * Provides packaging of demodulated audio sample buffers into audio packets for
- * broadcast to registered audio packet listeners. Includes audio packet
+ * 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,
- IChannelEventListener,
- IMetadataListener,
- IFilteredRealBufferListener,
- ISquelchStateListener,
- Listener
+public class AudioModule extends Module implements IAudioPacketProvider,
+ IChannelEventListener,
+ IMetadataListener,
+ IFilteredRealBufferListener,
+ ISquelchStateListener,
+ Listener
{
- protected static final Logger mLog = LoggerFactory.getLogger( AudioModule.class );
-
- /* Provides a unique identifier for this audio module instance to use as a
- * source identifier for all audio packets */
- private static int UNIQUE_ID = 0;
- private int mSourceID;
-
- private AudioMetadata mAudioMetadata;
- private ChannelEventListener mChannelEventListener = new ChannelEventListener();
- private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
- private SquelchState mSquelchState = SquelchState.SQUELCH;
- private Listener mAudioPacketListener;
+ protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
-//TODO: remove this -- is being replaced by the DemodulatedAudioFilterModule
- private RealFIRFilter_R_R mAudioFilter = new RealFIRFilter_R_R(
- Filters.FIR_BANDPASS_AUDIO_48KHZ.getCoefficients(), 2.0f );
-
- public AudioModule( boolean record )
- {
- mSourceID = ++UNIQUE_ID;
- mAudioMetadata = new AudioMetadata( mSourceID, record );
- }
-
- @Override
- public void dispose()
- {
- mSquelchStateListener = null;
- mAudioPacketListener = null;
- }
+ private static float[] mDecimationCoefficients;
- @Override
- public void reset()
- {
- mAudioMetadata.reset();
- }
-
- @Override
- public void start( ScheduledExecutorService executor )
- {
- /* No start operations provided */
- }
+ 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();
- @Override
- public void stop()
- {
+ 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);
+ }
+ }
+
+ /* Provides a unique identifier for this audio module instance to use as a
+ * source identifier for all audio packets */
+ private static int UNIQUE_ID = 0;
+ private int mSourceID;
+
+ private AudioMetadata mAudioMetadata;
+ private ChannelEventListener mChannelEventListener = new ChannelEventListener();
+ private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
+ private SquelchState mSquelchState = SquelchState.SQUELCH;
+ private Listener mAudioPacketListener;
+ private PolyphaseFIRDecimatingFilter_RB mAudioDecimationFilter;
+
+ public AudioModule(boolean record)
+ {
+ mSourceID = ++UNIQUE_ID;
+ mAudioMetadata = new AudioMetadata(mSourceID, record);
+
+ mAudioDecimationFilter = new PolyphaseFIRDecimatingFilter_RB(mDecimationCoefficients, 6, 2.0f);
+ mAudioDecimationFilter.setListener(new Listener()
+ {
+ @Override
+ public void receive(RealBuffer realBuffer)
+ {
+ if (mAudioPacketListener != null)
+ {
+ AudioPacket packet = new AudioPacket(realBuffer.getSamples(), mAudioMetadata.copyOf());
+ mAudioPacketListener.receive(packet);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void dispose()
+ {
+ mSquelchStateListener = null;
+ mAudioPacketListener = null;
+ }
+
+ @Override
+ public void reset()
+ {
+ mAudioMetadata.reset();
+ }
+
+ @Override
+ public void start(ScheduledExecutorService executor)
+ {
+ /* 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,
- mAudioMetadata.copyOf() ) );
- }
- }
+ if (mAudioPacketListener != null)
+ {
+ mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END,
+ mAudioMetadata.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 )
- {
- /* We make a copy of the samples, so that we don't affect any other
- * processes that might be concurrently processing the same buffer */
- float[] audio = Arrays.copyOf( buffer.getSamples(), buffer.getSamples().length );
-
- mAudioFilter.filter( audio );
-
- AudioPacket packet = new AudioPacket( audio, mAudioMetadata.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);
- if( mAudioPacketListener != null )
- {
- mAudioPacketListener.receive( packet );
- }
- }
- }
+// /* We make a copy of the samples, so that we don't affect any other
+// * processes that might be concurrently processing the same buffer */
+// float[] audio = Arrays.copyOf( buffer.getSamples(), buffer.getSamples().length );
+//
+// mAudioFilter.filter( audio );
+ }
+ }
- @Override
- public Listener getFilteredRealBufferListener()
- {
- return this;
- }
+ @Override
+ public Listener getFilteredRealBufferListener()
+ {
+ return this;
+ }
- @Override
- public Listener getMetadataListener()
- {
- return mAudioMetadata;
- }
+ @Override
+ public Listener getMetadataListener()
+ {
+ return mAudioMetadata;
+ }
- @Override
- public void setAudioPacketListener( Listener listener )
- {
- mAudioPacketListener = listener;
- }
+ @Override
+ public void setAudioPacketListener(Listener listener)
+ {
+ mAudioPacketListener = listener;
+ }
- @Override
- public void removeAudioPacketListener()
- {
- mAudioPacketListener = null;
- }
+ @Override
+ public void removeAudioPacketListener()
+ {
+ mAudioPacketListener = null;
+ }
- @Override
- public Listener getChannelEventListener()
- {
- return mChannelEventListener;
- }
+ @Override
+ public Listener getChannelEventListener()
+ {
+ return mChannelEventListener;
+ }
- @Override
- public Listener getSquelchStateListener()
- {
- return mSquelchStateListener;
- }
+ @Override
+ public Listener getSquelchStateListener()
+ {
+ return mSquelchStateListener;
+ }
- /**
- * Wrapper for channel event listener. Responds to channel state reset
- * events to remove/cleanup current audio metadata
- */
- public class ChannelEventListener implements Listener
- {
- @Override
- public void receive( ChannelEvent event )
- {
- switch( event.getEvent() )
- {
- case NOTIFICATION_STATE_RESET:
- mAudioMetadata.reset();
- break;
- case NOTIFICATION_SELECTION_CHANGE:
- mAudioMetadata.setSelected( event.getChannel().isSelected() );
- break;
- default:
- break;
- }
- }
- }
+ /**
+ * Wrapper for channel event listener. Responds to channel state reset
+ * events to remove/cleanup current audio metadata
+ */
+ public class ChannelEventListener implements Listener
+ {
+ @Override
+ public void receive(ChannelEvent event)
+ {
+ switch (event.getEvent())
+ {
+ case NOTIFICATION_STATE_RESET:
+ mAudioMetadata.reset();
+ break;
+ case NOTIFICATION_SELECTION_CHANGE:
+ mAudioMetadata.setSelected(event.getChannel().isSelected());
+ break;
+ default:
+ break;
+ }
+ }
+ }
- /**
- * Wrapper for squelch state listener
- */
- public class SquelchStateListener implements Listener
- {
- @Override
- public void receive( SquelchState state )
- {
- if( state == SquelchState.SQUELCH && mAudioPacketListener != null )
- {
- mAudioPacketListener.receive( new AudioPacket( AudioPacket.Type.END,
- mAudioMetadata.copyOf() ) );
- }
-
- mSquelchState = state;
- }
- }
+ /**
+ * Wrapper for squelch state listener
+ */
+ public class SquelchStateListener implements Listener
+ {
+ @Override
+ public void receive(SquelchState state)
+ {
+ if (state == SquelchState.SQUELCH && mAudioPacketListener != null)
+ {
+ mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END,
+ mAudioMetadata.copyOf()));
+ }
+
+ mSquelchState = state;
+ }
+ }
}
diff --git a/src/audio/AudioPacket.java b/src/audio/AudioPacket.java
index 12bbf22b..2936557e 100644
--- a/src/audio/AudioPacket.java
+++ b/src/audio/AudioPacket.java
@@ -5,7 +5,7 @@ import audio.metadata.AudioMetadata;
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014,2015 Dennis Sheirer
+ * 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
diff --git a/src/audio/output/MonoAudioOutput.java b/src/audio/output/MonoAudioOutput.java
index e146e507..a217c192 100644
--- a/src/audio/output/MonoAudioOutput.java
+++ b/src/audio/output/MonoAudioOutput.java
@@ -33,14 +33,14 @@ import controller.ThreadPoolManager;
*/
public class MonoAudioOutput extends AudioOutput
{
- private final static int BUFFER_SIZE = 48000;
+ private final static int BUFFER_SIZE = 8000;
public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
{
super( threadPoolManager,
mixer,
MixerChannel.MONO,
- AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO,
+ AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
AudioFormats.MONO_SOURCE_DATALINE_INFO,
BUFFER_SIZE );
}
diff --git a/src/audio/output/StereoAudioOutput.java b/src/audio/output/StereoAudioOutput.java
index 2ba8db41..098112ec 100644
--- a/src/audio/output/StereoAudioOutput.java
+++ b/src/audio/output/StereoAudioOutput.java
@@ -33,7 +33,7 @@ import controller.ThreadPoolManager;
*/
public class StereoAudioOutput extends AudioOutput
{
- private final static int BUFFER_SIZE = 96000;
+ private final static int BUFFER_SIZE = 16000;
public StereoAudioOutput( ThreadPoolManager threadPoolManager,
Mixer mixer, MixerChannel channel )
@@ -41,7 +41,7 @@ public class StereoAudioOutput extends AudioOutput
super( threadPoolManager,
mixer,
channel,
- AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO,
+ AudioFormats.PCM_SIGNED_8KHZ_16BITS_STEREO,
AudioFormats.STEREO_SOURCE_DATALINE_INFO,
BUFFER_SIZE );
}
diff --git a/src/dsp/filter/design/FilterView.css b/src/dsp/filter/design/FilterView.css
index 9c70eec8..43b2bf73 100644
--- a/src/dsp/filter/design/FilterView.css
+++ b/src/dsp/filter/design/FilterView.css
@@ -1,12 +1,12 @@
/* ....Show License.... */
.chart {
- -fx-background-image: url("../../../../images/Curve-fitted-background.png");
+ -fx-background-image: url("../../../Curve-fitted-background.png");
-fx-padding: 15 25 15 15;
}
.chart-plot-background {
-fx-background-color: red;
- -fx-background-image: url("../../../../images/Curve-fitted-chart-background.png"),
- url("../../../../images/Curve-fitted-graph-gridlines.png");
+ -fx-background-image: url("../../../Curve-fitted-chart-background.png"),
+ url("../../../Curve-fitted-graph-gridlines.png");
-fx-background-size: cover, auto;
-fx-background-repeat: no-repeat, repeat;
-fx-background-position: 0% 0%, 0% 100%;
diff --git a/src/dsp/filter/design/FilterViewer.java b/src/dsp/filter/design/FilterViewer.java
index 7dac65b7..3b84c4a4 100644
--- a/src/dsp/filter/design/FilterViewer.java
+++ b/src/dsp/filter/design/FilterViewer.java
@@ -40,37 +40,35 @@ public class FilterViewer extends Application
*/
private float[] getFilter()
{
- FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
- .sampleRate( 10000 )
- .gridDensity( 16 )
- .passBandCutoff( 1300 )
+// 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();
+
+ FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
+ .sampleRate( 8000 )
+ .stopBandCutoff( 250 )
+ .stopBandAmplitude( 0.0 )
+ .stopBandRipple( 0.01 )
+ .passBandStart( 300 )
.passBandAmplitude( 1.0 )
.passBandRipple( 0.01 )
- .stopBandStart( 2675 )
- .stopBandAmplitude( 0.0 )
- .stopBandRipple( 0.001 )
.build();
-
-// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
-// .sampleRate( 8000 )
-// .gridDensity( 16 )
-// .stopBandCutoff( 2000 )
-// .stopBandAmplitude( 0.0 )
-// .stopBandRipple( 0.003 )
-// .passBandStart( 3000 )
-// .passBandAmplitude( 1.0 )
-// .passBandRipple( 0.001 )
-// .build();
// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
-// .sampleRate( 8000 )
-// .order( 164 )
-// .stopFrequency1( 1100 )
-// .passFrequencyBegin( 1200 )
-// .passFrequencyEnd( 2400 )
-// .stopFrequency2( 2500 )
-// .stopRipple( 0.01 )
-// .passRipple( 0.01 )
+// .sampleRate( 48000 )
+// .stopFrequency1( 150 )
+// .passFrequencyBegin( 200 )
+// .passFrequencyEnd( 3950 )
+// .stopFrequency2( 4000 )
+// .stopRipple( 0.0005 )
+// .passRipple( 0.003)
// .build();
float[] taps = null;
diff --git a/src/dsp/filter/polyphase/FilterStage.java b/src/dsp/filter/polyphase/FilterStage.java
new file mode 100644
index 00000000..55a8556c
--- /dev/null
+++ b/src/dsp/filter/polyphase/FilterStage.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * 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 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/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java b/src/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java
new file mode 100644
index 00000000..b312cad2
--- /dev/null
+++ b/src/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * 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 dsp.filter.polyphase;
+
+import dsp.filter.design.FilterDesignException;
+import dsp.filter.fir.FIRFilter;
+import dsp.filter.fir.FIRFilterSpecification;
+import dsp.filter.fir.remez.RemezFIRFilterDesigner;
+import dsp.mixer.Oscillator;
+import gui.SDRTrunk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sample.Listener;
+import sample.real.RealBuffer;
+import sample.real.RealToRealBufferAssembler;
+
+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()));
+ }
+ });
+
+ Oscillator oscillator = new Oscillator(3400, 48000);
+
+ float[] samples = new float[500];
+ for(int x = 0; x < 500; x++)
+ {
+ samples[x] = oscillator.getFloat();
+ oscillator.rotate();
+ }
+
+ RealBuffer buffer = new RealBuffer(samples);
+ filter.receive(buffer);
+ }
+ catch(FilterDesignException e)
+ {
+ mLog.debug("Error designing filter", e);
+ }
+
+ }
+}
diff --git a/src/dsp/filter/PolyphaseFIRInterpolatingFilter.java b/src/dsp/filter/polyphase/PolyphaseFIRInterpolatingFilter.java
similarity index 84%
rename from src/dsp/filter/PolyphaseFIRInterpolatingFilter.java
rename to src/dsp/filter/polyphase/PolyphaseFIRInterpolatingFilter.java
index 00c76261..65b41b46 100644
--- a/src/dsp/filter/PolyphaseFIRInterpolatingFilter.java
+++ b/src/dsp/filter/polyphase/PolyphaseFIRInterpolatingFilter.java
@@ -1,4 +1,22 @@
-package dsp.filter;
+/*******************************************************************************
+ * 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 dsp.filter.polyphase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/module/decode/ltrnet/LTRNetDecoderState.java b/src/module/decode/ltrnet/LTRNetDecoderState.java
index 6f17d77a..56b0df90 100644
--- a/src/module/decode/ltrnet/LTRNetDecoderState.java
+++ b/src/module/decode/ltrnet/LTRNetDecoderState.java
@@ -1,17 +1,17 @@
/*******************************************************************************
* 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
******************************************************************************/
@@ -43,747 +43,755 @@ import audio.metadata.MetadataType;
public class LTRNetDecoderState extends DecoderState
{
- private final static Logger mLog = LoggerFactory.getLogger( LTRNetDecoderState.class );
+ private final static Logger mLog = LoggerFactory.getLogger(LTRNetDecoderState.class);
- private DecimalFormat mDecimalFormatter = new DecimalFormat( "0.00000" );
+ private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
- private TreeSet mTalkgroups = new TreeSet();
- private TreeSet mTalkgroupsFirstHeard = new TreeSet();
- private TreeSet mESNs = new TreeSet();
- private TreeSet mUniqueIDs = new TreeSet();
- private TreeSet mNeighborIDs = new TreeSet();
- private TreeSet mSiteIDs = new TreeSet();
- private HashMap mReceiveFrequencies =
- new HashMap();
- private HashMap mTransmitFrequencies =
- new HashMap();
- private HashMap mActiveCalls = new HashMap();
+ private TreeSet mTalkgroups = new TreeSet();
+ private TreeSet mTalkgroupsFirstHeard = new TreeSet();
+ private TreeSet mESNs = new TreeSet();
+ private TreeSet mUniqueIDs = new TreeSet();
+ private TreeSet mNeighborIDs = new TreeSet();
+ private TreeSet mSiteIDs = new TreeSet();
+ private HashMap mReceiveFrequencies =
+ new HashMap();
+ private HashMap mTransmitFrequencies =
+ new HashMap();
+ private HashMap mActiveCalls = new HashMap();
- private String mTalkgroup;
- private Alias mTalkgroupAlias;
- private String mDescription;
- private int mChannelNumber;
- private long mFrequency = 0;
+ private String mTalkgroup;
+ private Alias mTalkgroupAlias;
+ private String mDescription;
+ private int mChannelNumber;
+ private long mFrequency = 0;
- public LTRNetDecoderState( AliasList aliasList )
- {
- super( aliasList );
- }
-
- @Override
- public DecoderType getDecoderType()
- {
- return DecoderType.LTR_NET;
- }
-
- @Override
- public void receive( Message message )
+ public LTRNetDecoderState(AliasList aliasList)
{
- if( message.isValid() )
- {
- State state = State.IDLE;
+ super(aliasList);
+ }
- if( message instanceof LTRNetOSWMessage )
- {
- LTRNetOSWMessage ltr = (LTRNetOSWMessage)message;
-
- switch( ltr.getMessageType() )
- {
- case CA_ENDD:
- if( mChannelNumber == 0 )
- {
- mChannelNumber = ltr.getChannel();
- broadcast( ChangedAttribute.CHANNEL_NUMBER );
- }
+ @Override
+ public DecoderType getDecoderType()
+ {
+ return DecoderType.LTR_NET;
+ }
+
+ @Override
+ public void receive(Message message)
+ {
+ if (message.isValid())
+ {
+ State state = State.IDLE;
+
+ if (message instanceof LTRNetOSWMessage)
+ {
+ LTRNetOSWMessage ltr = (LTRNetOSWMessage) message;
+
+ switch (ltr.getMessageType())
+ {
+ case CA_ENDD:
+ if (mChannelNumber == 0)
+ {
+ mChannelNumber = ltr.getChannel();
+ broadcast(ChangedAttribute.CHANNEL_NUMBER);
+ }
/* Process FCC Station ID Events */
- if( ltr.getGroup() == 254 )
- {
- if( mCurrentCallEvent == null ||
- mCurrentCallEvent.getCallEventType() !=
- CallEventType.STATION_ID )
- {
- mCurrentCallEvent = new LTRCallEvent.Builder(
- DecoderType.LTR_NET, CallEventType.STATION_ID )
- .aliasList( getAliasList() )
- .channel( String.valueOf( mChannelNumber ) )
- .frequency( mFrequency )
- .to( ltr.getTalkgroupID() )
- .build();
+ if (ltr.getGroup() == 254)
+ {
+ if (mCurrentCallEvent == null ||
+ mCurrentCallEvent.getCallEventType() !=
+ CallEventType.STATION_ID)
+ {
+ mCurrentCallEvent = new LTRCallEvent.Builder(
+ DecoderType.LTR_NET, CallEventType.STATION_ID)
+ .aliasList(getAliasList())
+ .channel(String.valueOf(mChannelNumber))
+ .frequency(mFrequency)
+ .to(ltr.getTalkgroupID())
+ .build();
- broadcast( mCurrentCallEvent );
-
- broadcast( new DecoderStateEvent( this,
- Event.START, State.DATA ) );
- }
- else
- {
- broadcast( new DecoderStateEvent( this,
- Event.CONTINUATION, State.DATA ) );
- }
- }
- else
- {
- processCallEndMessage( ltr );
- }
- break;
- case CA_STRT:
- if( mChannelNumber == 0 )
- {
- mChannelNumber = ltr.getChannel();
- broadcast( ChangedAttribute.CHANNEL_NUMBER );
- }
+ broadcast(mCurrentCallEvent);
+
+ broadcast(new DecoderStateEvent(this,
+ Event.START, State.DATA));
+ }
+ else
+ {
+ broadcast(new DecoderStateEvent(this,
+ Event.CONTINUATION, State.DATA));
+ }
+ }
+ else
+ {
+ processCallEndMessage(ltr);
+ }
+ break;
+ case CA_STRT:
+ if (mChannelNumber == 0)
+ {
+ mChannelNumber = ltr.getChannel();
+ broadcast(ChangedAttribute.CHANNEL_NUMBER);
+ }
/* If the call event channel matches our current channel
- * then it's a call, otherwise it's a call detect. */
- if( ltr.getChannel() == mChannelNumber )
- {
- processCallMessage( ltr );
- }
- else
- {
- processCallDetectMessage( ltr );
- }
-
- break;
- case SY_IDLE:
- if( mChannelNumber != ltr.getChannel() )
- {
- mChannelNumber = ltr.getChannel();
- broadcast( ChangedAttribute.CHANNEL_NUMBER );
- }
- break;
- case MA_CHNH:
- break;
- case MA_CHNL:
- break;
- case FQ_RXHI:
- case FQ_RXLO:
- if( ltr.getFrequency() > 0 )
- {
- mReceiveFrequencies.put( ltr.getHomeRepeater(),
- ltr.getFrequency() );
- }
- break;
- case FQ_TXHI:
- case FQ_TXLO:
- if( ltr.getFrequency() > 0 )
- {
- mTransmitFrequencies.put( ltr.getHomeRepeater(),
- ltr.getFrequency() );
- }
- break;
- case ID_NBOR:
- String neighborID = ltr.getNeighborID();
-
- if( neighborID != null )
- {
- mNeighborIDs.add( neighborID );
- }
- break;
- case ID_UNIQ:
- state = State.DATA;
-
- int uniqueID = ltr.getRadioUniqueID();
-
- if( uniqueID != LTRNetOSWMessage.INT_NULL_VALUE )
- {
- mUniqueIDs.add( uniqueID );
- }
-
- if( getCurrentLTRCallEvent() == null )
- {
- mCurrentCallEvent = new LTRCallEvent.Builder(
- DecoderType.LTR_NET, CallEventType.REGISTER )
- .aliasList( getAliasList() )
- .channel( String.valueOf( mChannelNumber ) )
- .frequency( mFrequency )
- .from( String.valueOf( uniqueID ) )
- .build();
- }
- else
- {
- mCurrentCallEvent.setFromID(
- String.valueOf( ltr.getRadioUniqueID() ) );
- mCurrentCallEvent.setDetails( "Unique ID" );
-
- broadcast( mCurrentCallEvent );
- }
+ * then it's a call, otherwise it's a call detect. */
+ if (ltr.getChannel() == mChannelNumber)
+ {
+ processCallMessage(ltr);
+ }
+ else
+ {
+ processCallDetectMessage(ltr);
+ }
- mTalkgroup = String.valueOf( uniqueID );
- broadcast( ChangedAttribute.TO_TALKGROUP );
-
- mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
- broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
- break;
- case ID_SITE:
- String siteID = ltr.getSiteID();
-
- if( siteID != null )
- {
- mSiteIDs.add( siteID );
- }
- break;
- default:
- break;
- }
- }
- else if( message instanceof LTRNetISWMessage )
- {
- LTRNetISWMessage ltr = ((LTRNetISWMessage)message);
-
- switch( ltr.getMessageType() )
- {
- case CA_STRT:
- processCallMessage( ltr );
- break;
- case CA_ENDD:
- processCallEndMessage( ltr );
- break;
- case ID_ESNH:
- case ID_ESNL:
- state = State.DATA;
-
- String esn = ltr.getESN();
-
- if( !esn.contains( "xxxx" ) )
- {
- mESNs.add( ltr.getESN() );
- }
-
- mDescription = "ESN";
- broadcast( ChangedAttribute.DESCRIPTION );
-
- mTalkgroup = ltr.getESN();
- broadcast( ChangedAttribute.TO_TALKGROUP );
+ break;
+ case SY_IDLE:
+ if (mChannelNumber != ltr.getChannel())
+ {
+ mChannelNumber = ltr.getChannel();
+ broadcast(ChangedAttribute.CHANNEL_NUMBER);
+ }
+ break;
+ case MA_CHNH:
+ break;
+ case MA_CHNL:
+ break;
+ case FQ_RXHI:
+ case FQ_RXLO:
+ if (ltr.getFrequency() > 0)
+ {
+ mReceiveFrequencies.put(ltr.getHomeRepeater(),
+ ltr.getFrequency());
+ }
+ break;
+ case FQ_TXHI:
+ case FQ_TXLO:
+ if (ltr.getFrequency() > 0)
+ {
+ mTransmitFrequencies.put(ltr.getHomeRepeater(),
+ ltr.getFrequency());
+ }
+ break;
+ case ID_NBOR:
+ String neighborID = ltr.getNeighborID();
- mTalkgroupAlias = ltr.getESNAlias();
- broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
+ if (neighborID != null)
+ {
+ mNeighborIDs.add(neighborID);
+ }
+ break;
+ case ID_UNIQ:
+ state = State.DATA;
- broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
+ int uniqueID = ltr.getRadioUniqueID();
- if( mCurrentCallEvent == null )
- {
- mCurrentCallEvent = new LTRCallEvent.Builder(
- DecoderType.LTR_NET, CallEventType.REGISTER_ESN )
- .aliasList( getAliasList() )
- .details( "ESN:" + ltr.getESN() )
- .frequency( mFrequency )
- .from( ltr.getESN() )
- .build();
-
- broadcast( mCurrentCallEvent );
- }
-
- break;
- case ID_UNIQ:
- state = State.DATA;
-
- int uniqueid = ltr.getRadioUniqueID();
-
- if( uniqueid != LTRNetISWMessage.INT_NULL_VALUE )
- {
- mUniqueIDs.add( uniqueid );
-
- mDescription = "REGISTER UID";
- broadcast( ChangedAttribute.DESCRIPTION );
+ if (uniqueID != LTRNetOSWMessage.INT_NULL_VALUE)
+ {
+ mUniqueIDs.add(uniqueID);
+ }
- mTalkgroup = String.valueOf( uniqueid );
- broadcast( ChangedAttribute.TO_TALKGROUP );
+ if (getCurrentLTRCallEvent() == null)
+ {
+ mCurrentCallEvent = new LTRCallEvent.Builder(
+ DecoderType.LTR_NET, CallEventType.REGISTER)
+ .aliasList(getAliasList())
+ .channel(String.valueOf(mChannelNumber))
+ .frequency(mFrequency)
+ .from(String.valueOf(uniqueID))
+ .build();
+ }
+ else
+ {
+ mCurrentCallEvent.setFromID(
+ String.valueOf(ltr.getRadioUniqueID()));
+ mCurrentCallEvent.setDetails("Unique ID");
- mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
- broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
-
- if( getCurrentLTRCallEvent() == null )
- {
- mCurrentCallEvent = new LTRCallEvent.Builder(
- DecoderType.LTR_NET, CallEventType.REGISTER )
- .aliasList( getAliasList() )
- .channel( String.valueOf( mChannelNumber ) )
- .frequency( mFrequency )
- .from( String.valueOf( uniqueid ) )
- .build();
- }
- else
- {
- mCurrentCallEvent.setFromID(
- String.valueOf( ltr.getRadioUniqueID() ) );
- mCurrentCallEvent.setDetails( "Unique ID" );
-
- broadcast( mCurrentCallEvent );
- }
- }
- break;
- default:
- break;
- }
- }
- }
+ broadcast(mCurrentCallEvent);
+ }
+
+ mTalkgroup = String.valueOf(uniqueID);
+ broadcast(ChangedAttribute.TO_TALKGROUP);
+
+ mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
+ broadcast(ChangedAttribute.TO_TALKGROUP_ALIAS);
+ break;
+ case ID_SITE:
+ String siteID = ltr.getSiteID();
+
+ if (siteID != null)
+ {
+ mSiteIDs.add(siteID);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else if (message instanceof LTRNetISWMessage)
+ {
+ LTRNetISWMessage ltr = ((LTRNetISWMessage) message);
+
+ switch (ltr.getMessageType())
+ {
+ case CA_STRT:
+ processCallMessage(ltr);
+ break;
+ case CA_ENDD:
+ processCallEndMessage(ltr);
+ break;
+ case ID_ESNH:
+ case ID_ESNL:
+ state = State.DATA;
+
+ String esn = ltr.getESN();
+
+ if (!esn.contains("xxxx"))
+ {
+ mESNs.add(ltr.getESN());
+ }
+
+ mDescription = "ESN";
+ broadcast(ChangedAttribute.DESCRIPTION);
+
+ mTalkgroup = ltr.getESN();
+ broadcast(ChangedAttribute.TO_TALKGROUP);
+
+ mTalkgroupAlias = ltr.getESNAlias();
+ broadcast(ChangedAttribute.TO_TALKGROUP_ALIAS);
+
+ broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA));
+
+ if (mCurrentCallEvent == null)
+ {
+ mCurrentCallEvent = new LTRCallEvent.Builder(
+ DecoderType.LTR_NET, CallEventType.REGISTER_ESN)
+ .aliasList(getAliasList())
+ .details("ESN:" + ltr.getESN())
+ .frequency(mFrequency)
+ .from(ltr.getESN())
+ .build();
+
+ broadcast(mCurrentCallEvent);
+ }
+
+ break;
+ case ID_UNIQ:
+ state = State.DATA;
+
+ int uniqueid = ltr.getRadioUniqueID();
+
+ if (uniqueid != LTRNetISWMessage.INT_NULL_VALUE)
+ {
+ mUniqueIDs.add(uniqueid);
+
+ mDescription = "REGISTER UID";
+ broadcast(ChangedAttribute.DESCRIPTION);
+
+ mTalkgroup = String.valueOf(uniqueid);
+ broadcast(ChangedAttribute.TO_TALKGROUP);
+
+ mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
+ broadcast(ChangedAttribute.TO_TALKGROUP_ALIAS);
+
+ if (getCurrentLTRCallEvent() == null)
+ {
+ mCurrentCallEvent = new LTRCallEvent.Builder(
+ DecoderType.LTR_NET, CallEventType.REGISTER)
+ .aliasList(getAliasList())
+ .channel(String.valueOf(mChannelNumber))
+ .frequency(mFrequency)
+ .from(String.valueOf(uniqueid))
+ .build();
+ }
+ else
+ {
+ mCurrentCallEvent.setFromID(
+ String.valueOf(ltr.getRadioUniqueID()));
+ mCurrentCallEvent.setDetails("Unique ID");
+
+ broadcast(mCurrentCallEvent);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
}
-
- public LTRCallEvent getCurrentLTRCallEvent()
- {
- if( mCurrentCallEvent != null )
- {
- return (LTRCallEvent)mCurrentCallEvent;
- }
-
- return null;
- }
-
- @Override
+
+ public LTRCallEvent getCurrentLTRCallEvent()
+ {
+ if (mCurrentCallEvent != null)
+ {
+ return (LTRCallEvent) mCurrentCallEvent;
+ }
+
+ return null;
+ }
+
+ @Override
public String getActivitySummary()
{
- StringBuilder sb = new StringBuilder();
+ StringBuilder sb = new StringBuilder();
- sb.append( "Activity Summary\n" );
- sb.append( "Decoder:\tLTR-Net\n\n" );
+ sb.append("Activity Summary\n");
+ sb.append("Decoder:\tLTR-Net\n\n");
- if( mSiteIDs.isEmpty() )
- {
- sb.append( "Site:\tUnknown\n" );
- }
- else
- {
- Iterator it = mSiteIDs.iterator();
-
- while( it.hasNext() )
- {
- sb.append( "Site:\t" );
-
- String siteID = it.next();
+ if (mSiteIDs.isEmpty())
+ {
+ sb.append("Site:\tUnknown\n");
+ }
+ else
+ {
+ Iterator it = mSiteIDs.iterator();
- sb.append( siteID );
-
- if( hasAliasList() )
- {
- Alias siteAlias = getAliasList().getSiteID( String.valueOf( siteID ) );
+ while (it.hasNext())
+ {
+ sb.append("Site:\t");
- if( siteAlias != null )
- {
- sb.append( " " );
- sb.append( siteAlias.getName() );
- }
- }
+ String siteID = it.next();
- sb.append( "\n" );
- }
- }
-
- sb.append( "\nLCNs (transmit | receive)\n" );
-
- if( mReceiveFrequencies.isEmpty() && mTransmitFrequencies.isEmpty() )
- {
- sb.append( " None\n" );
- }
- else
- {
- for( int x = 1; x < 21; x++ )
- {
- long rcv = 0;
-
- if( mReceiveFrequencies.containsKey( x ) )
- {
- rcv = mReceiveFrequencies.get( x );
- }
-
- long xmt = 0;
-
- if( mTransmitFrequencies.containsKey( x ) )
- {
- xmt = mTransmitFrequencies.get( x );
- }
+ sb.append(siteID);
- if( rcv > 0 || xmt > 0 )
- {
- if( x < 10 )
- {
- sb.append( " " );
- }
+ if (hasAliasList())
+ {
+ Alias siteAlias = getAliasList().getSiteID(String.valueOf(siteID));
- sb.append( x );
- sb.append( ": " );
+ if (siteAlias != null)
+ {
+ sb.append(" ");
+ sb.append(siteAlias.getName());
+ }
+ }
- if( xmt == 0 )
- {
- sb.append( "---.-----" );
- }
- else
- {
- sb.append( mDecimalFormatter.format( (double)xmt/1E6d ) );
- }
- sb.append( " | " );
-
- if( rcv == 0 )
- {
- sb.append( "---.-----" );
- }
- else
- {
- sb.append( mDecimalFormatter.format( (double)rcv/1E6d ) );
- }
-
- if( x == mChannelNumber )
- {
- sb.append( " **" );
- }
-
- sb.append( "\n" );
- }
- }
- }
-
- sb.append( "\nTalkgroups\n" );
-
- if( mTalkgroups.isEmpty() )
- {
- sb.append( " None\n" );
- }
- else
- {
- Iterator it = mTalkgroups.iterator();
-
- while( it.hasNext() )
- {
- String tgid = it.next();
-
- sb.append( " " );
- sb.append( tgid );
- sb.append( " " );
-
- if( hasAliasList() )
- {
- Alias alias = getAliasList().getTalkgroupAlias( tgid );
-
- if( alias != null )
- {
- sb.append( alias.getName() );
- }
- }
-
- sb.append( "\n" );
- }
- }
-
- sb.append( "\nRadio Unique IDs\n" );
-
- if( mUniqueIDs.isEmpty() )
- {
- sb.append( " None\n" );
- }
- else
- {
- Iterator it = mUniqueIDs.iterator();
-
- while( it.hasNext() )
- {
- int uid = it.next();
-
- sb.append( " " );
- sb.append( uid );
- sb.append( " " );
-
- if( hasAliasList() )
- {
- Alias alias = getAliasList().getUniqueID( uid );
-
- if( alias != null )
- {
- sb.append( alias.getName() );
- }
- }
-
- sb.append( "\n" );
- }
- }
+ sb.append("\n");
+ }
+ }
- sb.append( "\nESNs\n" );
-
- if( mESNs.isEmpty() )
- {
- sb.append( " None\n" );
- }
- else
- {
- Iterator it = mESNs.iterator();
-
- while( it.hasNext() )
- {
- String esn = it.next();
-
- sb.append( " " );
- sb.append( esn );
- sb.append( " " );
-
- if( hasAliasList() )
- {
- Alias alias = getAliasList().getESNAlias( esn );
-
- if( alias != null )
- {
- sb.append( alias.getName() );
- }
- }
-
- sb.append( "\n" );
- }
- }
-
- sb.append( "\nNeighbor Sites\n" );
-
- if( mNeighborIDs.isEmpty() )
- {
- sb.append( " None\n" );
- }
- else
- {
- Iterator it = mNeighborIDs.iterator();
-
- while( it.hasNext() )
- {
- String neighbor = it.next();
-
- sb.append( " " );
- sb.append( neighbor );
- sb.append( " " );
-
- if( hasAliasList() )
- {
- Alias alias = getAliasList().getSiteID( String.valueOf( neighbor ) );
-
- if( alias != null )
- {
- sb.append( alias.getName() );
- }
- }
-
- sb.append( "\n" );
- }
- }
-
- return sb.toString();
+ sb.append("\nLCNs (transmit | receive)\n");
+
+ if (mReceiveFrequencies.isEmpty() && mTransmitFrequencies.isEmpty())
+ {
+ sb.append(" None\n");
+ }
+ else
+ {
+ for (int x = 1; x < 21; x++)
+ {
+ long rcv = 0;
+
+ if (mReceiveFrequencies.containsKey(x))
+ {
+ rcv = mReceiveFrequencies.get(x);
+ }
+
+ long xmt = 0;
+
+ if (mTransmitFrequencies.containsKey(x))
+ {
+ xmt = mTransmitFrequencies.get(x);
+ }
+
+ if (rcv > 0 || xmt > 0)
+ {
+ if (x < 10)
+ {
+ sb.append(" ");
+ }
+
+ sb.append(x);
+ sb.append(": ");
+
+ if (xmt == 0)
+ {
+ sb.append("---.-----");
+ }
+ else
+ {
+ sb.append(mDecimalFormatter.format((double) xmt / 1E6d));
+ }
+ sb.append(" | ");
+
+ if (rcv == 0)
+ {
+ sb.append("---.-----");
+ }
+ else
+ {
+ sb.append(mDecimalFormatter.format((double) rcv / 1E6d));
+ }
+
+ if (x == mChannelNumber)
+ {
+ sb.append(" **");
+ }
+
+ sb.append("\n");
+ }
+ }
+ }
+
+ sb.append("\nTalkgroups\n");
+
+ if (mTalkgroups.isEmpty())
+ {
+ sb.append(" None\n");
+ }
+ else
+ {
+ Iterator it = mTalkgroups.iterator();
+
+ while (it.hasNext())
+ {
+ String tgid = it.next();
+
+ sb.append(" ");
+ sb.append(tgid);
+ sb.append(" ");
+
+ if (hasAliasList())
+ {
+ Alias alias = getAliasList().getTalkgroupAlias(tgid);
+
+ if (alias != null)
+ {
+ sb.append(alias.getName());
+ }
+ }
+
+ sb.append("\n");
+ }
+ }
+
+ sb.append("\nRadio Unique IDs\n");
+
+ if (mUniqueIDs.isEmpty())
+ {
+ sb.append(" None\n");
+ }
+ else
+ {
+ Iterator it = mUniqueIDs.iterator();
+
+ while (it.hasNext())
+ {
+ int uid = it.next();
+
+ sb.append(" ");
+ sb.append(uid);
+ sb.append(" ");
+
+ if (hasAliasList())
+ {
+ Alias alias = getAliasList().getUniqueID(uid);
+
+ if (alias != null)
+ {
+ sb.append(alias.getName());
+ }
+ }
+
+ sb.append("\n");
+ }
+ }
+
+ sb.append("\nESNs\n");
+
+ if (mESNs.isEmpty())
+ {
+ sb.append(" None\n");
+ }
+ else
+ {
+ Iterator it = mESNs.iterator();
+
+ while (it.hasNext())
+ {
+ String esn = it.next();
+
+ sb.append(" ");
+ sb.append(esn);
+ sb.append(" ");
+
+ if (hasAliasList())
+ {
+ Alias alias = getAliasList().getESNAlias(esn);
+
+ if (alias != null)
+ {
+ sb.append(alias.getName());
+ }
+ }
+
+ sb.append("\n");
+ }
+ }
+
+ sb.append("\nNeighbor Sites\n");
+
+ if (mNeighborIDs.isEmpty())
+ {
+ sb.append(" None\n");
+ }
+ else
+ {
+ Iterator it = mNeighborIDs.iterator();
+
+ while (it.hasNext())
+ {
+ String neighbor = it.next();
+
+ sb.append(" ");
+ sb.append(neighbor);
+ sb.append(" ");
+
+ if (hasAliasList())
+ {
+ Alias alias = getAliasList().getSiteID(String.valueOf(neighbor));
+
+ if (alias != null)
+ {
+ sb.append(alias.getName());
+ }
+ }
+
+ sb.append("\n");
+ }
+ }
+
+ return sb.toString();
}
- /**
- * Call Detect - current channel messages indicate a call on another channel
- */
- private void processCallDetectMessage( LTRNetMessage message )
- {
- if( !mActiveCalls.containsKey( message.getChannel() ) ||
- !mActiveCalls.get( message.getChannel() ).contentEquals( message.getTalkgroupID() ) )
- {
- mActiveCalls.put( message.getChannel(), message.getTalkgroupID() );
-
- int channel = message.getChannel();
+ /**
+ * Call Detect - current channel messages indicate a call on another channel
+ */
+ private void processCallDetectMessage(LTRNetMessage message)
+ {
+ if (!mActiveCalls.containsKey(message.getChannel()) ||
+ !mActiveCalls.get(message.getChannel()).contentEquals(message.getTalkgroupID()))
+ {
+ mActiveCalls.put(message.getChannel(), message.getTalkgroupID());
- long frequency = 0;
-
- if( mTransmitFrequencies.containsKey( channel ) )
- {
- frequency = mTransmitFrequencies.get( channel );
- }
+ int channel = message.getChannel();
- broadcast( new LTRCallEvent.Builder( DecoderType.LTR_NET, CallEventType.CALL_DETECT )
- .aliasList( getAliasList() )
- .channel( String.valueOf( message.getChannel() ) )
- .frequency( frequency )
- .to( message.getTalkgroupID() )
- .build() );
- }
-
- broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.IDLE ) );
- }
-
- /**
- * Indicates if the talkgroup is different than the talkgroup specified in
- * the current call event
- */
- private boolean isDifferentTalkgroup( String talkgroup )
- {
- return talkgroup != null &&
- mCurrentCallEvent != null &&
- mCurrentCallEvent.getToID() != null &&
- !mCurrentCallEvent.getToID().contentEquals( talkgroup );
- }
-
- private void processCallMessage( LTRNetMessage message )
- {
- int group = message.getGroup();
+ long frequency = 0;
+
+ if (mTransmitFrequencies.containsKey(channel))
+ {
+ frequency = mTransmitFrequencies.get(channel);
+ }
+
+ broadcast(new LTRCallEvent.Builder(DecoderType.LTR_NET, CallEventType.CALL_DETECT)
+ .aliasList(getAliasList())
+ .channel(String.valueOf(message.getChannel()))
+ .frequency(frequency)
+ .to(message.getTalkgroupID())
+ .build());
+ }
+
+ broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
+ }
+
+ /**
+ * Indicates if the talkgroup is different than the talkgroup specified in
+ * the current call event
+ */
+ private boolean isDifferentTalkgroup(String talkgroup)
+ {
+ return talkgroup != null &&
+ mCurrentCallEvent != null &&
+ mCurrentCallEvent.getToID() != null &&
+ !mCurrentCallEvent.getToID().contentEquals(talkgroup);
+ }
+
+ private boolean isValidTalkgroup(String talkgroup)
+ {
+ return talkgroup != null && !talkgroup.matches("0-00-000");
+ }
+
+ private void processCallMessage(LTRNetMessage message)
+ {
+ int group = message.getGroup();
/* Process call registration */
- if( group == 253 )
- {
- broadcast( new DecoderStateEvent( this, Event.START, State.DATA ) );
- mDescription = "REGISTER";
- broadcast( ChangedAttribute.DESCRIPTION );
- }
- /* Process call */
- else
- {
- String talkgroup = message.getTalkgroupID();
+ if (group == 253)
+ {
+ broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
+ mDescription = "REGISTER";
+ broadcast(ChangedAttribute.DESCRIPTION);
+ }
+ /* Process call */
+ else
+ {
+ String talkgroup = message.getTalkgroupID();
+
+ if (isValidTalkgroup(talkgroup))
+ {
+ final LTRCallEvent current = getCurrentLTRCallEvent();
- final LTRCallEvent current = getCurrentLTRCallEvent();
-
/* If this is a new call or the talkgroup is different from the current
* call, create a new call event */
- if( current == null || isDifferentTalkgroup( talkgroup ) )
- {
+ if (current == null || isDifferentTalkgroup(talkgroup))
+ {
/* Invalidate the current call */
- if( current != null )
- {
- current.setValid( false );
- broadcast( current );
- }
-
- mTalkgroup = message.getTalkgroupID();
- broadcast( ChangedAttribute.TO_TALKGROUP );
+ if (current != null)
+ {
+ current.setValid(false);
+ broadcast(current);
+ }
+
+ mTalkgroup = message.getTalkgroupID();
+ broadcast(ChangedAttribute.TO_TALKGROUP);
/* A talkgroup must be seen at least once before it will be added
* to the mTalkgroups list that is used in the activity summary,
* so that we don't pollute the summary with one-off error talkgroups */
- if( mTalkgroupsFirstHeard.contains( mTalkgroup ) )
- {
- mTalkgroups.add( mTalkgroup );
- }
- else
- {
- mTalkgroupsFirstHeard.add( mTalkgroup );
- }
-
- mTalkgroupAlias = message.getTalkgroupIDAlias();
- broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
+ if (mTalkgroupsFirstHeard.contains(mTalkgroup))
+ {
+ mTalkgroups.add(mTalkgroup);
+ }
+ else
+ {
+ mTalkgroupsFirstHeard.add(mTalkgroup);
+ }
- CallEvent callEvent = new LTRCallEvent.Builder( DecoderType.LTR_NET, CallEventType.CALL )
- .aliasList( getAliasList() )
- .channel( String.valueOf( message.getChannel() ) )
- .frequency( mFrequency )
- .to( message.getTalkgroupID() )
- .build();
-
- broadcast( callEvent );
-
- mCurrentCallEvent = callEvent;
-
- broadcast( new Metadata( MetadataType.TO, mTalkgroup, mTalkgroupAlias, true ) );
+ mTalkgroupAlias = message.getTalkgroupIDAlias();
+ broadcast(ChangedAttribute.TO_TALKGROUP_ALIAS);
- broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
- }
+ CallEvent callEvent = new LTRCallEvent.Builder(DecoderType.LTR_NET, CallEventType.CALL)
+ .aliasList(getAliasList())
+ .channel(String.valueOf(message.getChannel()))
+ .frequency(mFrequency)
+ .to(message.getTalkgroupID())
+ .build();
- broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
- }
- }
-
- private void processCallEndMessage( LTRNetMessage message )
- {
- if( mCurrentCallEvent != null &&
- mCurrentCallEvent.getCallEventType() == CallEventType.CALL )
- {
- mCurrentCallEvent.setEnd( System.currentTimeMillis() );
- broadcast( mCurrentCallEvent );
- }
+ broadcast(callEvent);
- mCurrentCallEvent = null;
-
- broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
- }
+ mCurrentCallEvent = callEvent;
- /**
- * Performs a full reset
- */
- public void reset()
- {
- mChannelNumber = 0;
-
- mActiveCalls.clear();
- mESNs.clear();
- mNeighborIDs.clear();
- mReceiveFrequencies.clear();
- mSiteIDs.clear();
- mTalkgroups.clear();
- mTransmitFrequencies.clear();
- mUniqueIDs.clear();
-
- resetState();
- }
+ broadcast(new Metadata(MetadataType.TO, mTalkgroup, mTalkgroupAlias, true));
- /**
- * Resets the decoder state after a call or other decode event
- */
- private void resetState()
- {
- if( mCurrentCallEvent != null && mCurrentCallEvent
- .getCallEventType() == CallEventType.CALL )
- {
- mCurrentCallEvent.end();
- broadcast( mCurrentCallEvent );
- }
-
- mCurrentCallEvent = null;
-
- mTalkgroup = null;
- broadcast( ChangedAttribute.TO_TALKGROUP );
+ broadcast(new DecoderStateEvent(this, Event.START, State.CALL));
+ }
- mTalkgroupAlias = null;
- broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
+ broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
+ }
+ }
+ }
- mDescription = null;
- broadcast( ChangedAttribute.DESCRIPTION );
- }
+ private void processCallEndMessage(LTRNetMessage message)
+ {
+ if (mCurrentCallEvent != null &&
+ mCurrentCallEvent.getCallEventType() == CallEventType.CALL)
+ {
+ mCurrentCallEvent.setEnd(System.currentTimeMillis());
+ broadcast(mCurrentCallEvent);
+ }
- public String getToTalkgroup()
- {
- return mTalkgroup;
- }
-
- public Alias getToTalkgroupAlias()
- {
- return mTalkgroupAlias;
- }
-
- public String getDescription()
- {
- return mDescription;
- }
-
- public boolean hasChannelNumber()
- {
- return mChannelNumber != 0;
- }
-
- public int getChannelNumber()
- {
- return mChannelNumber;
- }
+ mCurrentCallEvent = null;
- @Override
- public void init()
- {
+ broadcast(new DecoderStateEvent(this, Event.END, State.FADE));
+ }
+
+ /**
+ * Performs a full reset
+ */
+ public void reset()
+ {
+ mChannelNumber = 0;
+
+ mActiveCalls.clear();
+ mESNs.clear();
+ mNeighborIDs.clear();
+ mReceiveFrequencies.clear();
+ mSiteIDs.clear();
+ mTalkgroups.clear();
+ mTransmitFrequencies.clear();
+ mUniqueIDs.clear();
+
+ resetState();
+ }
+
+ /**
+ * Resets the decoder state after a call or other decode event
+ */
+ private void resetState()
+ {
+ if (mCurrentCallEvent != null && mCurrentCallEvent
+ .getCallEventType() == CallEventType.CALL)
+ {
+ mCurrentCallEvent.end();
+ broadcast(mCurrentCallEvent);
+ }
+
+ mCurrentCallEvent = null;
+
+ mTalkgroup = null;
+ broadcast(ChangedAttribute.TO_TALKGROUP);
+
+ mTalkgroupAlias = null;
+ broadcast(ChangedAttribute.TO_TALKGROUP_ALIAS);
+
+ mDescription = null;
+ broadcast(ChangedAttribute.DESCRIPTION);
+ }
+
+ public String getToTalkgroup()
+ {
+ return mTalkgroup;
+ }
+
+ public Alias getToTalkgroupAlias()
+ {
+ return mTalkgroupAlias;
+ }
+
+ public String getDescription()
+ {
+ return mDescription;
+ }
+
+ public boolean hasChannelNumber()
+ {
+ return mChannelNumber != 0;
+ }
+
+ public int getChannelNumber()
+ {
+ return mChannelNumber;
+ }
+
+ @Override
+ public void init()
+ {
/* No initialization steps required */
- }
-
- @Override
- public void receiveDecoderStateEvent( DecoderStateEvent event )
- {
- switch( event.getEvent() )
- {
- case RESET:
- resetState();
- break;
- case SOURCE_FREQUENCY:
- mFrequency = event.getFrequency();
- break;
- default:
- break;
- }
- }
+ }
- @Override
- public void start( ScheduledExecutorService executor )
- {
- }
+ @Override
+ public void receiveDecoderStateEvent(DecoderStateEvent event)
+ {
+ switch (event.getEvent())
+ {
+ case RESET:
+ resetState();
+ break;
+ case SOURCE_FREQUENCY:
+ mFrequency = event.getFrequency();
+ break;
+ default:
+ break;
+ }
+ }
- @Override
- public void stop()
- {
- }
+ @Override
+ public void start(ScheduledExecutorService executor)
+ {
+ }
+
+ @Override
+ public void stop()
+ {
+ }
}
diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java
index 8245b981..fbc28f00 100644
--- a/src/module/decode/p25/audio/P25AudioModule.java
+++ b/src/module/decode/p25/audio/P25AudioModule.java
@@ -25,244 +25,249 @@ import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener;
import dsp.gain.NonClippingGain;
-public class P25AudioModule extends Module implements Listener,
- IAudioPacketProvider, IMessageListener, IChannelEventListener,
- IMetadataListener, ISquelchStateListener
+public class P25AudioModule extends Module implements Listener,
+ IAudioPacketProvider, IMessageListener, IChannelEventListener,
+ IMetadataListener, ISquelchStateListener
{
- private final static Logger mLog = LoggerFactory.getLogger( P25AudioModule.class );
+ private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class);
- private static final String IMBE_CODEC = "IMBE";
+ private static final String IMBE_CODEC = "IMBE";
- /* Provides a unique identifier for this audio module instance to use as a
- * source identifier for all audio packets */
- private static int UNIQUE_ID = 0;
- private int mSourceID;
- private static boolean mLibraryLoadStatusLogged = false;
- private boolean mCanConvertAudio = false;
- private AudioConverter mAudioConverter;
- private Listener mAudioPacketListener;
- private AudioMetadata mAudioMetadata;
- private ChannelEventListener mChannelEventListener = new ChannelEventListener();
- private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
- private NonClippingGain mGain = new NonClippingGain( 5.0f, 0.95f );
+ /* Provides a unique identifier for this audio module instance to use as a
+ * source identifier for all audio packets */
+ private static int UNIQUE_ID = 0;
+ private int mSourceID;
+ private static boolean mLibraryLoadStatusLogged = false;
+ private boolean mCanConvertAudio = false;
+ private AudioConverter mAudioConverter;
+ private Listener mAudioPacketListener;
+ private AudioMetadata mAudioMetadata;
+ private ChannelEventListener mChannelEventListener = new ChannelEventListener();
+ private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
+ private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
- public P25AudioModule( boolean record )
- {
- mSourceID = ++UNIQUE_ID;
- mAudioMetadata = new AudioMetadata( mSourceID, record );
-
- loadConverter();
- }
-
- @Override
- public Listener getMessageListener()
- {
- return this;
- }
+ public P25AudioModule(boolean record)
+ {
+ mSourceID = ++UNIQUE_ID;
+ mAudioMetadata = new AudioMetadata(mSourceID, record);
- @Override
- public Listener getSquelchStateListener()
- {
- return mSquelchStateListener;
- }
+ loadConverter();
+ }
- @Override
- public void dispose()
- {
- mAudioConverter = null;
- }
+ @Override
+ public Listener getMessageListener()
+ {
+ return this;
+ }
- @Override
- public void reset()
- {
- mAudioMetadata.reset();
- }
+ @Override
+ public Listener getSquelchStateListener()
+ {
+ return mSquelchStateListener;
+ }
- @Override
- public void start( ScheduledExecutorService executor )
- {
-
- }
+ @Override
+ public void dispose()
+ {
+ mAudioConverter = null;
+ }
- @Override
- public void stop()
- {
-
- }
+ @Override
+ public void reset()
+ {
+ mAudioMetadata.reset();
+ }
+
+ @Override
+ public void start(ScheduledExecutorService executor)
+ {
+
+ }
+
+ @Override
+ public void stop()
+ {
+
+ }
+
+ /**
+ * Primary processing method for p25 imbe audio frame messages. Each LDU
+ * audio message contains 9 imbe voice frames. Each frame is converted to
+ * audio when the JMBE library is loaded, processed by auto-gain, and
+ * broadcast to the audio packet listener.
+ */
+ public void receive(Message message)
+ {
+ if (mCanConvertAudio && mAudioPacketListener != null)
+ {
+ if (message instanceof LDUMessage)
+ {
+ LDUMessage ldu = (LDUMessage) message;
- /**
- * Primary processing method for p25 imbe audio frame messages. Each LDU
- * audio message contains 9 imbe voice frames. Each frame is converted to
- * audio when the JMBE library is loaded, processed by auto-gain, and
- * broadcast to the audio packet listener.
- */
- public void receive( Message message )
- {
- if( mCanConvertAudio && mAudioPacketListener != null )
- {
- if( message instanceof LDUMessage )
- {
- LDUMessage ldu = (LDUMessage)message;
-
/* Only process unencrypted audio frames */
- if( !( ldu.isValid() && ldu.isEncrypted() ) )
- {
- for( byte[] frame: ldu.getIMBEFrames() )
- {
- float[] audio = mAudioConverter.decode( frame );
+ if (!(ldu.isValid() && ldu.isEncrypted()))
+ {
+ for (byte[] frame : ldu.getIMBEFrames())
+ {
+ float[] audio = mAudioConverter.decode(frame);
- audio = mGain.apply( audio );
+ audio = mGain.apply(audio);
- mAudioPacketListener.receive(
- new AudioPacket( audio, mAudioMetadata.copyOf() ) );
- }
- }
- }
- }
- }
+ mAudioPacketListener.receive(
+ new AudioPacket(audio, mAudioMetadata.copyOf()));
+ }
+ }
+ }
+ }
+ }
- /**
- * Loads audio frame processing chain. Constructs an imbe targetdataline
- * to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
- * stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
- */
- private void loadConverter()
- {
- AudioConversionLibrary library = null;
-
- try
- {
- @SuppressWarnings( "rawtypes" )
- Class temp = Class.forName( "jmbe.JMBEAudioLibrary" );
-
- library = (AudioConversionLibrary)temp.newInstance();
-
- if( ( library.getMajorVersion() == 0 &&
- library.getMinorVersion() >= 3 &&
- library.getBuildVersion() >= 1 ) ||
- library.getMajorVersion() >= 1 )
- {
- mAudioConverter = library.getAudioConverter( IMBE_CODEC,
- AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
-
- if( mAudioConverter != null )
- {
- mCanConvertAudio = true;
-
- if( !mLibraryLoadStatusLogged )
- {
- mLog.info( "JMBE audio conversion library successfully loaded"
- + " - P25 audio will be available" );
-
- mLibraryLoadStatusLogged = true;
- }
- }
- else
- {
- if( !mLibraryLoadStatusLogged )
- {
- mLog.info( "JMBE audio conversion library NOT FOUND" );
- mLibraryLoadStatusLogged = true;
- }
- }
- }
- else
- {
- mLog.warn( "JMBE library version 0.3.1 or higher is required - found: " + library.getVersion() );
- }
- }
- catch ( ClassNotFoundException e1 )
- {
- if( !mLibraryLoadStatusLogged )
- {
- mLog.error( "Couldn't find/load JMBE audio conversion library" );
- mLibraryLoadStatusLogged = true;
- }
- }
- catch ( InstantiationException e1 )
- {
- if( !mLibraryLoadStatusLogged )
- {
- mLog.error( "Couldn't instantiate JMBE audio conversion library class" );
- mLibraryLoadStatusLogged = true;
- }
- }
- catch ( IllegalAccessException e1 )
- {
- if( !mLibraryLoadStatusLogged )
- {
- mLog.error( "Couldn't load JMBE audio conversion library due to "
- + "security restrictions" );
- mLibraryLoadStatusLogged = true;
- }
- }
- }
+ /**
+ * Loads audio frame processing chain. Constructs an imbe targetdataline
+ * to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
+ * stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
+ */
+ private void loadConverter()
+ {
+ AudioConversionLibrary library = null;
- @Override
- public void setAudioPacketListener( Listener listener )
- {
- mAudioPacketListener = listener;
- }
+ try
+ {
+ @SuppressWarnings("rawtypes")
+ Class temp = Class.forName("jmbe.JMBEAudioLibrary");
- @Override
- public void removeAudioPacketListener()
- {
- mAudioPacketListener = null;
- }
-
- @Override
- public Listener getMetadataListener()
- {
- return mAudioMetadata;
- }
+ library = (AudioConversionLibrary) temp.newInstance();
- @Override
- public Listener getChannelEventListener()
- {
- return mChannelEventListener;
- }
+ if ((library.getMajorVersion() == 0 &&
+ library.getMinorVersion() >= 3 &&
+ library.getBuildVersion() >= 3) ||
+ library.getMajorVersion() >= 1)
+ {
+// mAudioConverter = library.getAudioConverter( IMBE_CODEC,
+// AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
+ mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
- /**
- * Wrapper for channel event listener. Responds to channel state reset
- * events to remove/cleanup current audio metadata
- */
- public class ChannelEventListener implements Listener
- {
- @Override
- public void receive( ChannelEvent event )
- {
- switch( event.getEvent() )
- {
- case NOTIFICATION_STATE_RESET:
- mAudioMetadata.reset();
- break;
- case NOTIFICATION_SELECTION_CHANGE:
- mAudioMetadata.setSelected( event.getChannel().isSelected() );
- break;
- default:
- break;
- }
- }
- }
-
- /**
- * Wrapper for squelch state listener. Internally, the P25 audio module
- * doesn't have a squelch state. If there are IMBE audio frames, we have
- * audio. We use this listener to signal to the recorder manager that the
- * channel state is resetting and it should end the recording.
- */
- public class SquelchStateListener implements Listener
- {
- @Override
- public void receive( SquelchState state )
- {
- if( state == SquelchState.SQUELCH && mAudioPacketListener != null )
- {
- mAudioPacketListener.receive( new AudioPacket( AudioPacket.Type.END,
- mAudioMetadata.copyOf() ) );
+ if (mAudioConverter != null)
+ {
+ mCanConvertAudio = true;
- mAudioMetadata.reset();
- }
- }
- }
+ if (!mLibraryLoadStatusLogged)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("JMBE audio conversion library [");
+ sb.append(library.getVersion());
+ sb.append("] successfully loaded - P25 audio will be available");
+
+ mLog.info(sb.toString());
+
+ mLibraryLoadStatusLogged = true;
+ }
+ }
+ else
+ {
+ if (!mLibraryLoadStatusLogged)
+ {
+ mLog.info("JMBE audio conversion library NOT FOUND");
+ mLibraryLoadStatusLogged = true;
+ }
+ }
+ }
+ else
+ {
+ mLog.warn("JMBE library version 0.3.1 or higher is required - found: " + library.getVersion());
+ }
+ }
+ catch (ClassNotFoundException e1)
+ {
+ if (!mLibraryLoadStatusLogged)
+ {
+ mLog.error("Couldn't find/load JMBE audio conversion library");
+ mLibraryLoadStatusLogged = true;
+ }
+ }
+ catch (InstantiationException e1)
+ {
+ if (!mLibraryLoadStatusLogged)
+ {
+ mLog.error("Couldn't instantiate JMBE audio conversion library class");
+ mLibraryLoadStatusLogged = true;
+ }
+ }
+ catch (IllegalAccessException e1)
+ {
+ if (!mLibraryLoadStatusLogged)
+ {
+ mLog.error("Couldn't load JMBE audio conversion library due to "
+ + "security restrictions");
+ mLibraryLoadStatusLogged = true;
+ }
+ }
+ }
+
+ @Override
+ public void setAudioPacketListener(Listener listener)
+ {
+ mAudioPacketListener = listener;
+ }
+
+ @Override
+ public void removeAudioPacketListener()
+ {
+ mAudioPacketListener = null;
+ }
+
+ @Override
+ public Listener getMetadataListener()
+ {
+ return mAudioMetadata;
+ }
+
+ @Override
+ public Listener getChannelEventListener()
+ {
+ return mChannelEventListener;
+ }
+
+ /**
+ * Wrapper for channel event listener. Responds to channel state reset
+ * events to remove/cleanup current audio metadata
+ */
+ public class ChannelEventListener implements Listener
+ {
+ @Override
+ public void receive(ChannelEvent event)
+ {
+ switch (event.getEvent())
+ {
+ case NOTIFICATION_STATE_RESET:
+ mAudioMetadata.reset();
+ break;
+ case NOTIFICATION_SELECTION_CHANGE:
+ mAudioMetadata.setSelected(event.getChannel().isSelected());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Wrapper for squelch state listener. Internally, the P25 audio module
+ * doesn't have a squelch state. If there are IMBE audio frames, we have
+ * audio. We use this listener to signal to the recorder manager that the
+ * channel state is resetting and it should end the recording.
+ */
+ public class SquelchStateListener implements Listener
+ {
+ @Override
+ public void receive(SquelchState state)
+ {
+ if (state == SquelchState.SQUELCH && mAudioPacketListener != null)
+ {
+ mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END,
+ mAudioMetadata.copyOf()));
+
+ mAudioMetadata.reset();
+ }
+ }
+ }
}
diff --git a/src/record/RecorderManager.java b/src/record/RecorderManager.java
index 4cc30ab2..cd7c526f 100644
--- a/src/record/RecorderManager.java
+++ b/src/record/RecorderManager.java
@@ -37,7 +37,7 @@ public class RecorderManager implements Listener
{
private static final Logger mLog = LoggerFactory.getLogger( RecorderManager.class );
- public static final int AUDIO_SAMPLE_RATE = 48000;
+ public static final int AUDIO_SAMPLE_RATE = 8000;
private Map mRecorders = new HashMap<>();
diff --git a/src/sample/real/RealToRealBufferAssembler.java b/src/sample/real/RealToRealBufferAssembler.java
index 0efe048f..3a476af7 100644
--- a/src/sample/real/RealToRealBufferAssembler.java
+++ b/src/sample/real/RealToRealBufferAssembler.java
@@ -1,19 +1,19 @@
/*******************************************************************************
- * 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
+ * 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 sample.real;
@@ -24,51 +24,49 @@ import sample.Listener;
*/
public class RealToRealBufferAssembler implements RealSampleListener
{
- private int mBufferSize;
- private float[] mBuffer;
- private int mBufferPointer;
-
- private Listener mListener;
-
- public RealToRealBufferAssembler( int bufferSize )
- {
- mBufferSize = bufferSize;
- mBuffer = new float[ mBufferSize ];
- }
-
- public void dispose()
- {
- mListener = null;
- mBuffer = null;
- }
-
- public void reset()
- {
- mBuffer = new float[ mBufferSize ];
- mBufferPointer = 0;
- }
-
- @Override
- public void receive( float sample )
- {
- if( mBufferPointer >= mBufferSize )
- {
- if( mListener != null )
- {
- mListener.receive( new RealBuffer( mBuffer ) );
- }
+ private int mBufferSize;
+ private float[] mBuffer;
+ private int mBufferPointer;
- mBuffer = new float[ mBufferSize ];
- mBufferPointer = 0;
- }
+ private Listener mListener;
- mBuffer[ mBufferPointer ] = sample;
+ public RealToRealBufferAssembler(int bufferSize)
+ {
+ mBufferSize = bufferSize;
+ mBuffer = new float[mBufferSize];
+ }
- mBufferPointer++;
- }
+ public void dispose()
+ {
+ mListener = null;
+ mBuffer = null;
+ }
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
+ public void reset()
+ {
+ mBuffer = new float[mBufferSize];
+ mBufferPointer = 0;
+ }
+
+ @Override
+ public void receive(float sample)
+ {
+ mBuffer[mBufferPointer++] = sample;
+
+ if (mBufferPointer >= mBufferSize)
+ {
+ if (mListener != null)
+ {
+ mListener.receive(new RealBuffer(mBuffer));
+ }
+
+ mBuffer = new float[mBufferSize];
+ mBufferPointer = 0;
+ }
+ }
+
+ public void setListener(Listener listener)
+ {
+ mListener = listener;
+ }
}
diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java
index 140b6058..e102d4d7 100644
--- a/src/source/tuner/rtl/RTL2832TunerController.java
+++ b/src/source/tuner/rtl/RTL2832TunerController.java
@@ -1599,7 +1599,7 @@ public abstract class RTL2832TunerController extends TunerController
}
catch( Exception e )
{
- mLog.error( "error duing rtl2832 buffer dispatcher run", e );
+ mLog.error( "error during rtl2832 buffer dispatcher run", e );
}
}
}