diff --git a/src/main/java/io/github/dsheirer/audio/AudioModule.java b/src/main/java/io/github/dsheirer/audio/AudioModule.java
index 81f3451c..5836d4bc 100644
--- a/src/main/java/io/github/dsheirer/audio/AudioModule.java
+++ b/src/main/java/io/github/dsheirer/audio/AudioModule.java
@@ -1,19 +1,16 @@
/*******************************************************************************
- * 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.audio;
@@ -23,90 +20,76 @@ import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
+import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
-import io.github.dsheirer.dsp.filter.polyphase.PolyphaseFIRDecimatingFilter_RB;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
-import io.github.dsheirer.sample.real.RealBuffer;
+import io.github.dsheirer.sample.buffer.IReusableBufferListener;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * 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.
+ * 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.
+ * Incorporates audio squelch state listener to control if audio packets are broadcast or ignored.
+ *
+ * This class is designed to support 8 kHz sample rate demodulated audio.
*/
-public class AudioModule extends Module implements IAudioPacketProvider, IFilteredRealBufferListener,
- ISquelchStateListener, Listener
+public class AudioModule extends Module implements IAudioPacketProvider, IReusableBufferListener,
+ ISquelchStateListener, Listener
{
protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
- private static float[] mDecimationCoefficients;
+ private static float[] sHighPassFilterCoefficients;
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)
+ FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
+ .sampleRate(8000)
+ .stopBandCutoff(200)
+ .stopBandAmplitude(0.0)
+ .stopBandRipple(0.025)
+ .passBandStart(300)
+ .passBandAmplitude(1.0)
+ .passBandRipple(0.01)
.build();
-
- RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
-
try
{
- if(!designer.isValid())
- {
- throw new FilterDesignException("Couldn't design the audio decimation filter");
- }
+ RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
- mDecimationCoefficients = designer.getImpulseResponse();
+ if(designer.isValid())
+ {
+ sHighPassFilterCoefficients = designer.getImpulseResponse();
+ }
}
- catch(FilterDesignException e)
+ catch(FilterDesignException fde)
{
- mLog.debug("Error designing filter", e);
+ mLog.error("Filter design error", fde);
}
}
+ private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
private Listener mAudioPacketListener;
- private PolyphaseFIRDecimatingFilter_RB mAudioDecimationFilter;
private Metadata mMetadata;
+ /**
+ * Creates an Audio Module.
+ * @param metadata to use for audio packets produced by this audio module.
+ */
public AudioModule(Metadata metadata)
{
mMetadata = metadata;
-
- 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(), mMetadata.copyOf());
- mAudioPacketListener.receive(packet);
- }
- }
- });
}
@Override
public void dispose()
{
- mSquelchStateListener = null;
mAudioPacketListener = null;
+ mSquelchStateListener = null;
}
@Override
@@ -123,32 +106,13 @@ public class AudioModule extends Module implements IAudioPacketProvider, IFilter
@Override
public void stop()
{
- /* Issue an end audio packet in case a recorder is still rolling */
+ /* Issue an end-audio packet in case a recorder is still rolling */
if(mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
}
}
- /**
- * Processes demodulated audio samples into audio packets with current audio
- * metadata and sends to the registered listener
- */
- @Override
- public void receive(RealBuffer buffer)
- {
- if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH)
- {
- mAudioDecimationFilter.receive(buffer);
- }
- }
-
- @Override
- public Listener getFilteredRealBufferListener()
- {
- return this;
- }
-
@Override
public void setAudioPacketListener(Listener listener)
{
@@ -167,6 +131,29 @@ public class AudioModule extends Module implements IAudioPacketProvider, IFilter
return mSquelchStateListener;
}
+ @Override
+ public void receive(ReusableBuffer reusableBuffer)
+ {
+ if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH)
+ {
+ ReusableBuffer highPassFiltered = mHighPassFilter.filter(reusableBuffer);
+ AudioPacket packet = new AudioPacket(highPassFiltered.getSamplesCopy(), mMetadata.copyOf());
+ mAudioPacketListener.receive(packet);
+ highPassFiltered.decrementUserCount();
+ }
+ else
+ {
+ reusableBuffer.decrementUserCount();
+ }
+ }
+
+ @Override
+ public Listener getReusableBufferListener()
+ {
+ //Redirect received reusable buffers to the receive(buffer) method
+ return this;
+ }
+
/**
* Wrapper for squelch state listener
*/
diff --git a/src/main/java/io/github/dsheirer/audio/AudioModule2.java b/src/main/java/io/github/dsheirer/audio/AudioModule2.java
deleted file mode 100644
index c2f498ac..00000000
--- a/src/main/java/io/github/dsheirer/audio/AudioModule2.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*******************************************************************************
- * 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.audio;
-
-import io.github.dsheirer.audio.squelch.ISquelchStateListener;
-import io.github.dsheirer.audio.squelch.SquelchState;
-import io.github.dsheirer.channel.metadata.Metadata;
-import io.github.dsheirer.dsp.filter.resample.RealResampler;
-import io.github.dsheirer.module.Module;
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.buffer.ReusableBuffer;
-import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
-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;
-
-/**
- * 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 AudioModule2 extends Module implements IAudioPacketProvider, IFilteredRealBufferListener,
- ISquelchStateListener, ISourceEventListener, Listener
-{
- protected static final Logger mLog = LoggerFactory.getLogger(AudioModule2.class);
- private static final double OUTPUT_SAMPLE_RATE = 8000.0;
- private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
- private SquelchState mSquelchState = SquelchState.SQUELCH;
- private Listener mAudioPacketListener;
- private Listener mResampledBufferListener;
- private RealResampler mAudioResampler;
- private Metadata mMetadata;
-
- public AudioModule2(Metadata metadata)
- {
- mMetadata = metadata;
-
- mResampledBufferListener = new Listener()
- {
- @Override
- public void receive(ReusableBuffer reusableBuffer)
- {
- if(mAudioPacketListener != null)
- {
- AudioPacket packet = new AudioPacket(reusableBuffer.getSamplesCopy(), mMetadata.copyOf());
- reusableBuffer.decrementUserCount();
- mAudioPacketListener.receive(packet);
- }
- }
- };
-
- setSampleRate(25000.0);
- }
-
- /**
- * Sets the incoming sample rate
- */
- private void setSampleRate(double sampleRate)
- {
- mAudioResampler = new RealResampler(sampleRate, OUTPUT_SAMPLE_RATE);
- mAudioResampler.setListener(mResampledBufferListener);
- }
-
- @Override
- public void dispose()
- {
- mSquelchStateListener = null;
- mAudioPacketListener = null;
- }
-
- @Override
- public void reset()
- {
- }
-
- @Override
- public void start()
- {
- /* No start operations provided */
- }
-
- @Override
- public void stop()
- {
- /* Issue an end audio packet in case a recorder is still rolling */
- if(mAudioPacketListener != null)
- {
- mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
- }
- }
-
- /**
- * Processes demodulated audio samples into audio packets with current audio
- * metadata and sends to the registered listener
- */
- @Override
- public void receive(RealBuffer buffer)
- {
- if(mAudioResampler != null && mSquelchState == SquelchState.UNSQUELCH)
- {
- mAudioResampler.receive(buffer);
- }
- }
-
- @Override
- public Listener getFilteredRealBufferListener()
- {
- return this;
- }
-
- @Override
- public void setAudioPacketListener(Listener listener)
- {
- mAudioPacketListener = listener;
- }
-
- @Override
- public void removeAudioPacketListener()
- {
- mAudioPacketListener = null;
- }
-
- @Override
- public Listener getSourceEventListener()
- {
- return new Listener()
- {
- @Override
- public void receive(SourceEvent sourceEvent)
- {
- if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
- {
- setSampleRate(sourceEvent.getValue().doubleValue());
- }
- }
- };
- }
-
- @Override
- public Listener getSquelchStateListener()
- {
- return mSquelchStateListener;
- }
-
- /**
- * 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, mMetadata.copyOf()));
- }
-
- mSquelchState = state;
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/bits/ISyncDetectListener.java b/src/main/java/io/github/dsheirer/bits/ISyncDetectListener.java
deleted file mode 100644
index bbe93440..00000000
--- a/src/main/java/io/github/dsheirer/bits/ISyncDetectListener.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.github.dsheirer.bits;
-
-public interface ISyncDetectListener
-{
- public void syncDetected();
-}
diff --git a/src/main/java/io/github/dsheirer/bits/MessageFramer.java b/src/main/java/io/github/dsheirer/bits/MessageFramer.java
index 79596cae..591858c5 100644
--- a/src/main/java/io/github/dsheirer/bits/MessageFramer.java
+++ b/src/main/java/io/github/dsheirer/bits/MessageFramer.java
@@ -1,119 +1,110 @@
/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
+ * 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.bits;
-import io.github.dsheirer.dsp.symbol.SyncDetectListener;
+import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.dsp.symbol.SyncDetectProvider;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
/**
* MessageFramer - processes bitsets looking for a sync pattern within
* the bits, and then extracts the message, including the sync
* pattern, for a total bit length of messageLength.
- *
+ *
* Will extract multiple messages simultaneously, for each sync pattern that is
* encountered within the bitset bit stream.
*/
-public class MessageFramer implements Listener,
- SyncDetectProvider
+public class MessageFramer implements Listener, SyncDetectProvider
{
- private boolean[] mSyncPattern;
- private int mMessageLength;
- private SyncDetectListener mSyncDetectListener;
- private Broadcaster mBroadcaster =
- new Broadcaster();
- private ArrayList mMessageAssemblers =
- new ArrayList();
+ private boolean[] mSyncPattern;
+ private int mMessageLength;
+ private ISyncDetectListener mSyncDetectListener;
+ private Broadcaster mBroadcaster = new Broadcaster();
+ private List mMessageAssemblers = new ArrayList();
+ private List mCompletedMessageAssemblers = new ArrayList();
+ private SyncPatternMatcher mMatcher;
- private ArrayList mCompletedMessageAssemblers =
- new ArrayList();
-
- private BinaryMessage mPreviousBuffer = null;
-
- private SyncPatternMatcher mMatcher;
-
- public MessageFramer( boolean[] syncPattern, int messageLength )
+ public MessageFramer(boolean[] syncPattern, int messageLength)
{
mSyncPattern = syncPattern;
- mMatcher = new SyncPatternMatcher( syncPattern );
+ mMatcher = new SyncPatternMatcher(syncPattern);
mMessageLength = messageLength;
}
-
+
public void reset()
{
- for( MessageAssembler assembler: mMessageAssemblers )
- {
- assembler.dispose();
- }
-
+ for(MessageAssembler assembler : mMessageAssemblers)
+ {
+ assembler.dispose();
+ }
+
mMessageAssemblers.clear();
- }
-
+ }
+
public void dispose()
{
- mBroadcaster.dispose();
- mCompletedMessageAssemblers.clear();
- mMessageAssemblers.clear();
+ mBroadcaster.dispose();
+ mCompletedMessageAssemblers.clear();
+ mMessageAssemblers.clear();
}
@Override
- public void receive( Boolean bit )
+ public void receive(Boolean bit)
{
- mMatcher.receive( bit );
-
+ mMatcher.receive(bit);
+
Iterator it = mMessageAssemblers.iterator();
-
+
MessageAssembler assembler;
-
- while( it.hasNext() )
+
+ while(it.hasNext())
{
assembler = it.next();
/* Dispose and remove any completed assemblers */
- if( assembler.complete() )
+ if(assembler.complete())
{
- assembler.dispose();
- it.remove();
+ assembler.dispose();
+ it.remove();
}
/* Otherwise, send them the bit */
else
{
- assembler.receive( bit );
+ assembler.receive(bit);
}
}
-
+
/* Check for sync match and add new message assembler */
- if( mMatcher.matches() )
- {
- addMessageAssembler( new MessageAssembler( mMessageLength, mSyncPattern ) );
-
+ if(mMatcher.matches())
+ {
+ addMessageAssembler(new MessageAssembler(mMessageLength, mSyncPattern));
+
/* Notify any sync detect listener(s) */
- if( mSyncDetectListener != null )
+ if(mSyncDetectListener != null)
{
- mSyncDetectListener.syncDetected();
+ mSyncDetectListener.syncDetected();
}
- }
+ }
}
-
+
/**
* Causes all messages currently under assembly to be forcibly
* sent (ie flushed) to all registered message listeners, and
@@ -121,44 +112,44 @@ public class MessageFramer implements Listener,
*/
public void flush()
{
- for( MessageAssembler assembler: mMessageAssemblers )
+ for(MessageAssembler assembler : mMessageAssemblers)
{
assembler.flush();
}
}
-
- @Override
- public void setSyncDetectListener( SyncDetectListener listener )
+
+ @Override
+ public void setSyncDetectListener(ISyncDetectListener listener)
{
- mSyncDetectListener = listener;
+ mSyncDetectListener = listener;
}
-
+
/**
* Allow a message listener to register with this framer to receive
* all framed messages
*/
- public void addMessageListener( Listener listener )
+ public void addMessageListener(Listener listener)
{
- mBroadcaster.addListener( listener );
+ mBroadcaster.addListener(listener);
}
-
- public void removeMessageListener( Listener listener )
+
+ public void removeMessageListener(Listener listener)
{
- mBroadcaster.removeListener( listener );
+ mBroadcaster.removeListener(listener);
}
/*
* Adds a message assembler to receive bits from the bit stream
*/
- private void addMessageAssembler( MessageAssembler assembler )
+ private void addMessageAssembler(MessageAssembler assembler)
{
- mMessageAssemblers.add( assembler );
+ mMessageAssemblers.add(assembler);
}
- @SuppressWarnings( "unused" )
- private void removeMessageAssembler( MessageAssembler assembler )
+ @SuppressWarnings("unused")
+ private void removeMessageAssembler(MessageAssembler assembler)
{
- mMessageAssemblers.remove( assembler );
+ mMessageAssemblers.remove(assembler);
}
/**
@@ -166,7 +157,7 @@ public class MessageFramer implements Listener,
* identified sync pattern, and every bit thereafter. Once the accumulated
* bits equal the message length, the message is sent and the assembler
* flags itself as complete.
- *
+ *
* By design, multiple message assemblers can exist at the same time, each
* assembling different, overlapping potential messages
*/
@@ -174,46 +165,46 @@ public class MessageFramer implements Listener,
{
BinaryMessage mMessage;
boolean mComplete = false;
-
- MessageAssembler( int messageLength )
+
+ MessageAssembler(int messageLength)
{
- mMessage = new BinaryMessage( messageLength );
+ mMessage = new BinaryMessage(messageLength);
}
-
- MessageAssembler( int messageLength, boolean[] initialFill )
+
+ MessageAssembler(int messageLength, boolean[] initialFill)
{
- this( messageLength );
-
+ this(messageLength);
+
/* Pre-load the message with the sync pattern */
- for( int x = 0; x < initialFill.length; x++ )
+ for(int x = 0; x < initialFill.length; x++)
{
- receive( initialFill[ x ] );
+ receive(initialFill[x]);
}
}
-
+
public void dispose()
{
- mMessage = null;
+ mMessage = null;
}
@Override
/**
* Receives one bit at a time, and assembles them into a message
*/
- public void receive( Boolean bit )
+ public void receive(Boolean bit)
{
try
{
- mMessage.add( bit );
+ mMessage.add(bit);
}
- catch( BitSetFullException e )
+ catch(BitSetFullException e)
{
e.printStackTrace();
}
-
+
/* Once our message is complete (ie full), send it to all registered
* message listeners, and set complete flag so for auto-removal */
- if( mMessage.isFull() )
+ if(mMessage.isFull())
{
mComplete = true;
flush();
@@ -226,7 +217,7 @@ public class MessageFramer implements Listener,
*/
public void flush()
{
- mBroadcaster.receive( mMessage );
+ mBroadcaster.receive(mMessage);
mComplete = true;
}
diff --git a/src/main/java/io/github/dsheirer/bits/SoftSyncDetector.java b/src/main/java/io/github/dsheirer/bits/SoftSyncDetector.java
index 1a285f70..c2748f83 100644
--- a/src/main/java/io/github/dsheirer/bits/SoftSyncDetector.java
+++ b/src/main/java/io/github/dsheirer/bits/SoftSyncDetector.java
@@ -1,5 +1,7 @@
package io.github.dsheirer.bits;
+import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
+
public class SoftSyncDetector implements ISyncProcessor
{
private ISyncDetectListener mListener;
diff --git a/src/main/java/io/github/dsheirer/bits/SyncDetector.java b/src/main/java/io/github/dsheirer/bits/SyncDetector.java
index fd8e2091..23aa6be0 100644
--- a/src/main/java/io/github/dsheirer/bits/SyncDetector.java
+++ b/src/main/java/io/github/dsheirer/bits/SyncDetector.java
@@ -1,5 +1,7 @@
package io.github.dsheirer.bits;
+import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
+
public class SyncDetector implements ISyncProcessor
{
private long mPattern;
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/ComplexFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/ComplexFilter.java
deleted file mode 100644
index 59b9abb9..00000000
--- a/src/main/java/io/github/dsheirer/dsp/filter/ComplexFilter.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
-package io.github.dsheirer.dsp.filter;
-
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.complex.Complex;
-
-@Deprecated
-public abstract class ComplexFilter implements Listener
-{
- private Listener mListener;
-
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-
- public void dispose()
- {
- mListener = null;
- }
-
- public Listener getListener()
- {
- return mListener;
- }
-
- public boolean hasListener()
- {
- return mListener != null;
- }
-
- protected void send( Complex sample )
- {
- if( mListener != null )
- {
- mListener.receive( sample );
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java
index 3a3a2750..c9cb1cac 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java
@@ -22,7 +22,7 @@ import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcesso
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
+import io.github.dsheirer.sample.buffer.IReusableComplexBufferProvider;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.Source;
@@ -69,7 +69,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
private Broadcaster mSourceEventBroadcaster = new Broadcaster<>();
- private IReusableBufferProvider mReusableBufferProvider;
+ private IReusableComplexBufferProvider mReusableBufferProvider;
private List mChannelSources = new CopyOnWriteArrayList<>();
private ChannelCalculator mChannelCalculator;
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
@@ -81,20 +81,20 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
/**
* Creates a polyphase channel manager instance.
*
- * @param reusableBufferProvider (ie tuner) that supports register/deregister for reusable baseband sample buffer
+ * @param reusableComplexBufferProvider (ie tuner) that supports register/deregister for reusable baseband sample buffer
* streams
* @param frequency of the baseband complex buffer sample stream (ie center frequency)
* @param sampleRate of the baseband complex buffer sample stream
*/
- public PolyphaseChannelManager(IReusableBufferProvider reusableBufferProvider,
+ public PolyphaseChannelManager(IReusableComplexBufferProvider reusableComplexBufferProvider,
long frequency, double sampleRate)
{
- if(reusableBufferProvider == null)
+ if(reusableComplexBufferProvider == null)
{
throw new IllegalArgumentException("Complex buffer provider argument cannot be null");
}
- mReusableBufferProvider = reusableBufferProvider;
+ mReusableBufferProvider = reusableComplexBufferProvider;
int channelCount = (int)(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java
index bf229d4a..8236b6b1 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java
@@ -15,26 +15,23 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.dc;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BufferDCFilter
{
private final static Logger mLog = LoggerFactory.getLogger(BufferDCFilter.class);
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Buffer DC Filter");
+ private double mAccumulator;
/**
* Calculates the DC offset present in the samples and subtracts the offset from all samples.
*/
- public static void filterReal(float[] samples)
+ public void filterReal(float[] samples)
{
- double accumulator = 0.0;
-
- for(int x = 0; x < samples.length; x++)
- {
- accumulator += samples[x];
- }
-
- float adjustment = (float)(accumulator / (double)samples.length);
+ float adjustment = getOffset(samples);
for(int x = 0; x < samples.length; x++)
{
@@ -42,6 +39,18 @@ public class BufferDCFilter
}
}
+ private float getOffset(float[] samples)
+ {
+ mAccumulator = 0.0;
+
+ for(int x = 0; x < samples.length; x++)
+ {
+ mAccumulator += samples[x];
+ }
+
+ return (float)(mAccumulator / (double)samples.length);
+ }
+
/**
* Calculates the DC offset present in the samples and subtracts the offset from all samples.
*/
@@ -67,4 +76,24 @@ public class BufferDCFilter
samples[x + 1] = samples[x + 1] - qAdjustment;
}
}
+
+ public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
+ {
+ float[] unfilteredSamples = unfilteredBuffer.getSamples();
+
+ ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
+ float[] filteredSamples = filteredBuffer.getSamples();
+
+ float offset = getOffset(unfilteredSamples);
+
+ for(int x = 0; x < unfilteredSamples.length; x++)
+ {
+ filteredSamples[x] = unfilteredSamples[x] - offset;
+ }
+
+ unfilteredBuffer.decrementUserCount();
+ filteredBuffer.incrementUserCount();
+
+ return filteredBuffer;
+ }
}
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 d265b8e6..3f044177 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
@@ -1,41 +1,87 @@
/*******************************************************************************
- * 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
+ * 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.filter.dc;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
+
+/**
+ * DC removal filter for removing any DC offset that may be present when a signal is not aligned within the
+ * demodulated channel.
+ */
public class DCRemovalFilter
{
- protected float mAverage;
- protected float mRatio;
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("DC Removal Filter");
+ protected float mAverage;
+ protected float mRatio;
- public DCRemovalFilter( float ratio )
- {
- mRatio = ratio;
- }
+ /**
+ * Creates a DC removal filter.
+ *
+ * @param ratio
+ */
+ public DCRemovalFilter(float ratio)
+ {
+ mRatio = ratio;
+ }
- public void reset()
- {
- mAverage = 0.0f;
- }
+ /**
+ * Resets the running average to zero.
+ */
+ public void reset()
+ {
+ mAverage = 0.0f;
+ }
- public float filter( float sample )
- {
- float filtered = sample - mAverage;
- mAverage += mRatio * filtered;
- return filtered;
- }
+ /**
+ * Filters the sample and returns a filtered (ie DC-removed) sample.
+ * @param sample to filter
+ * @return filtered sample
+ */
+ public float filter(float sample)
+ {
+ float filtered = sample - mAverage;
+ mAverage += mRatio * filtered;
+ return filtered;
+ }
+
+ /**
+ * Filters the buffer samples and returns a new reusable buffer with the filtered samples.
+ *
+ * Note: this method automatically manages the user accounting between the buffer argument and the new returned
+ * filtered sample buffer.
+ *
+ * @param unfilteredBuffer containing samples to filter
+ * @return a new reusable buffer containing the filtered samples with the user count set to 1.
+ */
+ public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
+ {
+ ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
+
+ float[] unfilteredSamples = unfilteredBuffer.getSamples();
+ float[] filteredSamples = filteredBuffer.getSamples();
+
+ for(int x = 0; x < unfilteredSamples.length; x++)
+ {
+ filteredSamples[x] = filter(unfilteredSamples[x]);
+ }
+
+ unfilteredBuffer.decrementUserCount();
+ filteredBuffer.incrementUserCount();
+
+ return filteredBuffer;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter.java
index 83a59eae..202c83f0 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter.java
@@ -1,45 +1,106 @@
+/*******************************************************************************
+ * 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.filter.dc;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.real.RealSampleListener;
public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
{
- private float mAlpha;
- private float mPreviousInput = 0.0f;
- private float mPreviousOutput = 0.0f;
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("IIR DC Filter");
+ private float mAlpha;
+ private float mPreviousInput = 0.0f;
+ private float mPreviousOutput = 0.0f;
+ private float mCurrentOutput = 0.0f;
- private RealSampleListener mListener;
-
- /**
- * IIR single-pole DC removal filter, as described by J M de Freitas in
- * 29Jan2007 paper at:
- *
- * http://www.mathworks.com/matlabcentral/fileexchange/downloads/72134/download
- *
- * @param alpha 0.0 - 1.0 float - the closer alpha is to unity, the closer
- * the cutoff frequency is to DC.
- */
- public IIRSinglePoleDCRemovalFilter( float alpha )
- {
- mAlpha = alpha;
- }
- @Override
- public void receive( float currentInput )
+ private RealSampleListener mListener;
+
+ /**
+ * IIR single-pole DC removal filter, as described by J M de Freitas in
+ * 29Jan2007 paper at:
+ *
+ * http://www.mathworks.com/matlabcentral/fileexchange/downloads/72134/download
+ *
+ * @param alpha 0.0 - 1.0 float - the closer alpha is to unity, the closer
+ * the cutoff frequency is to DC.
+ */
+ public IIRSinglePoleDCRemovalFilter(float alpha)
{
- float currentOutput = ( currentInput - mPreviousInput ) +
- ( mAlpha * mPreviousOutput );
-
- if( mListener != null )
- {
- mListener.receive( currentOutput );
- }
-
- mPreviousInput = currentInput;
- mPreviousOutput = currentOutput;
+ mAlpha = alpha;
}
- public void setListener( RealSampleListener listener )
- {
- mListener = listener;
- }
+ /**
+ * Filters the sample
+ * @param sample to filter
+ * @return filtered output sample
+ */
+ public float filter(float sample)
+ {
+ mCurrentOutput = (sample - mPreviousInput) + (mAlpha * mPreviousOutput);
+
+ mPreviousInput = sample;
+ mPreviousOutput = mCurrentOutput;
+
+ return mCurrentOutput;
+ }
+
+ /**
+ * Filters the input buffer and returns a new filtered output buffer.
+ *
+ * Buffer user accounting is handled by this method where the input buffer user count is decremented and the
+ * returned output buffer has the user count incremented to one.
+ *
+ * @param inputBuffer to filter
+ * @return filtered samples in a new (ie reused) output buffer
+ */
+ public ReusableBuffer filter(ReusableBuffer inputBuffer)
+ {
+ ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
+ float[] inputSamples = inputBuffer.getSamples();
+ float[] outputSamples = outputBuffer.getSamples();
+
+ for(int x = 0; x < inputSamples.length; x++)
+ {
+ outputSamples[x] = filter(inputSamples[x]);
+ }
+
+ inputBuffer.decrementUserCount();
+ outputBuffer.incrementUserCount();
+
+ return outputBuffer;
+ }
+
+ /**
+ * Implements the RealSampleListener interface for single sample streams.
+ */
+ @Override
+ public void receive(float sample)
+ {
+ if(mListener != null)
+ {
+ mListener.receive(filter(sample));
+ }
+ }
+
+ /**
+ * Assigns the listener to receive filtered samples processed by the RealSampleListener interface
+ */
+ public void setListener(RealSampleListener listener)
+ {
+ mListener = listener;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter_RB.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter_RB.java
deleted file mode 100644
index 974374c0..00000000
--- a/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter_RB.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package io.github.dsheirer.dsp.filter.dc;
-
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.RealBuffer;
-
-public class IIRSinglePoleDCRemovalFilter_RB implements Listener
-{
- private float mAlpha;
- private float mPreviousInput = 0.0f;
- private float mPreviousOutput = 0.0f;
- private Listener mListener;
-
- /**
- * IIR single-pole DC removal filter, as described by J M de Freitas in
- * 29Jan2007 paper at:
- *
- * http://www.mathworks.com/matlabcentral/fileexchange/downloads/72134/download
- *
- * @param alpha 0.0 - 1.0 float - the closer alpha is to unity, the closer
- * the cutoff frequency is to DC.
- */
- public IIRSinglePoleDCRemovalFilter_RB( float alpha )
- {
- mAlpha = alpha;
- }
-
- @Override
- public void receive( RealBuffer buffer )
- {
- if( mListener != null )
- {
- mListener.receive( filter( buffer ) );
- }
- }
-
- public RealBuffer filter( RealBuffer buffer )
- {
- float[] samples = buffer.getSamples();
-
- for( int x = 0; x < samples.length; x++ )
- {
- samples[ x ] = filter( samples[ x ] );
- }
-
- return buffer;
- }
-
- public float filter( float sample )
- {
- float currentOutput = ( sample - mPreviousInput ) +
- ( mAlpha * mPreviousOutput );
-
- mPreviousInput = sample;
- mPreviousOutput = currentOutput;
-
- return currentOutput;
- }
-
- public void reset()
- {
- mPreviousInput = 0.0f;
- mPreviousOutput = 0.0f;
- }
-
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
index c4298aef..821c432b 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
@@ -1,7 +1,7 @@
package io.github.dsheirer.dsp.filter.design;
-import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
+import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -39,64 +39,68 @@ public class FilterViewer extends Application
*/
private float[] getFilter()
{
- FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
- .sampleRate(25000)
- .gridDensity(16)
- .oddLength(true)
- .passBandCutoff(6000)
+// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
+// .sampleRate(8000)
+// .gridDensity(16)
+// .oddLength(true)
+// .passBandCutoff(300)
+// .passBandAmplitude(1.0)
+// .passBandRipple(0.01)
+// .stopBandStart(500)
+// .stopBandAmplitude(0.0)
+// .stopBandRipple(0.03) //Approximately 60 dB attenuation
+// .build();
+//
+ FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
+ .sampleRate(8000)
+ .stopBandCutoff(200)
+ .stopBandAmplitude(0.0)
+ .stopBandRipple(0.025)
+ .passBandStart(300)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
- .stopBandStart(6500)
- .stopBandAmplitude(0.0)
- .stopBandRipple(0.005) //Approximately 94 dB attenuation
.build();
-// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
-// .sampleRate(24000)
-// .stopBandCutoff(800)
-// .stopBandAmplitude(0.0)
-// .stopBandRipple(0.03)
-// .passBandStart(1000)
-// .passBandAmplitude(1.0)
-// .passBandRipple(0.08)
+// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
+// .sampleRate(8000)
+// .stopFrequency1(200)
+// .passFrequencyBegin(300)
+// .passFrequencyEnd(3800)
+// .stopFrequency2(3900)
+// .stopRipple(0.01)
+// .passRipple(0.01)
// .build();
-// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
-// .sampleRate( 48000 )
-// .stopFrequency1( 200 )
-// .passFrequencyBegin( 400 )
-// .passFrequencyEnd( 3200 )
-// .stopFrequency2( 3400 )
-// .stopRipple( 0.0003 )
-// .passRipple( 0.008)
-// .build();
-
float[] taps = null;
- try
- {
- taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19);
-// taps = FilterFactory.getSincFilter(25000.0, 12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
- }
- catch(FilterDesignException fde)
- {
- mLog.error("Error");
- }
-
// try
// {
-// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
-//
-// if(designer.isValid())
-// {
-// taps = designer.getImpulseResponse();
-// }
+// taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19);
+// taps = FilterFactory.getSincFilter(25000.0, 12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
// }
// catch(FilterDesignException fde)
// {
-// mLog.error("Filter design error", fde);
+// mLog.error("Error");
// }
+ try
+ {
+ RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
+
+ if(designer.isValid())
+ {
+ taps = designer.getImpulseResponse();
+ }
+ }
+ catch(FilterDesignException fde)
+ {
+ mLog.error("Filter design error", fde);
+ }
+
+ if(taps == null)
+ {
+ throw new IllegalStateException("Couldn't design filter");
+ }
return taps;
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java
index 7122ea76..7023debb 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java
@@ -27,7 +27,7 @@ import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
*/
public class RealFIRFilter2 extends FIRFilter
{
- private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue();
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealFIRFilter2");
private float[] mData;
private float[] mCoefficients;
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java b/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java
index ece20c3d..39238e8c 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java
@@ -20,21 +20,20 @@ import com.laszlosystems.libresample4j.SampleBuffers;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
-import io.github.dsheirer.sample.real.RealBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
-public class RealResampler implements Listener
+public class RealResampler
{
protected static final Logger mLog = LoggerFactory.getLogger(RealResampler.class);
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealResampler");
private Resampler mResampler;
private Listener mResampledListener;
private BufferManager mBufferManager = new BufferManager();
- private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealResampler");
private double mResampleFactor;
/**
@@ -50,12 +49,11 @@ public class RealResampler implements Listener
/**
* Primary input method to the resampler
- * @param realBuffer to resample
+ * @param reusableBuffer to resample
*/
- @Override
- public void receive(RealBuffer realBuffer)
+ public void resample(ReusableBuffer reusableBuffer)
{
- mBufferManager.load(realBuffer);
+ mBufferManager.load(reusableBuffer);
mResampler.process(mResampleFactor, mBufferManager, false);
}
@@ -74,25 +72,10 @@ public class RealResampler implements Listener
*/
public class BufferManager implements SampleBuffers
{
- private int mBufferLength = 1000;
+ private int mBufferLength = 8000;
private FloatBuffer mInputBuffer = ByteBuffer.allocate(mBufferLength).asFloatBuffer();
private FloatBuffer mOutputBuffer = ByteBuffer.allocate(mBufferLength).asFloatBuffer();
- public void resize(int bufferLength)
- {
- int floatBufferLength = bufferLength * 4;
-
- FloatBuffer inputBuffer = ByteBuffer.allocate(floatBufferLength).asFloatBuffer();
- inputBuffer.put(mInputBuffer);
- mInputBuffer = inputBuffer;
-
- FloatBuffer outputBuffer = ByteBuffer.allocate(floatBufferLength).asFloatBuffer();
- outputBuffer.put(mOutputBuffer);
- mOutputBuffer = outputBuffer;
-
- mBufferLength = bufferLength;
- }
-
/**
* Current length of the input and output buffers
*/
@@ -104,16 +87,10 @@ public class RealResampler implements Listener
/**
* Queues the buffer sample for resampling
*/
- public void load(RealBuffer realBuffer)
+ public void load(ReusableBuffer reusableBuffer)
{
- int remaining = mInputBuffer.remaining();
-
- if(mInputBuffer.remaining() < realBuffer.getSamples().length)
- {
- resize(mInputBuffer.capacity() + (realBuffer.getSamples().length - mInputBuffer.remaining()));
- }
-
- mInputBuffer.put(realBuffer.getSamples());
+ mInputBuffer.put(reusableBuffer.getSamples());
+ reusableBuffer.decrementUserCount();
}
@Override
@@ -147,9 +124,10 @@ public class RealResampler implements Listener
{
mOutputBuffer.put(samples, offset, length);
- while(mOutputBuffer.position() > 200)
+ while(mOutputBuffer.position() > 1000)
{
- ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(200);
+ ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(1000);
+ outputBuffer.incrementUserCount();
mOutputBuffer.flip();
mOutputBuffer.get(outputBuffer.getSamples());
@@ -157,9 +135,12 @@ public class RealResampler implements Listener
if(mResampledListener != null)
{
- outputBuffer.incrementUserCount();
mResampledListener.receive(outputBuffer);
}
+ else
+ {
+ outputBuffer.decrementUserCount();
+ }
}
}
}
diff --git a/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java
index 6a53a8ff..9a46e9dd 100644
--- a/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java
+++ b/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java
@@ -17,19 +17,47 @@
******************************************************************************/
package io.github.dsheirer.dsp.fm;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
+import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.Complex;
-public abstract class FMDemodulator
+/**
+ * FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
+ */
+public class FMDemodulator
{
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("FMDemodulator");
private float mPreviousI = 0.0f;
private float mPreviousQ = 0.0f;
protected float mGain;
+ /**
+ * Creates an FM demodulator instance with a default gain of 1.0.
+ */
+ public FMDemodulator()
+ {
+ this(1.0f);
+ }
+
+ /**
+ * Creates an FM demodulator instance and applies the gain value to each demodulated output sample.
+ * @param gain to apply to demodulated samples.
+ */
public FMDemodulator(float gain)
{
mGain = gain;
}
+ /**
+ * Demodulates the I/Q sample via complex sample multiplication by the complex conjugates of the most recently
+ * demodulated complex sample (ie previous sample). Each new sample overwrites the previously stored sample to
+ * allow future invocations of the method to simply use a new sample value.
+ *
+ * @param currentI of the sample
+ * @param currentQ of the sample
+ * @return demodulated sample
+ */
public float demodulate(float currentI, float currentQ)
{
/**
@@ -65,6 +93,12 @@ public abstract class FMDemodulator
return (float)(angle * mGain);
}
+ /**
+ * Demodulates the complex samples and returns the demodulated value.
+ * @param previous
+ * @param current
+ * @return
+ */
public static double demodulate(Complex previous, Complex current)
{
double inphase = (current.inphase() * previous.inphase()) - (current.quadrature() * -previous.quadrature());
@@ -88,14 +122,48 @@ public abstract class FMDemodulator
return angle;
}
- public abstract void dispose();
+ /**
+ * Demodulates the complex baseband sample buffer and returns a demodulated reusable buffer with the user count
+ * set to 1. The complex baseband buffer's user count is decremented after demodulation.
+ *
+ * @param basebandSampleBuffer containing samples to demodulate
+ * @return demodulated sample buffer.
+ */
+ public ReusableBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer)
+ {
+ ReusableBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount());
+ float[] basebandSamples = basebandSampleBuffer.getSamples();
+ float[] demodulatedSamples = demodulatedBuffer.getSamples();
+
+ for(int x = 0; x < basebandSamples.length; x += 2)
+ {
+ demodulatedSamples[x / 2] = demodulate(basebandSamples[x], basebandSamples[x + 1]);
+ }
+
+ basebandSampleBuffer.decrementUserCount();
+ demodulatedBuffer.incrementUserCount();
+
+ return demodulatedBuffer;
+ }
+
+ public void dispose()
+ {
+ //no-op
+ }
+
+ /**
+ * Resets this demodulator by zeroing the stored previous sample.
+ */
public void reset()
{
mPreviousI = 0.0f;
mPreviousQ = 0.0f;
}
+ /**
+ * Sets the gain to the specified level.
+ */
public void setGain(float gain)
{
mGain = gain;
diff --git a/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator_CB.java b/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator_CB.java
deleted file mode 100644
index b5cd4380..00000000
--- a/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator_CB.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014,2015 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
-package io.github.dsheirer.dsp.fm;
-
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.complex.ComplexBuffer;
-import io.github.dsheirer.sample.real.RateCounter_RB;
-import io.github.dsheirer.sample.real.RealBuffer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class FMDemodulator_CB extends FMDemodulator implements Listener
-{
- private final static Logger mLog = LoggerFactory.getLogger( FMDemodulator_CB.class );
-
- private Listener mListener;
-
- private RateCounter_RB mRateCounter = new RateCounter_RB( "FM DEMOD OUTPUT" );
-
- public FMDemodulator_CB( float gain )
- {
- super( gain );
- }
-
- @Override
- public void receive( ComplexBuffer buffer )
- {
- if( mListener != null )
- {
- float[] samples = buffer.getSamples();
-
- int half = samples.length / 2;
-
- float[] demodulated = new float[ half ];
-
- for( int x = 0; x < half; x++ )
- {
- int index = x * 2;
-
- demodulated[ x ] = demodulate( samples[ index ],
- samples[ index + 1 ] );
- }
-
- mListener.receive( new RealBuffer( demodulated ) );
- }
- }
-
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-
- public void removeListener()
- {
- mListener = null;
- }
-
- @Override
- public void dispose()
- {
- mListener = null;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/fm/FMDiscriminator.java b/src/main/java/io/github/dsheirer/dsp/fm/FMDiscriminator.java
deleted file mode 100644
index 5d157f87..00000000
--- a/src/main/java/io/github/dsheirer/dsp/fm/FMDiscriminator.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014,2015 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
-package io.github.dsheirer.dsp.fm;
-
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.complex.Complex;
-import io.github.dsheirer.sample.real.RealSampleListener;
-
-@Deprecated
-public class FMDiscriminator implements Listener
-{
- private RealSampleListener mListener;
- private Complex mPreviousSample = new Complex( 0.0f, 0.0f );
- private double mGain;
-
- /**
- * Implements a polar discriminator with ArcTangent angle estimator to
- * demodulate complex sampled frequency modulated signals.
- *
- * @param gain - gain to be applied to the demodulated output - can be
- * dynamically applied to continuously adjust audio output
- */
- public FMDiscriminator( double gain )
- {
- mGain = gain;
- }
-
- public void dispose()
- {
- mListener = null;
- mPreviousSample = null;
- }
-
- public synchronized void setGain( double gain )
- {
- mGain = gain;
- }
-
- @Override
- public void receive( Complex currentSample )
- {
- /**
- * Multiply the current sample against the complex conjugate of the
- * previous sample to derive the phase delta between the two samples
- *
- * Negating the previous sample quadrature produces the conjugate
- */
- double i = ( currentSample.inphase() * mPreviousSample.inphase() ) -
- ( currentSample.quadrature() * -mPreviousSample.quadrature() );
- double q = ( currentSample.quadrature() * mPreviousSample.inphase() ) +
- ( currentSample.inphase() * -mPreviousSample.quadrature() );
-
- double angle;
-
- //Check for divide by zero
- if( i == 0 )
- {
- angle = 0.0;
- }
- else
- {
- /**
- * Use the arc-tangent of imaginary (q) divided by real (i) to
- * get the phase angle (+/-) which was directly manipulated by the
- * original message waveform during the modulation. This value now
- * serves as the instantaneous amplitude of the demodulated signal
- */
- double denominator = 1.0d / i;
- angle = Math.atan( (double)q * denominator );
- }
-
- if( mListener != null )
- {
- mListener.receive( (float)( angle * mGain ) );
- }
-
- /**
- * Store the current sample to use during the next iteration
- */
- mPreviousSample = currentSample;
- }
-
- public void setListener( RealSampleListener listener )
- {
- mListener = listener;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/ISyncStateListener.java b/src/main/java/io/github/dsheirer/dsp/fsk/ISyncStateListener.java
new file mode 100644
index 00000000..336fa42e
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/ISyncStateListener.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * 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.fsk;
+
+/**
+ * Interface to receive sync state change events.
+ */
+public interface ISyncStateListener
+{
+ void setSyncState(SyncState syncState);
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java
new file mode 100644
index 00000000..b4a48545
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * 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.fsk;
+
+import io.github.dsheirer.dsp.filter.dc.IIRSinglePoleDCRemovalFilter;
+import io.github.dsheirer.dsp.filter.design.FilterDesignException;
+import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
+import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
+import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
+import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Logic Trunked Radio (LTR) 300-baud FSK decoder is designed to operate against an 8 kHz FM demodulated audio stream.
+ *
+ * This decoder employs a zero-crossing timing error detector with adjustable symbol timing error gain. This decoder
+ * also employs a synchronization monitor that automatically adjusts symbol timing error gain according to the quantity
+ * of successful message sync pattern detections. This is optimized for LTR signals that periodically transmit idle
+ * bursts and transmit continuous message streams during voice transmissions. The synchronization monitor adjusts to an
+ * aggressive gain value when sync is lost and automatically reduces timing error gain as successive sync pattern
+ * detections occur.
+ *
+ * FM demodulated sample buffers should not be filtered prior to this decoder. An internal DC-removal filter coupled
+ * with a low-pass filter removes any tuning offset and/or audio components above 300 Hertz. The DC removal filter is
+ * adjusted for a slower offset removal in order to prevent overly distorting the FSK signalling component. This mild
+ * adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However,
+ * the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission.
+ */
+public class LTRDecoder implements Listener, ISyncDetectListener, ISyncStateListener
+{
+ private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
+
+ public static final float SAMPLES_PER_SYMBOL = 8000.0f / 300.0f;
+
+ protected static final float COARSE_TIMING_GAIN = 1.0f / 3.0f;
+ protected static final float MEDIUM_TIMING_GAIN = 1.0f / 4.0f;
+ protected static final float FINE_TIMING_GAIN = 1.0f / 5.0f;
+
+ private static float[] sLowPassFilterCoefficients;
+
+ static
+ {
+ FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
+ .sampleRate(8000)
+ .gridDensity(16)
+ .oddLength(true)
+ .passBandCutoff(300)
+ .passBandAmplitude(1.0)
+ .passBandRipple(0.01)
+ .stopBandStart(500)
+ .stopBandAmplitude(0.0)
+ .stopBandRipple(0.03) //Approximately 60 dB attenuation
+ .build();
+
+ try
+ {
+ RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
+
+ if(designer.isValid())
+ {
+ sLowPassFilterCoefficients = designer.getImpulseResponse();
+ }
+ }
+ catch(FilterDesignException fde)
+ {
+ mLog.error("Filter design error", fde);
+ }
+ }
+
+ protected float mSymbolTimingGain = COARSE_TIMING_GAIN;
+ protected SampleBuffer mSampleBuffer;
+ protected ZeroCrossingErrorDetector mTimingErrorDetector = new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL);
+ protected SynchronizationMonitor mSynchronizationMonitor;
+ private IIRSinglePoleDCRemovalFilter mDCFilter = new IIRSinglePoleDCRemovalFilter(0.99999f);
+ private RealFIRFilter2 mLowPassFilter = new RealFIRFilter2(sLowPassFilterCoefficients);
+ private Listener mSymbolDecisionListener;
+
+ private boolean mSampleDecision;
+
+ /**
+ * Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder
+ *
+ * @param messageLength in symbols
+ */
+ public LTRDecoder(int messageLength)
+ {
+ this(messageLength, new SampleBuffer(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
+ new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL));
+ }
+
+ /**
+ * Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder.
+ *
+ * @param messageLength in symbols
+ * @param sampleBuffer to use for storing sample decisions
+ * @param detector for symbol timing errors
+ */
+ public LTRDecoder(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector)
+ {
+ mSynchronizationMonitor = new SynchronizationMonitor(messageLength);
+ mSynchronizationMonitor.setListener(this);
+ mTimingErrorDetector = detector;
+ mSampleBuffer = sampleBuffer;
+ mSampleBuffer.setTimingGain(mSymbolTimingGain);
+ }
+
+ /**
+ * Implements the ISyncDetectedListener interface to be notified of message sync detection events.
+ *
+ * This allows the internal timing error detector to adjust symbol timing error gain levels according to the
+ * message synchronization state to quickly adjust to initial signal streams or to reduce gain levels once
+ * synchronization has been achieved.
+ */
+ @Override
+ public void syncDetected()
+ {
+ mSynchronizationMonitor.syncDetected();
+ }
+
+ /**
+ * Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
+ * greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
+ * adjusted and then each of the symbols are decoded using a simple majority decision.
+ *
+ * @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
+ */
+ @Override
+ public void receive(ReusableBuffer buffer)
+ {
+ ReusableBuffer dcFiltered = mDCFilter.filter(buffer);
+ ReusableBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered);
+
+ for(float sample : lowPassFiltered.getSamples())
+ {
+ mSampleDecision = sample > 0.0;
+
+ mSampleBuffer.receive(mSampleDecision);
+ mTimingErrorDetector.receive(mSampleDecision);
+
+ if(mSampleBuffer.hasSymbol())
+ {
+ if(mSymbolDecisionListener != null)
+ {
+ mSymbolDecisionListener.receive(mSampleBuffer.getSymbol());
+ }
+
+ mSampleBuffer.resetAndAdjust(-mTimingErrorDetector.getError());
+
+ mSynchronizationMonitor.increment();
+ }
+ }
+
+ lowPassFiltered.decrementUserCount();
+ }
+
+ /**
+ * Registers a listener to receive decoded LTR symbols.
+ *
+ * @param listener to receive symbols.
+ */
+ public void setListener(Listener listener)
+ {
+ mSymbolDecisionListener = listener;
+ }
+
+ /**
+ * Removes the symbol listener from receiving decoded LTR symbols.
+ */
+ public void removeListener()
+ {
+ mSymbolDecisionListener = null;
+ }
+
+
+ /**
+ * Implements the ISyncStateListener interface to receive synchronization state events and adjust the symbol timing
+ * error gain levels on the internal timing error detector.
+ *
+ * Gain levels are defined relative to a unit gain of 1.0 over a number of symbol periods. LTR uses an initial
+ * ramp-up symbol reversal pattern of 0101 which provides three zero crossing detection opportunities for the timing
+ * error detector, therefore we react to a COARSE gain state to average timing error over 3 symbols.
+ *
+ * @param syncState from an external synchronization state monitor
+ */
+ @Override
+ public void setSyncState(SyncState syncState)
+ {
+ switch(syncState)
+ {
+ case FINE:
+ mSymbolTimingGain = FINE_TIMING_GAIN;
+ break;
+ case MEDIUM:
+ mSymbolTimingGain = MEDIUM_TIMING_GAIN;
+ break;
+ case COARSE:
+ mSymbolTimingGain = COARSE_TIMING_GAIN;
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognized sync state level: " + syncState.name());
+ }
+
+ mSampleBuffer.setTimingGain(mSymbolTimingGain);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoderInstrumented.java b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoderInstrumented.java
new file mode 100644
index 00000000..eeabaadf
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoderInstrumented.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.fsk;
+
+public class LTRDecoderInstrumented extends LTRDecoder
+{
+ /**
+ * Instrumented extension to LTRDecoder
+ *
+ * @param messageLength in symbols
+ */
+ public LTRDecoderInstrumented(int messageLength)
+ {
+ super(messageLength, new SampleBufferInstrumented(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
+ new ZeroCrossingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL));
+ }
+
+ public SampleBufferInstrumented getSampleBuffer()
+ {
+ return (SampleBufferInstrumented)mSampleBuffer;
+ }
+
+ public ZeroCrossingErrorDetectorInstrumented getErrorDetector()
+ {
+ return (ZeroCrossingErrorDetectorInstrumented)mTimingErrorDetector;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/LTRFSKDecoder.java b/src/main/java/io/github/dsheirer/dsp/fsk/LTRFSKDecoderLegacy.java
similarity index 98%
rename from src/main/java/io/github/dsheirer/dsp/fsk/LTRFSKDecoder.java
rename to src/main/java/io/github/dsheirer/dsp/fsk/LTRFSKDecoderLegacy.java
index 6e473ba6..34812d2e 100644
--- a/src/main/java/io/github/dsheirer/dsp/fsk/LTRFSKDecoder.java
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/LTRFSKDecoderLegacy.java
@@ -40,7 +40,7 @@ import io.github.dsheirer.sample.real.RealBuffer;
import java.util.ArrayList;
import java.util.List;
-public class LTRFSKDecoder implements Listener, Instrumentable
+public class LTRFSKDecoderLegacy implements Listener, Instrumentable
{
private RealPrimeCICDecimate mCICDecimator;
@@ -72,7 +72,7 @@ public class LTRFSKDecoder implements Listener, Instrumentable
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling
* decoder. Expects a 48000 sample rate input.
*/
- public LTRFSKDecoder()
+ public LTRFSKDecoderLegacy()
{
mCICDecimator = new RealPrimeCICDecimate( 32, 1, 375, 60, WindowType.HAMMING, 2 );
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/SampleBuffer.java b/src/main/java/io/github/dsheirer/dsp/fsk/SampleBuffer.java
new file mode 100644
index 00000000..5a264320
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/SampleBuffer.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * 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.fsk;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SampleBuffer
+{
+ private final static Logger mLog = LoggerFactory.getLogger(SampleBuffer.class);
+
+ protected boolean[] mDelayLine;
+ protected int mDelayLinePointer = 0;
+
+ protected int mTwiceSamplesPerSymbol;
+ private int mIntegerSamplesPerSymbol;
+ private int mHalfIntegerSamplesPerSymbol;
+ private int mPositiveSampleDecisionCount;
+ protected int mSymbolStart;
+ protected int mSymbolEnd;
+ protected float mSamplesPerSymbol;
+ protected float mMidSymbolSamplingPoint;
+ protected float mSymbolTimingGain;
+
+ /**
+ * Buffer to store complex sample data and produce interpolated samples.
+ * @param samplesPerSymbol
+ */
+ public SampleBuffer(float samplesPerSymbol, float symbolTimingGain)
+ {
+ mSamplesPerSymbol = samplesPerSymbol;
+ mSymbolTimingGain = symbolTimingGain;
+
+ mIntegerSamplesPerSymbol = (int)(samplesPerSymbol + 0.5f);
+ mHalfIntegerSamplesPerSymbol = (int)(samplesPerSymbol / 2.0f + 0.5f);
+ mMidSymbolSamplingPoint = samplesPerSymbol + mHalfIntegerSamplesPerSymbol;
+ mTwiceSamplesPerSymbol = (int)Math.floor(2.0 * samplesPerSymbol);
+ mDelayLine = new boolean[2 * mTwiceSamplesPerSymbol];
+ }
+
+ /**
+ * Stores the sample in the sample buffer to support making symbol decisions.
+ *
+ * Note: use the hasSymbol() method to determine if sufficient samples have been collected to make a symbol
+ * determination.
+ *
+ * @param sample to store in the sample buffer
+ */
+ public void receive(boolean sample)
+ {
+ mMidSymbolSamplingPoint--;
+
+ mDelayLine[mDelayLinePointer] = sample;
+ mDelayLine[mDelayLinePointer + mTwiceSamplesPerSymbol] = sample;
+
+ //Increment pointer and keep pointer in bounds
+ mDelayLinePointer++;
+ mDelayLinePointer = mDelayLinePointer % mTwiceSamplesPerSymbol;
+ }
+
+ /**
+ * Adjusts samples per symbol and symbol timing counters and increments the sample counter to collect another
+ * symbol.
+ *
+ * @param symbolTimingError from a symbol timing error detector
+ */
+ public void resetAndAdjust(float symbolTimingError)
+ {
+ mMidSymbolSamplingPoint += (mSamplesPerSymbol + (symbolTimingError * mSymbolTimingGain));
+ }
+
+ public void setTimingGain(float gain)
+ {
+ mSymbolTimingGain = gain;
+ }
+
+ /**
+ * Indicates if this buffer has accumulated enough samples to represent a full symbol
+ */
+ public boolean hasSymbol()
+ {
+ return mMidSymbolSamplingPoint < 1.0f;
+ }
+
+ /**
+ * Calculates the symbol as a majority decision where the number of positive/true samples are greater than half
+ * of the samples per symbol. Symbol decision is based on the samples collected over the preceeding 0.5 to 1.5
+ * symbol periods.
+ *
+ * @return a true symbol if the majority of the samples were true or positive, otherwise a false symbol.
+ */
+ public boolean getSymbol()
+ {
+ mPositiveSampleDecisionCount = 0;
+
+ //Symbol samples are 0.5 and 1.5 symbols ahead of the current buffer pointer, but 0.5 to 1.5 symbol period
+ //before the most recently stored sample decision.
+ mSymbolStart = mDelayLinePointer + mHalfIntegerSamplesPerSymbol;
+ mSymbolEnd = mSymbolStart + mIntegerSamplesPerSymbol;
+
+ for(int x = mSymbolStart; x < mSymbolEnd; x++)
+ {
+ if(mDelayLine[x])
+ {
+ mPositiveSampleDecisionCount++;
+ }
+ }
+
+ return mPositiveSampleDecisionCount > mHalfIntegerSamplesPerSymbol;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/SampleBufferInstrumented.java b/src/main/java/io/github/dsheirer/dsp/fsk/SampleBufferInstrumented.java
new file mode 100644
index 00000000..255a15e7
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/SampleBufferInstrumented.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * 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.fsk;
+
+public class SampleBufferInstrumented extends SampleBuffer
+{
+ public boolean mLastSymbol;
+
+ /**
+ * Buffer to store complex sample data and produce interpolated samples.
+ *
+ * @param samplesPerSymbol
+ * @param symbolTimingGain
+ */
+ public SampleBufferInstrumented(float samplesPerSymbol, float symbolTimingGain)
+ {
+ super(samplesPerSymbol, symbolTimingGain);
+ }
+
+ /**
+ * Sample delay buffer containing boolean (ie positive or negative) samples.
+ */
+ public boolean[] getDelayLine()
+ {
+ return mDelayLine;
+ }
+
+ /**
+ * Current delay buffer pointer which points to where the next sample will be stored and/or the oldest sample in
+ * the buffer.
+ */
+ public int getDelayLinePointer()
+ {
+ return mDelayLinePointer;
+ }
+
+ public int getDelayLineSecondPointer()
+ {
+ return mDelayLinePointer + mTwiceSamplesPerSymbol;
+ }
+
+ /**
+ * Current symbol timing gain value
+ * @return
+ */
+ public float getSymbolTimingGain()
+ {
+ return mSymbolTimingGain;
+ }
+
+ /**
+ * Current mid-symbol sampling point
+ */
+ public float getMidSymbolSamplingPoint()
+ {
+ return mMidSymbolSamplingPoint;
+ }
+
+ public float getSamplesPerSymbol()
+ {
+ return mSamplesPerSymbol;
+ }
+
+ @Override
+ public boolean getSymbol()
+ {
+ mLastSymbol = super.getSymbol();
+ return mLastSymbol;
+ }
+
+ public boolean getLastSymbol()
+ {
+ return mLastSymbol;
+ }
+
+ public int getSymbolStart()
+ {
+ return mSymbolStart;
+ }
+
+ public int getSymbolEnd()
+ {
+ return mSymbolEnd;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/SyncState.java b/src/main/java/io/github/dsheirer/dsp/fsk/SyncState.java
new file mode 100644
index 00000000..57a3fddd
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/SyncState.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * 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.fsk;
+
+public enum SyncState
+{
+ COARSE, MEDIUM, FINE;
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/SynchronizationMonitor.java b/src/main/java/io/github/dsheirer/dsp/fsk/SynchronizationMonitor.java
new file mode 100644
index 00000000..1608a3d6
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/SynchronizationMonitor.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * 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.fsk;
+
+import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Monitors a symbol stream and externally provided sync detection events to track the sync state as the number of
+ * message synchronization events relative the count of symbols that have been decoded to provide synchronization state
+ * events to a registered listener.
+ *
+ * This might be used to provide a control signal to a symbol timing error detector to adjust timing error gain levels.
+ *
+ * This class only works with messages of uniform symbol length.
+ *
+ * Synchronization States:
+ *
+ * -COARSE - default state. demoted from MEDIUM after one message period with no sync detection.
+ *
+ * -MEDIUM - promoted from COARSE when one sync detection occurs after two message periods, or demoted from FINE
+ * after two message periods with no sync detections.
+ *
+ * -FINE - promoted from MEDIUM after two or more consecutive sync detections within the preceding two or more
+ * message periods.
+ */
+public class SynchronizationMonitor implements ISyncDetectListener
+{
+ private final static Logger mLog = LoggerFactory.getLogger(SynchronizationMonitor.class);
+
+ private ISyncStateListener mSyncStateListener;
+ private SyncState mSyncState = SyncState.COARSE;
+ private int mSyncCount;
+ private int mSymbolCount = 0;
+ private int mMessageLength;
+
+ /**
+ * Constructs the synchronization monitor for the specified message symbol length.
+ * @param messageLength in symbols.
+ */
+ public SynchronizationMonitor(int messageLength)
+ {
+ mMessageLength = messageLength;
+ }
+
+ /**
+ * Resets to the default state of COARSE
+ */
+ public void reset()
+ {
+ setSyncState(SyncState.COARSE);
+ mSymbolCount = 0;
+ }
+
+ /**
+ * Updates the sync state and broadcasts any state changes to the registered sync state listener.
+ */
+ private void setSyncState(SyncState syncState)
+ {
+ if(mSyncState != syncState)
+ {
+ mSyncState = syncState;
+
+ if(mSyncStateListener != null)
+ {
+ mSyncStateListener.setSyncState(mSyncState);
+ }
+ }
+ }
+
+ /**
+ * Updates the message synchronization state based on the current number of sync detections.
+ */
+ private void updateSyncState()
+ {
+ if(mSyncCount < 0)
+ {
+ mSyncCount = 0;
+
+ //The previous state was also zero, so there are no changes
+ return;
+ }
+
+ switch(mSyncCount)
+ {
+ case 0:
+ setSyncState(SyncState.COARSE);
+ break;
+ case 1:
+ setSyncState(SyncState.MEDIUM);
+ break;
+ default:
+ setSyncState(SyncState.FINE);
+ }
+
+ //Max is 3 which means state will drop to MEDIUM after 2 elapsed message periods and COARSE after 3 elapsed
+ // message periods with no sync detection.
+ if(mSyncCount > 3)
+ {
+ mSyncCount = 3;
+ }
+ }
+
+ /**
+ * Implements the ISyncDetectionListener interface to be notified when an external entity detects a message
+ * sync word in the symbol stream.
+ */
+ @Override
+ public void syncDetected()
+ {
+ mSymbolCount = 0;
+ mSyncCount++;
+ updateSyncState();
+ }
+
+ /**
+ * Increments the symbol count for each decoded symbol.
+ */
+ public void increment()
+ {
+ mSymbolCount++;
+
+ if(mSymbolCount > mMessageLength)
+ {
+ mSymbolCount = 0;
+ mSyncCount--;
+ updateSyncState();
+ }
+ }
+
+ public void setListener(ISyncStateListener listener)
+ {
+ mSyncStateListener = listener;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetector.java b/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetector.java
new file mode 100644
index 00000000..c93a09a1
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetector.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * 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.fsk;
+
+/**
+ * Zero crossing error detector. Provides symbol timing error correction values by detecting zero-crossings within
+ * each symbol period. Ideally there should be only 0 or 1 zero crossings within each symbol period. However, when
+ * there is a slight DC offset in the demodulated samples, there can be up to 2 zero crossings within a symbol
+ * period.
+ *
+ * This detector will only provide error corrections when there are 1 or 2 zero crossings detected. If the number
+ * of zero crossings is greater than 2, then it is assumed that there is no signal present and the returned timing
+ * error value is zero. This detector provides an error correction value relative to (approximately) the middle of
+ * a symbol period and therefore continuously attempts to align zero crossings to the middle of the period.
+ *
+ * Since this detector does NOT use interpolation, the error signal will contain minor levels of symbol timing
+ * jitter. Error signal values are reported as integrals of the sampling period.
+ */
+public class ZeroCrossingErrorDetector
+{
+ protected boolean[] mBuffer;
+ protected float mZeroCrossingIdeal;
+ protected float mZeroCrossingOne;
+ protected float mZeroCrossingTwo;
+ protected float mDetectedZeroCrossing;
+ private int mZeroCrossingCount;
+
+ /**
+ * Constructs the detector for the specified samples per symbol.
+ *
+ * @param samplesPerSymbol
+ */
+ public ZeroCrossingErrorDetector(float samplesPerSymbol)
+ {
+ mBuffer = new boolean[(int)Math.ceil(samplesPerSymbol)];
+ mZeroCrossingIdeal = mBuffer.length - 1.5f - (samplesPerSymbol / 2.0f);
+ }
+
+ /**
+ * Stores the sample in the buffer. Use the getError() method once enough samples have been processed to determine
+ * the error for a symbol period.
+ *
+ * @param sample
+ */
+ public void receive(boolean sample)
+ {
+ //Shift all samples left and store new sample at the end
+ System.arraycopy(mBuffer, 1, mBuffer, 0, mBuffer.length - 1);
+ mBuffer[mBuffer.length - 1] = sample;
+ }
+
+ /**
+ * Determines the symbol timing error for the samples processed thus far. This method should be invoked once per
+ * symbol period, triggered via an external samples per symbol tracking process.
+ *
+ * @return symbol timing error calculated as the distance between the approximate symbol center and the next closest
+ * zero crossing index. An error value of zero is returned if the number of zero crossings is less than one or
+ * greater than two.
+ */
+ public float getError()
+ {
+ mZeroCrossingCount = 0;
+
+ for(int x = 0; x < mBuffer.length - 1; x++)
+ {
+ if(mBuffer[x] ^ mBuffer[x + 1])
+ {
+ if(mZeroCrossingCount == 0)
+ {
+ mZeroCrossingCount++;
+ mZeroCrossingOne = x + 0.5f;
+ }
+ else if(mZeroCrossingCount == 1)
+ {
+ mZeroCrossingCount++;
+ mZeroCrossingTwo = x + 0.5f;
+ }
+ else
+ {
+ //Max zero crossings exceeded - no error can be calculated - abort early
+ return 0.0f;
+ }
+ }
+ }
+
+ if(mZeroCrossingCount == 1)
+ {
+ mDetectedZeroCrossing = mZeroCrossingOne;
+
+ return mZeroCrossingIdeal - mZeroCrossingOne;
+ }
+ else if(mZeroCrossingCount == 2)
+ {
+ float errorDistanceOne = mZeroCrossingIdeal - mZeroCrossingOne;
+ float errorDistanceTwo = mZeroCrossingCount - mZeroCrossingTwo;
+
+ if(Math.abs(errorDistanceOne) < Math.abs(errorDistanceTwo))
+ {
+ mDetectedZeroCrossing = mZeroCrossingOne;
+ }
+ else
+ {
+ mDetectedZeroCrossing = mZeroCrossingTwo;
+ }
+
+ return mZeroCrossingIdeal - mDetectedZeroCrossing;
+ }
+
+ return 0.0f;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetectorInstrumented.java b/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetectorInstrumented.java
new file mode 100644
index 00000000..2e90ab01
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetectorInstrumented.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.fsk;
+
+import javafx.beans.property.SimpleFloatProperty;
+
+public class ZeroCrossingErrorDetectorInstrumented extends ZeroCrossingErrorDetector
+{
+ public SimpleFloatProperty timingError = new SimpleFloatProperty();
+
+ /**
+ * Constructs the detector for the specified samples per symbol.
+ *
+ * @param samplesPerSymbol
+ */
+ public ZeroCrossingErrorDetectorInstrumented(float samplesPerSymbol)
+ {
+ super(samplesPerSymbol);
+ }
+
+ @Override
+ public float getError()
+ {
+ float error = super.getError();
+
+ timingError.setValue(error);
+
+ return error;
+ }
+
+ public float getZeroCrossingIdeal()
+ {
+ return mZeroCrossingIdeal;
+ }
+
+ public float getDetectedZeroCrossing()
+ {
+ return mDetectedZeroCrossing;
+ }
+
+ public boolean[] getBuffer()
+ {
+ return mBuffer;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/ISyncDetectListener.java b/src/main/java/io/github/dsheirer/dsp/symbol/ISyncDetectListener.java
new file mode 100644
index 00000000..5a47798a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/symbol/ISyncDetectListener.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * 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.symbol;
+
+public interface ISyncDetectListener
+{
+ void syncDetected();
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/SyncDetectListener.java b/src/main/java/io/github/dsheirer/dsp/symbol/SyncDetectListener.java
deleted file mode 100644
index 942c1635..00000000
--- a/src/main/java/io/github/dsheirer/dsp/symbol/SyncDetectListener.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
-package io.github.dsheirer.dsp.symbol;
-
-/**
- * Interface to allow an external process indicate that it has detected a
- * sync pattern in the bit stream
- */
-public interface SyncDetectListener
-{
- public void syncDetected();
-}
diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/SyncDetectProvider.java b/src/main/java/io/github/dsheirer/dsp/symbol/SyncDetectProvider.java
index 69dc7043..df0ff937 100644
--- a/src/main/java/io/github/dsheirer/dsp/symbol/SyncDetectProvider.java
+++ b/src/main/java/io/github/dsheirer/dsp/symbol/SyncDetectProvider.java
@@ -20,5 +20,5 @@ package io.github.dsheirer.dsp.symbol;
public interface SyncDetectProvider
{
- public void setSyncDetectListener( SyncDetectListener listener );
+ public void setSyncDetectListener( ISyncDetectListener listener );
}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/PlaybackController.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/PlaybackController.java
index ee74737e..25bee580 100644
--- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/PlaybackController.java
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/PlaybackController.java
@@ -15,13 +15,14 @@
******************************************************************************/
package io.github.dsheirer.instrument.gui.viewer;
-import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPane;
+import io.github.dsheirer.instrument.gui.viewer.decoder.AbstractDecoderPane;
import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
-import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.IControllableFileSource;
import io.github.dsheirer.source.IFrameLocationListener;
import io.github.dsheirer.source.wave.ComplexWaveSource;
+import io.github.dsheirer.source.wave.RealWaveSource;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
@@ -40,7 +41,7 @@ import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.File;
import java.io.IOException;
-public class PlaybackController extends HBox implements IFrameLocationListener, Listener
+public class PlaybackController extends HBox implements IFrameLocationListener
{
private final static Logger mLog = LoggerFactory.getLogger(PlaybackController.class);
@@ -48,15 +49,17 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
private TextField mPlaybackPositionText;
private Button mPlay1Button;
private Button mPlay10Button;
+ private Button mPlay30Button;
private Button mPlay100Button;
private Button mPlay1000Button;
+ private Button mPlay2000Button;
private HBox mControlsBox;
private Label mFileLabel;
private EventHandler mPlayEventHandler;
- private DecoderPane mSampleRateListener;
+ private AbstractDecoderPane mSampleRateListener;
private IControllableFileSource mControllableFileSource;
- private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster();
+ private ReusableBufferBroadcaster mReusableBufferBroadcaster = new ReusableBufferBroadcaster();
public PlaybackController()
{
@@ -64,10 +67,10 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
getChildren().addAll(getControlsBox(), getFileLabel());
- setControlsEnabled(false);
+ disableControls();
}
- public void setSampleRateListener(DecoderPane decoderPane)
+ public void setSampleRateListener(AbstractDecoderPane decoderPane)
{
mSampleRateListener = decoderPane;
@@ -81,38 +84,76 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
{
if(file != null && file.isFile())
{
- try
+ if(ComplexWaveSource.supports(file))
{
- mControllableFileSource = new ComplexWaveSource(file);
- mControllableFileSource.setListener(this);
- ((ComplexWaveSource)mControllableFileSource).setListener((Listener)this);
- mControllableFileSource.open();
-
- if(mSampleRateListener != null)
+ try
{
- mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
+ mControllableFileSource = new ComplexWaveSource(file);
+ mControllableFileSource.setListener(this);
+ ((ComplexWaveSource)mControllableFileSource).setListener(mReusableBufferBroadcaster);
+ mControllableFileSource.open();
+
+ if(mSampleRateListener != null)
+ {
+ mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
+ }
}
- }
- catch(UnsupportedAudioFileException e)
- {
- mLog.error("Unsupported Audio File Type [" + (file != null ? file.getAbsolutePath() : "null") + "]");
+ catch(UnsupportedAudioFileException e)
+ {
+ mLog.error("Unsupported Audio File Type [" + (file != null ? file.getAbsolutePath() : "null") + "]");
- Alert alert = new Alert(Alert.AlertType.ERROR, "The file type is unsupported.", ButtonType.OK);
- alert.show();
- return;
- }
- catch(IOException ioe)
- {
- mLog.error("Error opening file [" + (file != null ? file.getAbsolutePath() : "null") + "]", ioe);
+ Alert alert = new Alert(Alert.AlertType.ERROR, "The file type is unsupported.", ButtonType.OK);
+ alert.show();
+ return;
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Error opening file [" + (file != null ? file.getAbsolutePath() : "null") + "]", ioe);
- Alert alert = new Alert(Alert.AlertType.ERROR, "There was an error opening the file",
- ButtonType.OK);
- alert.show();
- return;
+ Alert alert = new Alert(Alert.AlertType.ERROR, "There was an error opening the file",
+ ButtonType.OK);
+ alert.show();
+ return;
+ }
+
+ enableControls();
+ }
+ else if(RealWaveSource.supports(file))
+ {
+ try
+ {
+ mControllableFileSource = new RealWaveSource(file);
+ mControllableFileSource.setListener(this);
+ ((RealWaveSource)mControllableFileSource).setListener(mReusableBufferBroadcaster);
+ mControllableFileSource.open();
+
+ if(mSampleRateListener != null)
+ {
+ mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
+ }
+ }
+ catch(UnsupportedAudioFileException e)
+ {
+ mLog.error("Unsupported Audio File Type [" + (file != null ? file.getAbsolutePath() : "null") + "]");
+
+ Alert alert = new Alert(Alert.AlertType.ERROR, "The file type is unsupported.", ButtonType.OK);
+ alert.show();
+ return;
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Error opening file [" + (file != null ? file.getAbsolutePath() : "null") + "]", ioe);
+
+ Alert alert = new Alert(Alert.AlertType.ERROR, "There was an error opening the file",
+ ButtonType.OK);
+ alert.show();
+ return;
+ }
+
+ enableControls();
}
getFileLabel().setText(file.getName());
- setControlsEnabled(true);
}
}
@@ -130,36 +171,49 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
}
}
- setControlsEnabled(false);
+ disableControls();
}
/**
* Adds listener to receive complex buffers from this playback
*/
- public void addListener(Listener listener)
+ public void addListener(Listener listener)
{
- mComplexBufferBroadcaster.addListener(listener);
+ mReusableBufferBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving complex buffers from this playback
*/
- public void removeListener(Listener listener)
+ public void removeListener(Listener listener)
{
- mComplexBufferBroadcaster.removeListener(listener);
+ mReusableBufferBroadcaster.removeListener(listener);
}
/**
* Sets all of the playback controls to the specified enabled state.
*/
- private void setControlsEnabled(boolean enabled)
+ private void disableControls()
{
- getRewindButton().setDisable(!enabled);
- getPlaybackPositionText().setDisable(!enabled);
- getPlay1Button().setDisable(!enabled);
- getPlay10Button().setDisable(!enabled);
- getPlay100Button().setDisable(!enabled);
- getPlay1000Button().setDisable(!enabled);
+ getRewindButton().setDisable(true);
+ getPlaybackPositionText().setDisable(true);
+ getPlay1Button().setDisable(true);
+ getPlay10Button().setDisable(true);
+ getPlay100Button().setDisable(true);
+ getPlay1000Button().setDisable(true);
+ }
+
+ /**
+ * Sets all of the playback controls to the specified enabled state.
+ */
+ private void enableControls()
+ {
+ getRewindButton().setDisable(false);
+ getPlaybackPositionText().setDisable(false);
+ getPlay1Button().setDisable(false);
+ getPlay10Button().setDisable(false);
+ getPlay100Button().setDisable(false);
+ getPlay1000Button().setDisable(false);
}
private HBox getControlsBox()
@@ -169,7 +223,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
mControlsBox = new HBox();
mControlsBox.getChildren().addAll(getRewindButton(), getPlaybackPositionText(), getPlay1Button(),
- getPlay10Button(), getPlay100Button(), getPlay1000Button());
+ getPlay10Button(), getPlay30Button(), getPlay100Button(), getPlay1000Button(), getPlay2000Button());
}
return mControlsBox;
@@ -213,6 +267,19 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
return mPlay10Button;
}
+ private Button getPlay30Button()
+ {
+ if(mPlay30Button == null)
+ {
+ mPlay30Button = new Button("30");
+ mPlay30Button.setUserData(30);
+ mPlay30Button.setOnAction(getPlaybackEventHandler());
+ mPlay30Button.setGraphic(new FontIcon(FontAwesome.FAST_FORWARD));
+ }
+
+ return mPlay30Button;
+ }
+
private Button getPlay100Button()
{
if(mPlay100Button == null)
@@ -239,6 +306,19 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
return mPlay1000Button;
}
+ private Button getPlay2000Button()
+ {
+ if(mPlay2000Button == null)
+ {
+ mPlay2000Button = new Button("2000");
+ mPlay2000Button.setUserData(2000);
+ mPlay2000Button.setOnAction(getPlaybackEventHandler());
+ mPlay2000Button.setGraphic(new FontIcon(FontAwesome.FAST_FORWARD));
+ }
+
+ return mPlay2000Button;
+ }
+
private EventHandler getPlaybackEventHandler()
{
if(mPlayEventHandler == null)
@@ -302,10 +382,4 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
{
mLog.info("Frame location reset ... ignoring?");
}
-
- @Override
- public void receive(ReusableComplexBuffer complexBuffer)
- {
- mComplexBufferBroadcaster.broadcast(complexBuffer);
- }
}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/ViewerDesktop.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/ViewerDesktop.java
index fcdef3f9..ece3faa5 100644
--- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/ViewerDesktop.java
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/ViewerDesktop.java
@@ -15,7 +15,7 @@
******************************************************************************/
package io.github.dsheirer.instrument.gui.viewer;
-import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPane;
+import io.github.dsheirer.instrument.gui.viewer.decoder.AbstractDecoderPane;
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPaneFactory;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.p25.P25Decoder;
@@ -26,7 +26,7 @@ import java.io.File;
public class ViewerDesktop extends BorderPane
{
private PlaybackController mPlaybackController;
- private DecoderPane mDecoderPane = DecoderPaneFactory.getDefaultPane();
+ private AbstractDecoderPane mDecoderPane = DecoderPaneFactory.getDefaultPane();
public ViewerDesktop()
{
@@ -50,7 +50,7 @@ public class ViewerDesktop extends BorderPane
setDecoderPane(DecoderPaneFactory.getP25DecoderPane(modulation));
}
- private void setDecoderPane(DecoderPane decoderPane)
+ private void setDecoderPane(AbstractDecoderPane decoderPane)
{
getPlaybackController().removeListener(getDecoderPane());
getChildren().remove(getDecoderPane());
@@ -63,7 +63,7 @@ public class ViewerDesktop extends BorderPane
/**
* Decoder pane
*/
- public DecoderPane getDecoderPane()
+ public AbstractDecoderPane getDecoderPane()
{
if(mDecoderPane == null)
{
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/RealSampleLineChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/RealSampleLineChart.java
new file mode 100644
index 00000000..94f02060
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/RealSampleLineChart.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.instrument.gui.viewer.chart;
+
+import io.github.dsheirer.buffer.RealCircularBuffer;
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.scene.chart.LineChart;
+import javafx.scene.chart.NumberAxis;
+import javafx.scene.chart.XYChart;
+import javafx.scene.layout.StackPane;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RealSampleLineChart extends LineChart implements Listener
+{
+ private final static Logger mLog = LoggerFactory.getLogger(RealSampleLineChart.class);
+ private ObservableList> mSamples = FXCollections.observableArrayList();
+ private Series mSampleSeries = new Series<>("Samples", mSamples);
+ private RealCircularBuffer mRealCircularBuffer;
+
+ public RealSampleLineChart(int length, double tickUnit)
+ {
+ super(new NumberAxis("Samples", 0, length, tickUnit),
+ new NumberAxis("Value", -1.0, 1.0, 0.25));
+
+ ObservableList> observableList = FXCollections.observableArrayList(mSampleSeries);
+
+ setData(observableList);
+
+ init(length);
+ }
+
+ private void init(int length)
+ {
+ mRealCircularBuffer = new RealCircularBuffer(length);
+
+ for(int x = 0; x < length; x++)
+ {
+ Data sample = new Data<>(x, 0.0f);
+ mSamples.add(sample);
+ }
+
+ //Turn off the data point marker symbols
+ for(XYChart.Data data: mSampleSeries.getData())
+ {
+ StackPane stackPane = (StackPane)data.getNode();
+ stackPane.setVisible(false);
+ }
+ }
+
+ @Override
+ public void receive(ReusableBuffer buffer)
+ {
+ for(float sample: buffer.getSamples())
+ {
+ mRealCircularBuffer.put(sample);
+ }
+
+ float[] bufferSamples = mRealCircularBuffer.get();
+
+ for(int x = 0; x < bufferSamples.length; x++)
+ {
+ Data sample = mSamples.get(x);
+ sample.setYValue(bufferSamples[x]);
+ }
+
+ buffer.decrementUserCount();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SampleBufferChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SampleBufferChart.java
new file mode 100644
index 00000000..799776e9
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SampleBufferChart.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * 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.instrument.gui.viewer.chart;
+
+import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.scene.chart.LineChart;
+import javafx.scene.chart.NumberAxis;
+import javafx.scene.layout.StackPane;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SampleBufferChart extends LineChart
+{
+ private final static Logger mLog = LoggerFactory.getLogger(SampleBufferChart.class);
+ private ObservableList> mSamples = FXCollections.observableArrayList();
+ private Series mSampleSeries = new Series<>("Samples", mSamples);
+
+ private ObservableList> mSamplePoints = FXCollections.observableArrayList();
+ private Series mSamplePointSeries = new Series<>("Pointers", mSamplePoints);
+
+ private ObservableList> mSymbolSamples = FXCollections.observableArrayList();
+ private Series mSymbolSamplesSeries = new Series<>("Next Symbol Samples", mSymbolSamples);
+
+ private ObservableList> mSymbol = FXCollections.observableArrayList();
+ private Series mSymbolSeries = new Series<>("Previous Symbol", mSymbol);
+
+ private ObservableList> mZeroCrossing = FXCollections.observableArrayList();
+ private Series mZeroCrossingSeries = new Series<>("Zero Crossing", mZeroCrossing);
+
+ private LTRNetDecoderInstrumented mLTRNetDecoderInstrumented;
+
+ public SampleBufferChart(LTRNetDecoderInstrumented decoder, int length)
+ {
+ super(new NumberAxis("Samples", 0, length, 5),
+ new NumberAxis("Value", -1.0, 1.0, 0.25));
+
+ ObservableList observableList = FXCollections.observableArrayList(mSampleSeries, mSamplePointSeries,
+ mSymbolSamplesSeries, mSymbolSeries, mZeroCrossingSeries);
+
+ setData(observableList);
+
+ mLTRNetDecoderInstrumented = decoder;
+ decoder.bufferCount.addListener(new BufferChangeListener());
+ decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
+ }
+
+ public void setBuffer(boolean[] buffer)
+ {
+ while(mSamples.size() < buffer.length)
+ {
+ Data sample = new Data<>(mSamples.size(), 0.0f);
+ mSamples.add(sample);
+
+ //Turn off the data point marker symbols - yes I know this is inefficient
+ for(Data data: mSampleSeries.getData())
+ {
+ StackPane stackPane = (StackPane)data.getNode();
+ stackPane.setVisible(false);
+ }
+ }
+
+ for(int x = 0; x < buffer.length; x++)
+ {
+ Data sample = mSamples.get(x);
+
+ sample.setYValue(buffer[x] ? 0.5f : -0.5f);
+ }
+ }
+
+ public class BufferChangeListener implements ChangeListener
+ {
+ @Override
+ public void changed(ObservableValue observable, Object oldValue, Object newValue)
+ {
+ setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
+
+ while(mSymbol.size() < 2)
+ {
+ Data sample = new Data<>(0.0, -0.55);
+ mSymbol.add(sample);
+ }
+
+ while(mZeroCrossing.size() < 2)
+ {
+ Data sample = new Data<>(0.0, -0.65);
+ mZeroCrossing.add(sample);
+ }
+
+ while(mSamplePoints.size() < 2)
+ {
+ Data sample = new Data<>(0.0, -0.75);
+ mSamplePoints.add(sample);
+ }
+
+ while(mSymbolSamples.size() < 2)
+ {
+ Data sample = new Data<>(0.0, -0.85);
+ mSymbolSamples.add(sample);
+ }
+
+ int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
+ int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
+
+ mSamplePoints.get(0).setXValue(pointer1);
+ mSamplePoints.get(1).setXValue(pointer2);
+
+ float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
+ float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
+
+ float start = pointer1 + samplesRemaining;
+ float end = start + samplesPerSymbol;
+
+ mSymbolSamples.get(0).setXValue(start);
+ mSymbolSamples.get(1).setXValue(end);
+
+ boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
+ int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
+ int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
+
+ mSymbol.get(0).setXValue(symbolStart);
+ mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
+ mSymbol.get(1).setXValue(symbolEnd);
+ mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
+ }
+ }
+
+ public class ErrorChangeListener implements ChangeListener
+ {
+ @Override
+ public void changed(ObservableValue observable, Object oldValue, Object newValue)
+ {
+ while(mZeroCrossing.size() < 2)
+ {
+ Data sample = new Data<>(0.0, -0.65);
+ mZeroCrossing.add(sample);
+ }
+
+ int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
+ int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
+
+ float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
+ float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
+
+ int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
+ reference--;
+
+ float start = reference - zeroCrossingIdeal;
+ float end = reference - detectedZeroCrossing;
+
+ mZeroCrossing.get(0).setXValue(start);
+ mZeroCrossing.get(1).setXValue(end);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ZeroCrossingErrorDetectorChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ZeroCrossingErrorDetectorChart.java
new file mode 100644
index 00000000..2d9b055b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ZeroCrossingErrorDetectorChart.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * 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.instrument.gui.viewer.chart;
+
+import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.scene.chart.LineChart;
+import javafx.scene.chart.NumberAxis;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ZeroCrossingErrorDetectorChart extends LineChart
+{
+ private final static Logger mLog = LoggerFactory.getLogger(ZeroCrossingErrorDetectorChart.class);
+
+ private ObservableList> mCurrentSamples = FXCollections.observableArrayList();
+ private Series mCurrentSampleSeries = new Series<>("Samples", mCurrentSamples);
+
+ private ObservableList> mPreviousSamples = FXCollections.observableArrayList();
+ private Series mPreviousSampleSeries = new Series<>("Samples", mPreviousSamples);
+
+ private ObservableList> mIdeal = FXCollections.observableArrayList();
+ private Series mIdealSeries = new Series<>("Ideal", mIdeal);
+
+ private ObservableList> mDetected = FXCollections.observableArrayList();
+ private Series mDetectedSeries = new Series<>("Detected", mDetected);
+
+ private LTRNetDecoderInstrumented mLTRNetDecoderInstrumented;
+
+ public ZeroCrossingErrorDetectorChart(LTRNetDecoderInstrumented decoder, int length)
+ {
+ super(new NumberAxis("Samples", 0, length, 5),
+ new NumberAxis("Value", -1.0, 1.0, 0.25));
+
+ ObservableList observableList = FXCollections.observableArrayList(mCurrentSampleSeries,
+ mPreviousSampleSeries, mIdealSeries, mDetectedSeries);
+ setData(observableList);
+
+ mLTRNetDecoderInstrumented = decoder;
+ decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
+ decoder.bufferCount.addListener(new ChangeListener()
+ {
+ @Override
+ public void changed(ObservableValue observable, Object oldValue, Object newValue)
+ {
+ updateBuffer();
+ }
+ });
+
+ for(int x = 0; x < length; x++)
+ {
+ Data sample = new Data<>(x, 0.0f);
+ mCurrentSamples.add(sample);
+ Data sample2 = new Data<>(x, 0.0f);
+ mPreviousSamples.add(sample2);
+ }
+
+ Data ideal1 = new Data<>(5, -0.1f);
+ mIdeal.add(ideal1);
+ Data ideal2 = new Data<>(5, -0.9f);
+ mIdeal.add(ideal2);
+
+ Data detected1 = new Data<>(5, -0.25f);
+ mDetected.add(detected1);
+ Data detected2 = new Data<>(5, -0.75f);
+ mDetected.add(detected2);
+ }
+
+ private void updateBuffer()
+ {
+ boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
+
+ for(int x = 0; x < samples.length; x++)
+ {
+ Data sample = mCurrentSamples.get(x);
+ sample.setYValue(samples[x] ? 0.7f : 0.3f);
+ }
+ }
+
+ public class ErrorChangeListener implements ChangeListener
+ {
+ @Override
+ public void changed(ObservableValue observable, Object oldValue, Object newValue)
+ {
+ float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
+
+ boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
+
+ for(int x = 0; x < samples.length; x++)
+ {
+ Data sample = mPreviousSamples.get(x);
+ sample.setYValue(samples[x] ? -0.4f : -0.6f);
+ }
+
+ Data ideal1 = mIdeal.get(0);
+ ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
+ Data ideal2 = mIdeal.get(1);
+ ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
+
+ if(error == 0.0f)
+ {
+ Data detected1 = mDetected.get(0);
+ detected1.setXValue(0.0f);
+ Data detected2 = mDetected.get(1);
+ detected2.setXValue(0.0f);
+ }
+ else
+ {
+ Data detected1 = mDetected.get(0);
+ detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
+ Data detected2 = mDetected.get(1);
+ detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/AbstractDecoderPane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/AbstractDecoderPane.java
new file mode 100644
index 00000000..1535ec43
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/AbstractDecoderPane.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * 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.instrument.gui.viewer.decoder;
+
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.SampleType;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import javafx.scene.layout.VBox;
+
+public abstract class AbstractDecoderPane extends VBox implements Listener
+{
+ abstract SampleType getSampleType();
+
+ public void setSampleRate(double sampleRate)
+ {
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/ComplexDecoderPane.java
similarity index 87%
rename from src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPane.java
rename to src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/ComplexDecoderPane.java
index 82d3ae86..dc67f4e3 100644
--- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPane.java
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/ComplexDecoderPane.java
@@ -17,17 +17,17 @@ package io.github.dsheirer.instrument.gui.viewer.decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
-import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
-public class DecoderPane extends VBox implements Listener
+public class ComplexDecoderPane extends AbstractDecoderPane
{
private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster();
private DecoderType mDecoderType;
- public DecoderPane(DecoderType decoderType)
+ public ComplexDecoderPane(DecoderType decoderType)
{
mDecoderType = decoderType;
}
@@ -35,11 +35,17 @@ public class DecoderPane extends VBox implements Listener
/**
* Package private - used by factory class to create an empty pane
*/
- DecoderPane()
+ ComplexDecoderPane()
{
getChildren().add(new Text("Please select a decoder from the menu"));
}
+ @Override
+ SampleType getSampleType()
+ {
+ return SampleType.COMPLEX;
+ }
+
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
@@ -55,8 +61,4 @@ public class DecoderPane extends VBox implements Listener
{
mComplexBufferBroadcaster.removeListener(listener);
}
-
- public void setSampleRate(double sampleRate)
- {
- }
}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPaneFactory.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPaneFactory.java
index 03590922..03e61517 100644
--- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPaneFactory.java
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPaneFactory.java
@@ -22,15 +22,19 @@ import java.util.EnumSet;
public class DecoderPaneFactory
{
- private static final EnumSet SUPPORTED_DECODER_TYPES = EnumSet.of(DecoderType.P25_PHASE1);
+ private static final EnumSet SUPPORTED_DECODER_TYPES =
+ EnumSet.of(DecoderType.P25_PHASE1,
+ DecoderType.LTR_NET);
/**
* Creates a decoder pane for the decoder type
*/
- public static DecoderPane getDecoderPane(DecoderType decoderType)
+ public static AbstractDecoderPane getDecoderPane(DecoderType decoderType)
{
switch(decoderType)
{
+ case LTR_NET:
+ return new LTRNetPane();
case P25_PHASE1:
throw new IllegalArgumentException("Use the getP25DecoderPane() method for P25 decoder type");
}
@@ -41,7 +45,7 @@ public class DecoderPaneFactory
/**
* Creates a decoder pane for the P25 decoder type
*/
- public static DecoderPane getP25DecoderPane(P25Decoder.Modulation modulation)
+ public static AbstractDecoderPane getP25DecoderPane(P25Decoder.Modulation modulation)
{
switch(modulation)
{
@@ -57,9 +61,9 @@ public class DecoderPaneFactory
/**
* Creates an empty decoder pane
*/
- public static DecoderPane getDefaultPane()
+ public static AbstractDecoderPane getDefaultPane()
{
- return new DecoderPane();
+ return new ComplexDecoderPane();
}
/**
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/LTRNetPane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/LTRNetPane.java
new file mode 100644
index 00000000..1cc29446
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/LTRNetPane.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * 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.instrument.gui.viewer.decoder;
+
+import io.github.dsheirer.instrument.gui.viewer.chart.RealSampleLineChart;
+import io.github.dsheirer.instrument.gui.viewer.chart.SampleBufferChart;
+import io.github.dsheirer.instrument.gui.viewer.chart.ZeroCrossingErrorDetectorChart;
+import io.github.dsheirer.message.Message;
+import io.github.dsheirer.message.MessageDirection;
+import io.github.dsheirer.module.decode.DecoderType;
+import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet;
+import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
+import io.github.dsheirer.sample.Listener;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LTRNetPane extends RealDecoderPane
+{
+ private final static Logger mLog = LoggerFactory.getLogger(LTRNetPane.class);
+
+ private HBox mSampleChartBox;
+ private RealSampleLineChart mRealSampleLineChart;
+ private HBox mSymbolDecoderBox;
+ private SampleBufferChart mSampleBufferChart;
+ private ZeroCrossingErrorDetectorChart mZeroCrossingErrorDetectorChart;
+
+ private LTRNetDecoderInstrumented mDecoder;
+
+ public LTRNetPane()
+ {
+ super(DecoderType.LTR_NET);
+ init();
+ }
+
+ private void init()
+ {
+ addListener(getDecoder());
+ addListener(getSampleLineChart());
+
+ HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
+ HBox.setHgrow(getSymbolDecoderBox(), Priority.ALWAYS);
+
+ VBox.setVgrow(getSampleChartBox(), Priority.ALWAYS);
+ VBox.setVgrow(getSymbolDecoderBox(), Priority.ALWAYS);
+
+ getSampleChartBox().setMaxWidth(Double.MAX_VALUE);
+ getSymbolDecoderBox().setMaxWidth(Double.MAX_VALUE);
+
+ getChildren().addAll(getSampleChartBox(), getSymbolDecoderBox());
+
+ }
+
+ private LTRNetDecoderInstrumented getDecoder()
+ {
+ if(mDecoder == null)
+ {
+ DecodeConfigLTRNet config = new DecodeConfigLTRNet();
+ config.setMessageDirection(MessageDirection.OSW);
+ mDecoder = new LTRNetDecoderInstrumented(config, null);
+ mDecoder.setMessageListener(new Listener()
+ {
+ @Override
+ public void receive(Message message)
+ {
+ mLog.debug(message.getMessage());
+ }
+ });
+ }
+
+ return mDecoder;
+ }
+
+ private HBox getSampleChartBox()
+ {
+ if(mSampleChartBox == null)
+ {
+ mSampleChartBox = new HBox();
+ mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
+
+ getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
+ HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
+
+ mSampleChartBox.getChildren().addAll(getSampleLineChart());
+ }
+
+ return mSampleChartBox;
+ }
+
+ private RealSampleLineChart getSampleLineChart()
+ {
+ if(mRealSampleLineChart == null)
+ {
+ mRealSampleLineChart = new RealSampleLineChart(500, (8000.0 / 300.0));
+ }
+
+ return mRealSampleLineChart;
+ }
+
+ private HBox getSymbolDecoderBox()
+ {
+ if(mSymbolDecoderBox == null)
+ {
+ mSymbolDecoderBox = new HBox();
+ mSymbolDecoderBox.setMaxWidth(Double.MAX_VALUE);
+
+ getSampleBufferChart().setMaxWidth(Double.MAX_VALUE);
+ HBox.setHgrow(getSampleBufferChart(), Priority.ALWAYS);
+
+ mSampleChartBox.getChildren().addAll(getSampleBufferChart(), getZeroCrossingErrorDetectorChart());
+ }
+
+ return mSymbolDecoderBox;
+ }
+
+ private SampleBufferChart getSampleBufferChart()
+ {
+ if(mSampleBufferChart == null)
+ {
+ mSampleBufferChart = new SampleBufferChart(getDecoder(), 106);
+ }
+
+ return mSampleBufferChart;
+ }
+
+ private ZeroCrossingErrorDetectorChart getZeroCrossingErrorDetectorChart()
+ {
+ if(mZeroCrossingErrorDetectorChart == null)
+ {
+ mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
+ getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
+ }
+
+ return mZeroCrossingErrorDetectorChart;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1C4FMPane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1C4FMPane.java
index 56a3cff0..ec35b0de 100644
--- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1C4FMPane.java
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1C4FMPane.java
@@ -30,7 +30,7 @@ import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class P25Phase1C4FMPane extends DecoderPane
+public class P25Phase1C4FMPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1C4FMPane.class);
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1LSMPane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1LSMPane.java
index 33b0f7e4..185e26a4 100644
--- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1LSMPane.java
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1LSMPane.java
@@ -30,7 +30,7 @@ import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class P25Phase1LSMPane extends DecoderPane
+public class P25Phase1LSMPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class);
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/RealDecoderPane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/RealDecoderPane.java
new file mode 100644
index 00000000..4d9a4871
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/RealDecoderPane.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.instrument.gui.viewer.decoder;
+
+import io.github.dsheirer.module.decode.DecoderType;
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.SampleType;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
+import javafx.scene.text.Text;
+
+public class RealDecoderPane extends AbstractDecoderPane
+{
+ private ReusableBufferBroadcaster mBufferBroadcaster = new ReusableBufferBroadcaster();
+ private DecoderType mDecoderType;
+
+ public RealDecoderPane(DecoderType decoderType)
+ {
+ mDecoderType = decoderType;
+ }
+
+ /**
+ * Package private - used by factory class to create an empty pane
+ */
+ RealDecoderPane()
+ {
+ getChildren().add(new Text("Please select a decoder from the menu"));
+ }
+
+ @Override
+ SampleType getSampleType()
+ {
+ return SampleType.COMPLEX;
+ }
+
+ @Override
+ public void receive(ReusableBuffer reusableBuffer)
+ {
+ mBufferBroadcaster.broadcast(reusableBuffer);
+ }
+
+ public void addListener(Listener listener)
+ {
+ mBufferBroadcaster.addListener(listener);
+ }
+
+ public void removeListener(Listener listener)
+ {
+ mBufferBroadcaster.removeListener(listener);
+ }
+
+ public void setSampleRate(double sampleRate)
+ {
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/instrument/tap/event/SyncDetectTap.java b/src/main/java/io/github/dsheirer/instrument/tap/event/SyncDetectTap.java
index c027b596..087393a8 100644
--- a/src/main/java/io/github/dsheirer/instrument/tap/event/SyncDetectTap.java
+++ b/src/main/java/io/github/dsheirer/instrument/tap/event/SyncDetectTap.java
@@ -1,60 +1,58 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
******************************************************************************/
package io.github.dsheirer.instrument.tap.event;
-import io.github.dsheirer.dsp.symbol.SyncDetectListener;
+import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.dsp.symbol.SyncDetectProvider;
import io.github.dsheirer.instrument.tap.Tap;
import io.github.dsheirer.instrument.tap.TapListener;
import io.github.dsheirer.instrument.tap.TapType;
-public class SyncDetectTap extends Tap
- implements SyncDetectListener,
- SyncDetectProvider
+public class SyncDetectTap extends Tap implements ISyncDetectListener, SyncDetectProvider
{
- private SyncDetectListener mListener;
+ private ISyncDetectListener mListener;
- /**
- * Sync detect event tap. Indicates when message sync word has been
- * detected within a binary stream of data.
- */
- public SyncDetectTap()
+ /**
+ * Sync detect event tap. Indicates when message sync word has been
+ * detected within a binary stream of data.
+ */
+ public SyncDetectTap()
{
- super( TapType.EVENT_SYNC_DETECT, "Sync Detected", 0 );
+ super(TapType.EVENT_SYNC_DETECT, "Sync Detected", 0);
}
- @Override
+ @Override
public void syncDetected()
{
- if( mListener != null )
- {
- mListener.syncDetected();
- }
-
- for( TapListener listener: mListeners )
- {
- listener.receive( getName() );
- }
+ if(mListener != null)
+ {
+ mListener.syncDetected();
+ }
+
+ for(TapListener listener : mListeners)
+ {
+ listener.receive(getName());
+ }
}
- @Override
- public void setSyncDetectListener( SyncDetectListener listener )
+ @Override
+ public void setSyncDetectListener(ISyncDetectListener listener)
{
- mListener = listener;
+ mListener = listener;
}
}
diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java
index 1a92cfc9..d34adb24 100644
--- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java
+++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java
@@ -49,8 +49,12 @@ 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;
+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.ReusableBufferBroadcaster;
+import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
import io.github.dsheirer.sample.real.IFilteredRealBufferProvider;
import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener;
@@ -98,7 +102,8 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster mAudioPacketBroadcaster = new Broadcaster<>();
private Broadcaster mCallEventBroadcaster = new Broadcaster<>();
private Broadcaster mChannelEventBroadcaster = new Broadcaster<>();
- private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster();
+ private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster();
+ private ReusableBufferBroadcaster mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
private Broadcaster mDecoderStateEventBroadcaster = new Broadcaster<>();
private Broadcaster mHeartbeatBroadcaster = new Broadcaster<>();
private Broadcaster mSourceEventBroadcaster = new Broadcaster<>();
@@ -327,9 +332,14 @@ public class ProcessingChain implements IChannelEventListener
mMessageBroadcaster.addListener(((IMessageListener) module).getMessageListener());
}
+ if(module instanceof IReusableBufferListener)
+ {
+ mDemodulatedAudioBufferBroadcaster.addListener(((IReusableBufferListener)module).getReusableBufferListener());
+ }
+
if(module instanceof IReusableComplexBufferListener)
{
- mComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
+ mComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener());
}
if(module instanceof ISourceEventListener)
@@ -394,6 +404,11 @@ public class ProcessingChain implements IChannelEventListener
mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener());
}
+ if(module instanceof IReusableBufferListener)
+ {
+ mDemodulatedAudioBufferBroadcaster.removeListener(((IReusableBufferListener)module).getReusableBufferListener());
+ }
+
if(module instanceof IReusableComplexBufferListener)
{
mComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
@@ -471,6 +486,11 @@ public class ProcessingChain implements IChannelEventListener
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
}
+ if(module instanceof IReusableBufferProvider)
+ {
+ ((IReusableBufferProvider)module).setBufferListener(mDemodulatedAudioBufferBroadcaster);
+ }
+
if(module instanceof ISourceEventProvider)
{
((ISourceEventProvider) module).setSourceEventListener(mSourceEventBroadcaster);
@@ -538,6 +558,11 @@ public class ProcessingChain implements IChannelEventListener
((IMessageProvider) module).setMessageListener(null);
}
+ if(module instanceof IReusableBufferProvider)
+ {
+ ((IReusableBufferProvider)module).setBufferListener(null);
+ }
+
if(module instanceof ISourceEventProvider)
{
((ISourceEventProvider) module).setSourceEventListener(null);
@@ -588,7 +613,7 @@ public class ProcessingChain implements IChannelEventListener
((ComplexSource) mSource).setListener(mComplexBufferBroadcaster);
break;
case REAL:
- ((RealSource) mSource).setListener(mFilteredRealBufferBroadcaster);
+ ((RealSource) mSource).setListener(mDemodulatedAudioBufferBroadcaster);
break;
default:
throw new IllegalArgumentException("Unrecognized source "
@@ -636,7 +661,7 @@ public class ProcessingChain implements IChannelEventListener
((ComplexSource) mSource).removeListener(mComplexBufferBroadcaster);
break;
case REAL:
- ((RealSource) mSource).removeListener(mFilteredRealBufferBroadcaster);
+ ((RealSource) mSource).removeListener(mDemodulatedAudioBufferBroadcaster);
break;
default:
throw new IllegalArgumentException("Unrecognized source sample type - cannot start processing " +
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 c2020df7..5a3a3308 100644
--- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
@@ -20,7 +20,7 @@ package io.github.dsheirer.module.decode;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
-import io.github.dsheirer.audio.AudioModule2;
+import io.github.dsheirer.audio.AudioModule;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.channel.state.AlwaysUnsquelchedDecoderState;
import io.github.dsheirer.channel.traffic.TrafficChannelManager;
@@ -30,8 +30,6 @@ import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.map.ChannelMap;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
-import io.github.dsheirer.dsp.filter.FilterFactory;
-import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.filter.AllPassFilter;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.filter.IFilter;
@@ -101,39 +99,8 @@ public class DecoderFactory
{
private final static Logger mLog = LoggerFactory.getLogger(DecoderFactory.class);
- private static final int NBFM_PASS_FREQUENCY = 6000;
- private static final int NBFM_STOP_FREQUENCY = 6500;
-
- //Low-pass filter with ~60 dB attenuation and 89 taps
- private static final FIRFilterSpecification MPT1327_FILTER_SPECIFICATION = FIRFilterSpecification.lowPassBuilder()
- .sampleRate( 48000 ).gridDensity( 16 ).passBandCutoff( 3200 ).passBandAmplitude( 1.0 ).passBandRipple( 0.02 )
- .stopBandStart( 4000 ).stopBandAmplitude( 0.0 ).stopBandRipple( 0.03 ).build();
-
- private static final FIRFilterSpecification P25_C4FM_IQ_SPECIFICATION = FIRFilterSpecification.lowPassBuilder()
- .sampleRate(48000).gridDensity(16).passBandCutoff(6750).passBandAmplitude(1.0).passBandRipple(0.01)
- .stopBandStart(7000).stopBandAmplitude(0.0).stopBandRipple(0.008).build();
-
- private static final FIRFilterSpecification P25_C4FM_DEMOD_SPECIFICATION = FIRFilterSpecification.lowPassBuilder()
- .sampleRate(48000).gridDensity(16).passBandCutoff(2500).passBandAmplitude(1.0).passBandRipple(0.01)
- .stopBandStart(4000).stopBandAmplitude(0.0).stopBandRipple(0.008).build();
-
- private static float[] MPT1327_LOWPASS_FILTER;
- private static float[] P25_C4FM_IQ_FILTER;
- private static float[] P25_C4FM_DEMOD_FILTER;
-
- static
- {
- try
- {
- MPT1327_LOWPASS_FILTER = FilterFactory.getTaps(MPT1327_FILTER_SPECIFICATION);
- P25_C4FM_IQ_FILTER = FilterFactory.getTaps(P25_C4FM_IQ_SPECIFICATION);
- P25_C4FM_DEMOD_FILTER = FilterFactory.getTaps(P25_C4FM_DEMOD_SPECIFICATION);
- }
- catch(Exception e)
- {
- mLog.error("Couldn't design startup filter(s)");
- }
- }
+ private static final double CHANNEL_BANDWIDTH = 12500.0;
+ private static final double DEMODULATED_AUDIO_SAMPLE_RATE = 8000.0;
/**
* Returns a list of one primary decoder and any auxiliary decoders, as
@@ -183,30 +150,28 @@ public class DecoderFactory
modules.add(new AMDecoder(decodeConfig));
modules.add(new AlwaysUnsquelchedDecoderState(DecoderType.AM, channel.getName()));
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 AudioModule2(metadata));
+ modules.add(new AudioModule(metadata));
break;
case NBFM:
modules.add(new NBFMDecoder(decodeConfig));
modules.add(new AlwaysUnsquelchedDecoderState(DecoderType.NBFM, channel.getName()));
- modules.add(new FMDemodulatorModule(NBFM_PASS_FREQUENCY, NBFM_STOP_FREQUENCY));
- modules.add(new DemodulatedAudioFilterModule(4000, 6000));
- modules.add(new AudioModule2(metadata));
+ modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new AudioModule(metadata));
break;
case LTR_STANDARD:
MessageDirection direction = ((DecodeConfigLTRStandard) decodeConfig).getMessageDirection();
modules.add(new LTRStandardDecoder(aliasList, direction));
modules.add(new LTRStandardDecoderState(aliasList));
- modules.add(new FMDemodulatorModule(NBFM_PASS_FREQUENCY, NBFM_STOP_FREQUENCY));
- modules.add(new DemodulatedAudioFilterModule(4000, 6000));
- modules.add(new AudioModule2(metadata));
+ modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new AudioModule(metadata));
break;
case LTR_NET:
modules.add(new LTRNetDecoder((DecodeConfigLTRNet) decodeConfig, aliasList));
modules.add(new LTRNetDecoderState(aliasList));
- modules.add(new FMDemodulatorModule(NBFM_PASS_FREQUENCY, NBFM_STOP_FREQUENCY));
- modules.add(new DemodulatedAudioFilterModule(4000, 6000));
- modules.add(new AudioModule2(metadata));
+ modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new AudioModule(metadata));
break;
case MPT1327:
DecodeConfigMPT1327 mptConfig = (DecodeConfigMPT1327) decodeConfig;
@@ -227,16 +192,14 @@ public class DecoderFactory
(aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize()));
}
- modules.add(new FMDemodulatorModule(NBFM_PASS_FREQUENCY, NBFM_STOP_FREQUENCY));
- modules.add(new DemodulatedAudioFilterModule(P25_C4FM_DEMOD_FILTER, 1.0f));
- modules.add(new AudioModule2(metadata));
+ modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new AudioModule(metadata));
break;
case PASSPORT:
modules.add(new PassportDecoder(decodeConfig, aliasList));
modules.add(new PassportDecoderState(aliasList));
- modules.add(new FMDemodulatorModule(NBFM_PASS_FREQUENCY, NBFM_STOP_FREQUENCY));
- modules.add(new DemodulatedAudioFilterModule(4000, 6000));
- modules.add(new AudioModule2(metadata));
+ modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
+ modules.add(new AudioModule(metadata));
break;
case P25_PHASE1:
DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1) decodeConfig;
diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java
index 6f5f6d06..fa21ad92 100644
--- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java
+++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java
@@ -1,121 +1,92 @@
/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
+ * 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.module.decode.ltrnet;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.bits.MessageFramer;
import io.github.dsheirer.bits.SyncPattern;
-import io.github.dsheirer.dsp.fsk.LTRFSKDecoder;
-import io.github.dsheirer.instrument.Instrumentable;
-import io.github.dsheirer.instrument.tap.Tap;
-import io.github.dsheirer.instrument.tap.TapGroup;
+import io.github.dsheirer.dsp.fsk.LTRDecoder;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener;
-import io.github.dsheirer.sample.real.RealBuffer;
+import io.github.dsheirer.sample.buffer.IReusableBufferListener;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
-import java.util.ArrayList;
-import java.util.List;
-
-public class LTRNetDecoder extends Decoder
- implements IUnFilteredRealBufferListener, Instrumentable
+public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener
{
- public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
- private LTRFSKDecoder mLTRFSKDecoder;
- private MessageFramer mLTRMessageFramer;
- private LTRNetMessageProcessor mLTRMessageProcessor;
+ public static final int LTR_NET_MESSAGE_LENGTH = 40;
+ protected LTRDecoder mLTRDecoder;
+ private MessageFramer mLTRMessageFramer;
+ private LTRNetMessageProcessor mLTRMessageProcessor;
- private List mAvailableTaps;
-
/**
* LTR-Net Decoder. Decodes unfiltered (e.g. demodulated but with no DC or
* audio filtering) samples and produces LTR-Net messages.
*/
- public LTRNetDecoder( DecodeConfigLTRNet config, AliasList aliasList )
- {
- mLTRFSKDecoder = new LTRFSKDecoder();
-
-
- if( config.getMessageDirection() == MessageDirection.OSW )
- {
- mLTRMessageFramer =
- new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
- LTR_STANDARD_MESSAGE_LENGTH );
- }
- else
- {
- mLTRMessageFramer =
- new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
- LTR_STANDARD_MESSAGE_LENGTH );
- }
-
- mLTRFSKDecoder.addListener( mLTRMessageFramer );
-
- mLTRMessageProcessor = new LTRNetMessageProcessor(
- config.getMessageDirection(), aliasList );
-
- mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
-
- mLTRMessageProcessor.setMessageListener( getMessageListener() );
- }
-
- @Override
- public DecoderType getDecoderType()
- {
- return DecoderType.LTR_NET;
- }
-
- @Override
- public Listener getUnFilteredRealBufferListener()
- {
- return mLTRFSKDecoder;
- }
-
- @Override
- public List getTapGroups()
+ public LTRNetDecoder(DecodeConfigLTRNet config, AliasList aliasList, LTRDecoder ltrDecoder)
{
- if( mAvailableTaps == null )
+ mLTRDecoder = ltrDecoder;
+
+ if(config.getMessageDirection() == MessageDirection.OSW)
{
- mAvailableTaps = new ArrayList();
-
- mAvailableTaps.addAll( mLTRFSKDecoder.getTapGroups() );
+ mLTRMessageFramer = new MessageFramer(SyncPattern.LTR_STANDARD_OSW.getPattern(), LTR_NET_MESSAGE_LENGTH);
+ }
+ else
+ {
+ mLTRMessageFramer = new MessageFramer(SyncPattern.LTR_STANDARD_ISW.getPattern(), LTR_NET_MESSAGE_LENGTH);
}
- return mAvailableTaps;
+ mLTRDecoder.setListener(mLTRMessageFramer);
+ mLTRMessageFramer.setSyncDetectListener(mLTRDecoder);
+ mLTRMessageProcessor = new LTRNetMessageProcessor(config.getMessageDirection(), aliasList);
+ mLTRMessageFramer.addMessageListener(mLTRMessageProcessor);
+ mLTRMessageProcessor.setMessageListener(getMessageListener());
+ }
+
+ /**
+ * LTR-Net Decoder. Decodes unfiltered (e.g. demodulated but with no DC or
+ * audio filtering) samples and produces LTR-Net messages.
+ */
+ public LTRNetDecoder(DecodeConfigLTRNet config, AliasList aliasList)
+ {
+ this(config, aliasList, new LTRDecoder(LTR_NET_MESSAGE_LENGTH));
}
@Override
- public void registerTap( Tap tap )
+ public DecoderType getDecoderType()
{
- mLTRFSKDecoder.registerTap( tap );
+ return DecoderType.LTR_NET;
}
@Override
- public void unregisterTap( Tap tap )
+ public Listener getReusableBufferListener()
{
- mLTRFSKDecoder.unregisterTap( tap );
+ return mLTRDecoder;
}
- @Override
- public void reset()
- {
- mLTRMessageFramer.reset();
- }
+ @Override
+ public void receive(ReusableBuffer reusableBuffer)
+ {
+ mLTRDecoder.receive(reusableBuffer);
+ }
+
+ @Override
+ public void reset()
+ {
+ mLTRMessageFramer.reset();
+ }
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java
new file mode 100644
index 00000000..b8970362
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.module.decode.ltrnet;
+
+import io.github.dsheirer.alias.AliasList;
+import io.github.dsheirer.dsp.fsk.LTRDecoderInstrumented;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import javafx.beans.property.SimpleIntegerProperty;
+
+public class LTRNetDecoderInstrumented extends LTRNetDecoder
+{
+ public SimpleIntegerProperty bufferCount = new SimpleIntegerProperty();
+
+ public LTRNetDecoderInstrumented(DecodeConfigLTRNet config, AliasList aliasList)
+ {
+ super(config, aliasList, new LTRDecoderInstrumented(LTR_NET_MESSAGE_LENGTH));
+ }
+
+ public LTRDecoderInstrumented getLTRDecoder()
+ {
+ return (LTRDecoderInstrumented)mLTRDecoder;
+ }
+
+ @Override
+ public void receive(ReusableBuffer reusableBuffer)
+ {
+ super.receive(reusableBuffer);
+
+ bufferCount.setValue(bufferCount.intValue() + 1);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java
index 47bbd171..1f2ba7db 100644
--- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java
+++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java
@@ -1,85 +1,84 @@
/*******************************************************************************
- * 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
+ * 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.module.decode.ltrstandard;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.bits.MessageFramer;
import io.github.dsheirer.bits.SyncPattern;
-import io.github.dsheirer.dsp.fsk.LTRFSKDecoder;
+import io.github.dsheirer.dsp.fsk.LTRDecoder;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener;
-import io.github.dsheirer.sample.real.RealBuffer;
+import io.github.dsheirer.sample.buffer.IReusableBufferListener;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
-public class LTRStandardDecoder extends Decoder implements IUnFilteredRealBufferListener
+public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener
{
- public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
+ public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
+
+ private LTRDecoder mLTRDecoder;
+ private MessageFramer mLTRMessageFramer;
+ private LTRStandardMessageProcessor mLTRMessageProcessor;
- private LTRFSKDecoder mLTRFSKDecoder;
- private MessageFramer mLTRMessageFramer;
- private LTRStandardMessageProcessor mLTRMessageProcessor;
-
/**
* LTR Decoder. Decodes unfiltered (e.g. demodulated but with no DC or
* audio filtering) samples and produces LTR Standard messages.
*/
- public LTRStandardDecoder( AliasList aliasList,
- MessageDirection direction )
- {
- mLTRFSKDecoder = new LTRFSKDecoder();
+ public LTRStandardDecoder(AliasList aliasList, MessageDirection direction)
+ {
+ mLTRDecoder = new LTRDecoder(LTR_STANDARD_MESSAGE_LENGTH);
- if( direction == MessageDirection.OSW )
- {
- mLTRMessageFramer =
- new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
- LTR_STANDARD_MESSAGE_LENGTH );
- }
- else
- {
- mLTRMessageFramer =
- new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
- LTR_STANDARD_MESSAGE_LENGTH );
- }
-
- mLTRFSKDecoder.addListener( mLTRMessageFramer );
+ if(direction == MessageDirection.OSW)
+ {
+ mLTRMessageFramer = new MessageFramer(SyncPattern.LTR_STANDARD_OSW.getPattern(), LTR_STANDARD_MESSAGE_LENGTH);
+ }
+ else
+ {
+ mLTRMessageFramer = new MessageFramer(SyncPattern.LTR_STANDARD_ISW.getPattern(), LTR_STANDARD_MESSAGE_LENGTH);
+ }
- mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
- mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
- mLTRMessageProcessor.setMessageListener( getMessageListener() );
- }
+ mLTRDecoder.setListener(mLTRMessageFramer);
+ mLTRMessageFramer.setSyncDetectListener(mLTRDecoder);
+ mLTRMessageProcessor = new LTRStandardMessageProcessor(direction, aliasList);
+ mLTRMessageFramer.addMessageListener(mLTRMessageProcessor);
+ mLTRMessageProcessor.setMessageListener(getMessageListener());
+ }
- @Override
- public DecoderType getDecoderType()
- {
- return DecoderType.LTR_STANDARD;
- }
+ @Override
+ public DecoderType getDecoderType()
+ {
+ return DecoderType.LTR_STANDARD;
+ }
- @Override
- public Listener getUnFilteredRealBufferListener()
- {
- return mLTRFSKDecoder;
- }
+ @Override
+ public Listener getReusableBufferListener()
+ {
+ return mLTRDecoder;
+ }
- @Override
- public void reset()
- {
- mLTRMessageFramer.reset();
- }
+ @Override
+ public void receive(ReusableBuffer reusableBuffer)
+ {
+ mLTRDecoder.receive(reusableBuffer);
+ }
+
+ @Override
+ public void reset()
+ {
+ mLTRMessageFramer.reset();
+ }
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java
index 54047262..7419861d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java
@@ -3,13 +3,13 @@ package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.BitSetFullException;
-import io.github.dsheirer.bits.ISyncDetectListener;
import io.github.dsheirer.bits.MultiSyncPatternMatcher;
import io.github.dsheirer.bits.SoftSyncDetector;
import io.github.dsheirer.bits.SyncDetector;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.FrameSync;
+import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.edac.BCH_63_16_11;
import io.github.dsheirer.edac.CRC;
import io.github.dsheirer.edac.CRCP25;
diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java
index 42deaed1..131d0a69 100644
--- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java
+++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java
@@ -20,7 +20,7 @@ package io.github.dsheirer.module.decode.passport;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.bits.MessageFramer;
import io.github.dsheirer.bits.SyncPattern;
-import io.github.dsheirer.dsp.fsk.LTRFSKDecoder;
+import io.github.dsheirer.dsp.fsk.LTRFSKDecoderLegacy;
import io.github.dsheirer.instrument.Instrumentable;
import io.github.dsheirer.instrument.tap.Tap;
import io.github.dsheirer.instrument.tap.TapGroup;
@@ -40,7 +40,7 @@ public class PassportDecoder extends Decoder
public static final int PASSPORT_MESSAGE_LENGTH = 68;
public static final int PASSPORT_SYNC_LENGTH = 9;
- private LTRFSKDecoder mPassportFSKDecoder;
+ private LTRFSKDecoderLegacy mPassportFSKDecoder;
private MessageFramer mPassportMessageFramer;
private PassportMessageProcessor mPassportMessageProcessor;
@@ -52,7 +52,7 @@ public class PassportDecoder extends Decoder
*/
public PassportDecoder(DecodeConfiguration config, AliasList aliasList )
{
- mPassportFSKDecoder = new LTRFSKDecoder();
+ mPassportFSKDecoder = new LTRFSKDecoderLegacy();
mPassportMessageFramer =
new MessageFramer( SyncPattern.PASSPORT.getPattern(),
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
index 5560b9dd..57082c6f 100644
--- a/src/main/java/io/github/dsheirer/module/demodulate/audio/DemodulatedAudioFilterModule.java
+++ b/src/main/java/io/github/dsheirer/module/demodulate/audio/DemodulatedAudioFilterModule.java
@@ -30,6 +30,7 @@ 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);
diff --git a/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java b/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java
index b06864c8..d3da75e8 100644
--- a/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java
+++ b/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java
@@ -19,50 +19,55 @@ import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
-import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
-import io.github.dsheirer.dsp.fm.FMDemodulator_CB;
+import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
+import io.github.dsheirer.dsp.filter.resample.RealResampler;
+import io.github.dsheirer.dsp.fm.FMDemodulator;
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.real.IUnFilteredRealBufferProvider;
-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;
+/**
+ * FM Demodulator with integrated fractional resampler.
+ *
+ * Note: no filtering is applied to the demodulated audio.
+ */
public class FMDemodulatorModule extends Module implements ISourceEventListener, IReusableComplexBufferListener,
- IUnFilteredRealBufferProvider
+ Listener, IReusableBufferProvider
{
private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatorModule.class);
- private ComplexFIRFilter_CB_CB mIQFilter;
- private FMDemodulator_CB mDemodulator;
- private ReusableBufferProcessor mReusableBufferProcessor = new ReusableBufferProcessor();
+ private ComplexFIRFilter2 mIQFilter;
+ private FMDemodulator mDemodulator = new FMDemodulator();
+ private RealResampler mResampler;
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
- private int mPassFrequency;
- private int mStopFrequency;
+ private Listener mResampledReusableBufferListener;
+ private double mChannelBandwidth;
+ private double mOutputSampleRate;
/**
- * FM Demodulator with I/Q filter. Demodulated output is unfiltered and may contain a DC component.
+ * Creates an FM demodulator for the specified channel bandwidth and output sample rate.
*
- * @param pass - pass frequency for IQ filtering prior to demodulation. This
- * frequency should be less than or equal to half of the signal bandwidth since the filter will
- * be applied against each of the inphase and quadrature signals and the
- * combined pass bandwidth will be twice this value.
+ * A baseband I/Q filter will be constructed to low pass filter the incoming sample buffer stream to the desired
+ * channel bandwidth and the demodulated output will be resampled to the desired output sample rate. The input
+ * low pass filter is constructed at runtime based on receiving a sample rate notification source event.
*/
- public FMDemodulatorModule(int pass, int stop)
+ public FMDemodulatorModule(double channelBandwidth, double outputSampleRate)
{
- mDemodulator = new FMDemodulator_CB(1.0f);
- mPassFrequency = pass;
- mStopFrequency = stop;
+ mChannelBandwidth = channelBandwidth;
+ mOutputSampleRate = outputSampleRate;
}
@Override
public Listener getReusableComplexBufferListener()
{
- return mReusableBufferProcessor;
+ return this;
}
@Override
@@ -90,18 +95,6 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
mDemodulator.reset();
}
- @Override
- public void setUnFilteredRealBufferListener(Listener listener)
- {
- mDemodulator.setListener(listener);
- }
-
- @Override
- public void removeUnFilteredRealBufferListener()
- {
- mDemodulator.removeListener();
- }
-
@Override
public void start()
{
@@ -112,27 +105,38 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
{
}
- /**
- * Receives complex buffers and sends them through the filtering and demodulation process. Manages accounting for
- * the reusable buffers.
- */
- public class ReusableBufferProcessor implements Listener
+ @Override
+ public void setBufferListener(Listener listener)
{
- @Override
- public void receive(ReusableComplexBuffer reusableComplexBuffer)
+ mResampledReusableBufferListener = listener;
+ }
+
+ @Override
+ public void removeBufferListener()
+ {
+ mResampledReusableBufferListener = null;
+ }
+
+ @Override
+ public void receive(ReusableComplexBuffer reusableComplexBuffer)
+ {
+ if(mIQFilter == null)
{
- if(mIQFilter == null)
- {
- reusableComplexBuffer.decrementUserCount();
- throw new IllegalStateException("FM demodulator module must receive a sample rate change source " +
- "event before it can process complex sample buffers");
- }
-
- float[] samples = reusableComplexBuffer.getSamplesCopy();
-
- mIQFilter.receive(samples);
-
reusableComplexBuffer.decrementUserCount();
+ throw new IllegalStateException("FM demodulator module must receive a sample rate change source " +
+ "event before it can process complex sample buffers");
+ }
+
+ ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(reusableComplexBuffer);
+ ReusableBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer);
+
+ if(mResampler != null)
+ {
+ mResampler.resample(demodulatedBuffer);
+ }
+ else
+ {
+ demodulatedBuffer.decrementUserCount();
}
}
@@ -146,18 +150,35 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
{
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
{
+ if(mIQFilter != null)
+ {
+ mIQFilter.dispose();
+ mIQFilter = null;
+ }
+
double sampleRate = sourceEvent.getValue().doubleValue();
+ if((sampleRate < (2.0 * mChannelBandwidth)))
+ {
+ throw new IllegalStateException("FM 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(mPassFrequency)
+ .passBandCutoff(passBandStop)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
- .stopBandStart(mStopFrequency)
+ .stopBandStart(stopBandStart)
.stopBandAmplitude(0.0)
.stopBandRipple(0.028) //Approximately 60 dB attenuation
.build();
@@ -169,18 +190,35 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
catch(FilterDesignException fde)
{
mLog.error("Couldn't design FM demodulator remez filter for sample rate [" + sampleRate +
- "] pass frequency [" + mPassFrequency + "] and stop frequency [" + mStopFrequency +
+ "] pass frequency [" + passBandStop + "] and stop frequency [" + stopBandStart +
"] - using sinc filter");
}
if(filterTaps == null)
{
- filterTaps = FilterFactory.getLowPass(sampleRate, mPassFrequency, mStopFrequency, 60,
+ filterTaps = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60,
Window.WindowType.HAMMING, true);
}
- mIQFilter = new ComplexFIRFilter_CB_CB(filterTaps, 1.0f);
- mIQFilter.setListener(mDemodulator);
+ mIQFilter = new ComplexFIRFilter2(filterTaps);
+
+ mResampler = new RealResampler(sampleRate, mOutputSampleRate);
+
+ mResampler.setListener(new Listener()
+ {
+ @Override
+ public void receive(ReusableBuffer reusableBuffer)
+ {
+ if(mResampledReusableBufferListener != null)
+ {
+ mResampledReusableBufferListener.receive(reusableBuffer);
+ }
+ else
+ {
+ reusableBuffer.decrementUserCount();
+ }
+ }
+ });
}
}
}
diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java
new file mode 100644
index 00000000..4bc8c08c
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * 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.sample.buffer;
+
+import io.github.dsheirer.sample.Listener;
+
+public interface IReusableBufferListener
+{
+ Listener getReusableBufferListener();
+}
diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java
index afacda85..f2333731 100644
--- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java
+++ b/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java
@@ -17,20 +17,15 @@ package io.github.dsheirer.sample.buffer;
import io.github.dsheirer.sample.Listener;
-public interface IReusableBufferProvider
+public interface IReusableBufferProvider
{
/**
* Adds the listener to receive complex buffer samples
*/
- void addBufferListener(Listener listener);
+ void setBufferListener(Listener listener);
/**
* Removes the listener from receiving complex buffer samples
*/
- void removeBufferListener(Listener listener);
-
- /**
- * Indicates if there are any complex buffer listeners registered
- */
- boolean hasBufferListeners();
+ void removeBufferListener();
}
diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java
index 47eada60..80de66d5 100644
--- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java
+++ b/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java
@@ -17,7 +17,7 @@ package io.github.dsheirer.sample.buffer;
import io.github.dsheirer.sample.Listener;
-public interface IReusableComplexBufferListener
+public interface IReusableComplexBufferListener
{
- Listener getReusableComplexBufferListener();
+ Listener getReusableComplexBufferListener();
}
diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferProvider.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferProvider.java
new file mode 100644
index 00000000..14316bb3
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferProvider.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.sample.buffer;
+
+import io.github.dsheirer.sample.Listener;
+
+public interface IReusableComplexBufferProvider
+{
+ /**
+ * Adds the listener to receive complex buffer samples
+ */
+ void addBufferListener(Listener listener);
+
+ /**
+ * Removes the listener from receiving complex buffer samples
+ */
+ void removeBufferListener(Listener listener);
+
+ /**
+ * Indicates if there are any complex buffer listeners registered
+ */
+ boolean hasBufferListeners();
+}
diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java
index 2df8493c..3e21f337 100644
--- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java
@@ -20,7 +20,7 @@ import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class ReusableBufferBroadcaster extends Broadcaster
+public class ReusableBufferBroadcaster extends Broadcaster
{
private final static Logger mLog = LoggerFactory.getLogger(ReusableBufferBroadcaster.class);
@@ -34,15 +34,15 @@ public class ReusableBufferBroadcaster extends Broadcaster listener : mListeners)
+ for(Listener listener : mListeners)
{
- reusableComplexBuffer.incrementUserCount();
- listener.receive(reusableComplexBuffer);
+ reusableBuffer.incrementUserCount();
+ listener.receive(reusableBuffer);
}
//Decrement user counter for this broadcaster
- reusableComplexBuffer.decrementUserCount();
+ reusableBuffer.decrementUserCount();
}
}
diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java
index bb96adea..45af48a4 100644
--- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java
+++ b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java
@@ -27,10 +27,6 @@ public class ReusableBufferQueue extends AbstractReusableBufferQueue
+public abstract class RealSource extends Source implements Provider
{
@Override
public SampleType getSampleType()
diff --git a/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java b/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java
index 6d6ef037..ba4391d7 100644
--- a/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java
+++ b/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java
@@ -19,6 +19,9 @@ package io.github.dsheirer.source.mixer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.adapter.ISampleAdapter;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
+import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.source.RealSource;
import io.github.dsheirer.source.SourceEvent;
@@ -29,14 +32,14 @@ import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
-import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public class RealMixerSource extends RealSource
{
private final static Logger mLog = LoggerFactory.getLogger(RealMixerSource.class);
-
+ private ReusableBufferBroadcaster mBufferBroadcaster = new ReusableBufferBroadcaster<>();
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealMixerSource");
private long mFrequency = 0;
private int mBufferSize = 16384;
@@ -103,9 +106,9 @@ public class RealMixerSource extends RealSource
{
}
- public void setListener(Listener listener)
+ public void setListener(Listener listener)
{
- mSampleListeners.add(listener);
+ mBufferBroadcaster.addListener(listener);
/* If this is the first listener, start the reader thread */
if(!mBufferReader.isRunning())
@@ -117,38 +120,17 @@ public class RealMixerSource extends RealSource
}
}
- public void removeListener(Listener listener)
+ public void removeListener(Listener listener)
{
- mSampleListeners.remove(listener);
+ mBufferBroadcaster.removeListener(listener);
- /* If this is the laster listener, stop the reader thread */
+ /* If this is the last listener, stop the reader thread */
if(mSampleListeners.isEmpty())
{
mBufferReader.stop();
}
}
- public void broadcast(RealBuffer samples)
- {
- Iterator> it = mSampleListeners.iterator();
-
- while(it.hasNext())
- {
- Listener next = it.next();
-
- /* if this is the last (or only) listener, send him the original
- * buffer, otherwise send him a copy of the buffer */
- if(it.hasNext())
- {
- next.receive(samples.copyOf());
- }
- else
- {
- next.receive(samples);
- }
- }
- }
-
public double getSampleRate()
{
if(mTargetDataLine != null)
@@ -240,11 +222,14 @@ public class RealMixerSource extends RealSource
mTargetDataLine.read(buffer, 0, buffer.length);
/* Convert samples to float array */
- float[] samples =
- mSampleAdapter.convert(buffer);
+//TODO: change this sample adapter over to use reusable buffers and move the buffer queue into the adapter
+ float[] samples = mSampleAdapter.convert(buffer);
/* Dispatch samples to registered listeners */
- broadcast(new RealBuffer(samples));
+ ReusableBuffer reusableBuffer = mReusableBufferQueue.getBuffer(samples.length);
+ reusableBuffer.reloadFrom(samples, System.currentTimeMillis());
+ reusableBuffer.incrementUserCount();
+ mBufferBroadcaster.broadcast(reusableBuffer);
}
catch(Exception e)
{
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
index 4d4b3377..e533a6ad 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
@@ -18,7 +18,7 @@
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
+import io.github.dsheirer.sample.buffer.IReusableComplexBufferProvider;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.ISourceEventListener;
@@ -33,7 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener,
- IReusableBufferProvider, Listener
+ IReusableComplexBufferProvider, Listener
{
private final static Logger mLog = LoggerFactory.getLogger(TunerController.class);
protected ReusableBufferBroadcaster mReusableBufferBroadcaster = new ReusableBufferBroadcaster();
diff --git a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java
index 219a5e1b..0661b797 100644
--- a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java
+++ b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java
@@ -34,7 +34,7 @@ import java.io.File;
import java.io.IOException;
import java.util.Arrays;
-public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
+public class ComplexWaveSource extends ComplexSource implements IControllableFileSource, AutoCloseable
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexWaveSource.class);
@@ -275,4 +275,26 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
{
mFrameLocationListener = null;
}
+
+ /**
+ * Indicates if the file is a supported audio file type
+ */
+ public static boolean supports(File file)
+ {
+ try(AudioInputStream ais = AudioSystem.getAudioInputStream(file))
+ {
+ AudioFormat format = ais.getFormat();
+
+ if(format.getChannels() == 2 && format.getSampleSizeInBits() == 16)
+ {
+ return true;
+ }
+ }
+ catch(Exception e)
+ {
+ //Do nothing, we'll return a default of false
+ }
+
+ return false;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java b/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java
index 8a94520c..646065d9 100644
--- a/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java
+++ b/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java
@@ -19,7 +19,8 @@ package io.github.dsheirer.source.wave;
import io.github.dsheirer.sample.ConversionUtils;
import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.real.RealBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBuffer;
+import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.source.IControllableFileSource;
import io.github.dsheirer.source.IFrameLocationListener;
import io.github.dsheirer.source.RealSource;
@@ -39,11 +40,12 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc
{
private final static Logger mLog = LoggerFactory.getLogger(RealWaveSource.class);
+ private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealWaveSource");
private IFrameLocationListener mFrameLocationListener;
private int mBytesPerFrame;
private int mFrameCounter = 0;
private long mFrequency = 0;
- private Listener mListener;
+ private Listener mListener;
private AudioInputStream mInputStream;
private File mFile;
@@ -52,7 +54,7 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc
mFile = file;
}
- @Override
+ @Override
public void setSourceEventListener(Listener listener)
{
//Not implemented
@@ -226,20 +228,22 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc
buffer = Arrays.copyOf(buffer, samplesRead);
}
- float[] samples = ConversionUtils
- .convertFromSigned16BitSamples(buffer);
+ float[] samples = ConversionUtils.convertFromSigned16BitSamples(buffer);
- mListener.receive(new RealBuffer(samples));
+ ReusableBuffer reusableBuffer = mReusableBufferQueue.getBuffer(samples.length);
+ reusableBuffer.reloadFrom(samples, System.currentTimeMillis());
+ reusableBuffer.incrementUserCount();
+
+ mListener.receive(reusableBuffer);
}
}
}
/**
- * Registers the listener to receive sample buffers as they are read from
- * the wave file
+ * Registers the listener to receive sample buffers as they are read from the wave file
*/
@Override
- public void setListener(Listener listener)
+ public void setListener(Listener listener)
{
mListener = listener;
}
@@ -247,7 +251,8 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc
/**
* Unregisters the listener from receiving sample buffers
*/
- public void removeListener(Listener listener)
+ @Override
+ public void removeListener(Listener listener)
{
mListener = null;
}
@@ -285,4 +290,26 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc
{
mFrameLocationListener = null;
}
+
+ /**
+ * Indicates if the file is a supported audio file type
+ */
+ public static boolean supports(File file)
+ {
+ try(AudioInputStream ais = AudioSystem.getAudioInputStream(file))
+ {
+ AudioFormat format = ais.getFormat();
+
+ if(format.getChannels() == 1 && format.getSampleSizeInBits() == 16)
+ {
+ return true;
+ }
+ }
+ catch(Exception e)
+ {
+ //Do nothing, we'll return a default of false
+ }
+
+ return false;
+ }
}