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