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