From 0eec2e7ab8c5a9eae5b8d96cafcd6eb786139cc1 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Fri, 4 Mar 2022 08:45:05 -0500 Subject: [PATCH] Updates complex decimation filters to use either scalar or vector optimal implementation. (#1148) Updates HackRF native byte buffers with automatic DC removal. Creates new half-band channel source to replace the cic-based channel source in the heterodyne channelizer. Resolves issue in RTL native buffer processing. Creates a CACHED thread pool and reduces the number of threads for the SCHEDULED thread pool to 2. Changes each of the channel source implementations to use a dedicated thread (Dispatcher class) instead of the scheduled thread pool, to reduce the amount of buffered/retained samples objects in memory. Lots of other small changes and tweaks. Removes last of Reusable buffer code. Updates DFTProcessor to use a single thread scheduled executor. Updates Polyphase channelizer to use threads instead of an executor. Removes a number of unused classes. Commenting out window calibration. Scalar is faster in both implementations. Adds vector and scalar implementations for windows. Repackages window and window type classes. WIP adds vector implementations for Airspy non-interleaved buffer iterator and calibration plugins for both interleaved and non-interleaved buffer iterators. WIP adds vector implementations for Airspy interleaved buffer iterator. Work on developing scalar and vector implementations of airspy sample conversion and airspy native buffer processing. Introduces new INativeBuffer framework where tuner raw samples are retained in their native (compressed) format until when they're needed and they are converted on-demand into smaller complex sample buffers to aid garbage collection. Removes all usage of ReusableComplexBuffers and ReusableChannelREsultsBuffers. Updates polyphase channelizer to be more efficient in output channel processing. Updates ViterbiDecoder to change Map usage to array for memory efficiency. Updates channel processors to used scalar implementation for gain and mixer, until the polyphase channel results can be fixed to be a multiple of the SIMD lane width. Resolves final changes for the conversion from interleaved sample arrays to complexsamples buffers for distributing baseband samples to channels and processing chains. Updates tuner channel sources and all decoders to receive new complex samples buffers, removing the previous reusable complex buffers as the default baseband samples buffer object. Still to resolve is the ComplexMixer implementations. Needs validation and a calibration implementation with an update to the complex mixer factory. Updates the DMR decoder to use optimal (scalar vs vector) implementations of filter and AGC controls. Updates the AM demodulator for separated I&Q float sample processing. Code review and testing changes. updates build script for runtime configuration. Integrates calibration manager with main sdrtrunk application. Updates NBFMDecoder to use new optimized FIR and Decimation filter options. Updates real half-band decimation filters to select the optimal (scalar vs vector) implementation. Adds calibration manager plugins. Adds calibration manager and plugins to determine the optimal (scalar vs vector) implementation type to use for various classes of dsp components. Adds vector squelching FM demodulator and cleans up the IIR filter and power squelch to use all floats. Adds vector implementation of FM demodulator. This introduces a new interface that accepts i/q samples as two separate float arrays, versus an interleaved array, which enables more efficient processing. This new complex sample interface will be used across the codebase eventually. Adds ComplexOscillator and a SIMD version VectorComplexOscillator. Deprecates the LowPhaseNoiseOscillator because the ComplexOscillator is about 35% faster. The vectorized version is about 43% faster when SIMD lane width is 8x. Adds new vector complex filters and new real oscillators. Note: the RealOscillator using straight Java math sin() function is 3x slower than the existing legacy implementation. Will replace this with a complex oscillator that only uses the real part. Updates ComplexFIRFilter to be 50% more efficient. Adds 23 tap filter implementations. Will not add 63-tap implementations because they are slower than the default vector implementation. Adds 15-tap customized vector half band filters. Adds initial custom complex vector half-band decimation filters for 11-tap and 15-tap decimation filters.. Adds vector complex half band filter implementations. Note: performance of the generic 64-bit 2-lane filter is horrible and is not included. Optimizes complex half-band filter to be 27% more efficient. Implements vectorized real half-band filter implementations. Implements a new FIR filter class that processes sample buffers instead of single sample at a time. Refactored all of the calibratons to be time-duration based. Testing on windows i7 gen 3 CPU showed that it took 1000x longer for one of the calibrations to run, versus my AMD 5800x. Changed gradle jvm args so that the windows plaf export is always applied. Updates gradle build script to ensure incubator module is included and to cleanup javafx library management for building runtime images. Testing tweaks and changes. Adds logging limiter to dispatcher for buffer overflows Adds User Preferences editor for performing Vector/SIMD calibrations and new startup dialog prompting the user to perform calibrations. Updates calibration manager and all calibration implementations to add warm-up and test phases and to reduce the overall time it takes to calibrate a system. Co-authored-by: Dennis Sheirer --- .idea/dictionaries/denny.xml | 1 + .idea/misc.xml | 2 +- build.gradle | 138 +- .../alias/action/RecurringAction.java | 13 +- .../io/github/dsheirer/audio/AudioModule.java | 54 +- .../audio/AudioSegmentBroadcaster.java | 3 +- .../IcecastBroadcastMetadataUpdater.java | 31 +- .../icecast/IcecastHTTPAudioBroadcaster.java | 33 +- .../icecast/IcecastTCPAudioBroadcaster.java | 12 +- .../v1/ShoutcastV1AudioBroadcaster.java | 13 +- .../ShoutcastV1BroadcastMetadataUpdater.java | 34 +- .../ShoutcastV2AudioStreamingBroadcaster.java | 32 +- .../audio/convert/thumbdv/ThumbDv.java | 42 +- .../dsheirer/audio/invert/AudioAdapter.java | 51 +- .../dsheirer/audio/invert/AudioInverter.java | 73 +- .../dsheirer/buffer/ByteNativeBuffer.java | 154 ++ .../buffer/ByteNativeBufferFactory.java | 61 + .../dsheirer/buffer/FloatNativeBuffer.java | 145 ++ .../github/dsheirer/buffer/INativeBuffer.java | 53 + .../dsheirer/buffer/INativeBufferFactory.java | 39 + .../INativeBufferProvider.java} | 26 +- .../buffer/NativeBufferPoisonPill.java | 55 + .../NativeSampleDelayBuffer.java} | 133 +- .../buffer/SignedByteNativeBuffer.java | 161 ++ .../buffer/SignedByteNativeBufferFactory.java | 68 + .../buffer/airspy/AirspyBufferIterator.java | 105 + .../airspy/AirspyBufferIteratorScalar.java | 96 + .../AirspyBufferIteratorVector128Bits.java | 126 ++ .../AirspyBufferIteratorVector256Bits.java | 120 ++ .../AirspyBufferIteratorVector512Bits.java | 122 ++ .../AirspyBufferIteratorVector64Bits.java | 138 ++ ...AirspyInterleavedBufferIteratorScalar.java | 94 + ...nterleavedBufferIteratorVector128Bits.java | 123 ++ ...nterleavedBufferIteratorVector256Bits.java | 117 ++ ...nterleavedBufferIteratorVector512Bits.java | 120 ++ ...InterleavedBufferIteratorVector64Bits.java | 135 ++ .../buffer/airspy/AirspyNativeBuffer.java | 109 + .../airspy/AirspyNativeBufferFactory.java | 136 ++ .../buffer/airspy/IAirspySampleConverter.java | 40 + .../airspy/ScalarPackedSampleConverter.java | 98 + .../airspy/ScalarUnpackedSampleConverter.java | 66 + .../airspy/VectorUnpackedSampleConverter.java | 96 + .../channel/state/AbstractChannelState.java | 32 +- .../controller/NamingThreadFactory.java | 22 +- .../dsheirer/dsp/afsk/AFSK1200Decoder.java | 38 +- .../io/github/dsheirer/dsp/afsk/AFSKTest.java | 123 -- .../github/dsheirer/dsp/am/AMDemodulator.java | 54 +- .../dsheirer/dsp/am/IAmDemodulator.java | 15 + .../dsheirer/dsp/filter/FilterFactory.java | 201 +- .../github/dsheirer/dsp/filter/Filters.java | 1760 ----------------- .../dsp/filter/FloatHalfBandFilter.java | 204 -- .../filter/FloatHalfBandNoDecimateFilter.java | 194 -- .../dsheirer/dsp/filter/GoertzelFilter.java | 128 +- .../io/github/dsheirer/dsp/filter/Window.java | 505 ----- .../AbstractComplexPolyphaseChannelizer.java | 36 +- .../ComplexPolyphaseChannelizerM2.java | 113 +- .../ContinuousBufferProcessor.java | 227 --- .../ContinuousReusableBufferProcessor.java | 88 - .../channelizer/PolyphaseChannelManager.java | 98 +- .../channelizer/PolyphaseChannelSource.java | 142 +- .../channelizer/TwoChannelSynthesizerM2.java | 65 +- .../output/ChannelOutputProcessor.java | 142 +- .../IPolyphaseChannelOutputProcessor.java | 53 +- .../channelizer/output/MixerAssembler.java | 71 + .../output/OneChannelMixerAssembler.java | 77 + .../output/OneChannelOutputProcessor.java | 77 +- .../channelizer/output/SampleAssembler.java | 85 + .../output/TwoChannelMixerAssembler.java | 104 + .../output/TwoChannelOutputProcessor.java | 98 +- .../filter/cic/ComplexPrimeCICDecimate.java | 456 ----- .../filter/cic/PrimeCicDecimationFilter.java | 471 +++++ .../dsp/filter/cic/RealPrimeCICDecimate.java | 382 ---- .../filter/correction/IQCorrectionFilter.java | 58 - .../dsp/filter/dc/BufferDCFilter.java | 97 - .../dsp/filter/dc/DCRemovalFilter.java | 96 - .../dsp/filter/dc/DcRemovalFilterFactory.java | 49 + .../dsp/filter/dc/IDcRemovalFilter.java | 28 + .../dc/IIRSinglePoleDCRemovalFilter.java | 72 +- .../dsp/filter/dc/ScalarDcRemovalFilter.java | 86 + .../dsp/filter/dc/VectorDcRemovalFilter.java | 87 + .../decimate/ComplexDecimateX1024Filter.java | 53 - .../decimate/ComplexDecimateX128Filter.java | 53 - .../decimate/ComplexDecimateX16Filter.java | 53 - .../decimate/ComplexDecimateX256Filter.java | 53 - .../decimate/ComplexDecimateX2Filter.java | 55 - .../decimate/ComplexDecimateX32Filter.java | 53 - .../decimate/ComplexDecimateX4Filter.java | 54 - .../decimate/ComplexDecimateX512Filter.java | 53 - .../decimate/ComplexDecimateX64Filter.java | 53 - .../decimate/ComplexDecimateX8Filter.java | 53 - .../decimate/DecimationFilterFactory.java | 52 +- .../decimate/RealDecimateX1024Filter.java | 11 +- .../decimate/RealDecimateX128Filter.java | 12 +- .../decimate/RealDecimateX16Filter.java | 11 +- .../decimate/RealDecimateX256Filter.java | 11 +- .../filter/decimate/RealDecimateX2Filter.java | 18 +- .../decimate/RealDecimateX32Filter.java | 11 +- .../filter/decimate/RealDecimateX4Filter.java | 11 +- .../decimate/RealDecimateX512Filter.java | 11 +- .../decimate/RealDecimateX64Filter.java | 11 +- .../filter/decimate/RealDecimateX8Filter.java | 11 +- .../dsp/filter/design/FilterViewer.java | 12 +- .../HalfBandDecimationFilterAnalysis.java | 8 +- .../filter/fir/complex/ComplexFIRFilter2.java | 138 -- .../filter/fir/complex/IComplexFilter.java | 28 + .../fir/complex/ScalarComplexFIRFilter.java | 152 ++ .../complex/VectorComplexFIRFilter128Bit.java | 124 ++ .../complex/VectorComplexFIRFilter64Bit.java | 124 ++ .../VectorComplexFIRFilterDefaultBit.java | 124 ++ .../dsp/filter/fir/real/IRealFilter.java | 28 + .../dsp/filter/fir/real/RealFIRFilter.java | 135 ++ .../dsp/filter/fir/real/RealFIRFilter2.java | 132 -- .../fir/real/VectorRealFIRFilter128Bit.java | 114 ++ .../fir/real/VectorRealFIRFilter256Bit.java | 114 ++ .../fir/real/VectorRealFIRFilter512Bit.java | 114 ++ .../fir/real/VectorRealFIRFilter64Bit.java | 114 ++ .../real/VectorRealFIRFilterDefaultBit.java | 114 ++ .../RealHalfBandDecimationFilter.java | 160 ++ ...alHalfBandDecimationFilter11Tap128Bit.java | 115 ++ ...alHalfBandDecimationFilter11Tap256Bit.java | 112 ++ ...lHalfBandDecimationFilter11Tap512Bit.java} | 77 +- ...ealHalfBandDecimationFilter11Tap64Bit.java | 122 ++ ...torRealHalfBandDecimationFilter128Bit.java | 118 ++ ...alHalfBandDecimationFilter15Tap128Bit.java | 118 ++ ...alHalfBandDecimationFilter15Tap256Bit.java | 112 ++ ...alHalfBandDecimationFilter15Tap512Bit.java | 111 ++ ...ealHalfBandDecimationFilter15Tap64Bit.java | 127 ++ ...alHalfBandDecimationFilter23Tap128Bit.java | 123 ++ ...alHalfBandDecimationFilter23Tap256Bit.java | 115 ++ ...alHalfBandDecimationFilter23Tap512Bit.java | 114 ++ ...ealHalfBandDecimationFilter23Tap64Bit.java | 137 ++ ...torRealHalfBandDecimationFilter256Bit.java | 118 ++ ...torRealHalfBandDecimationFilter512Bit.java | 118 ++ ...alHalfBandDecimationFilter63Tap128Bit.java | 117 ++ ...alHalfBandDecimationFilter63Tap256Bit.java | 117 ++ ...alHalfBandDecimationFilter63Tap512Bit.java | 117 ++ ...ealHalfBandDecimationFilter63Tap64Bit.java | 117 ++ ...ctorRealHalfBandDecimationFilter64Bit.java | 118 ++ ...ealHalfBandDecimationFilterDefaultBit.java | 118 ++ .../ComplexHalfBandDecimationFilter.java | 137 -- .../complex/ComplexHalfBandFilter.java | 111 -- .../filter/halfband/real/HalfBandFilter.java | 190 -- .../filter/halfband/real/HalfBandFilter2.java | 132 -- .../dsp/filter/hilbert/HilbertTransform.java | 300 +-- .../hilbert/HilbertTransformFactory.java | 54 + .../hilbert/LegacyHilbertTransform.java | 248 +++ .../hilbert/ScalarHilbertTransform.java | 137 ++ .../VectorHilbertTransform128Bits.java | 96 + .../VectorHilbertTransform256Bits.java | 90 + .../VectorHilbertTransform512Bits.java | 108 + .../hilbert/VectorHilbertTransform64Bits.java | 108 + .../VectorHilbertTransformDefaultBits.java | 89 + .../dsp/filter/iir/SinglePoleIirFilter.java | 12 +- .../dsp/filter/resample/RealResampler.java | 99 +- .../dsheirer/dsp/fm/FmDemodulatorFactory.java | 76 + .../dsheirer/dsp/fm/IFmDemodulator.java | 14 + .../dsp/fm/ISquelchingFmDemodulator.java | 42 + ...odulator.java => ScalarFMDemodulator.java} | 107 +- .../dsp/fm/ScalarSquelchingFMDemodulator.java | 98 + .../dsp/fm/SquelchingFMDemodulator.java | 146 -- .../dsheirer/dsp/fm/VectorFMDemodulator.java | 141 ++ .../dsp/fm/VectorSquelchingFMDemodulator.java | 229 +++ .../github/dsheirer/dsp/fsk/LTRDecoder.java | 47 +- .../dsp/gain/AutomaticGainControl.java | 89 +- .../dsp/gain/ComplexAutomaticGainControl.java | 249 --- .../gain/ComplexFeedForwardGainControl.java | 182 -- ...omplexGain.java => LegacyComplexGain.java} | 4 +- .../dsp/gain/complex/ComplexGain.java | 62 + .../dsp/gain/complex/ComplexGainControl.java | 114 ++ .../dsp/gain/complex/ComplexGainFactory.java | 71 + .../dsp/gain/complex/IComplexGainControl.java | 17 + .../dsp/gain/complex/ScalarComplexGain.java | 35 + .../dsp/gain/complex/VectorComplexGain.java | 42 + .../complex/VectorComplexGainControl.java | 52 + .../dsp/mixer/AbstractOscillator.java | 192 -- .../dsheirer/dsp/mixer/ComplexMixer.java | 123 ++ .../dsp/mixer/ComplexMixerFactory.java | 62 + .../dsheirer/dsp/mixer/FS4DownConverter.java | 69 - .../dsheirer/dsp/mixer/IOscillator.java | 115 -- .../dsp/mixer/LowPhaseNoiseOscillator.java | 140 -- .../github/dsheirer/dsp/mixer/Oscillator.java | 69 - .../dsp/mixer/ScalarComplexMixer.java | 104 + .../dsp/mixer/VectorComplexMixer.java | 57 + .../dsp/oscillator/AbstractOscillator.java | 116 ++ .../dsp/oscillator/FS4DownConverter.java | 113 ++ .../{mixer => oscillator}/FS4UpConverter.java | 2 +- .../dsp/oscillator/IComplexOscillator.java | 39 + .../dsheirer/dsp/oscillator/IOscillator.java | 59 + .../dsp/oscillator/IRealOscillator.java | 30 + .../dsp/oscillator/OscillatorFactory.java | 76 + .../oscillator/ScalarComplexOscillator.java | 138 ++ .../dsp/oscillator/ScalarRealOscillator.java | 49 + .../oscillator/VectorComplexOscillator.java | 189 ++ .../dsp/oscillator/VectorRealOscillator.java | 63 + ...cisionDirectedDemodulatorInstrumented.java | 48 +- .../DQPSKGardnerDemodulatorInstrumented.java | 42 +- .../dsheirer/dsp/psk/PSKDemodulator.java | 44 +- .../dsheirer/dsp/squelch/PowerMonitor.java | 39 +- .../dsheirer/dsp/squelch/PowerSquelch.java | 16 +- .../symbol/BinaryToByteBufferAssembler.java | 32 +- .../symbol/DibitToByteBufferAssembler.java | 52 +- .../dsheirer/dsp/window/ScalarWindow.java | 29 + .../dsheirer/dsp/window/VectorWindow.java | 45 + .../io/github/dsheirer/dsp/window/Window.java | 38 + .../dsheirer/dsp/window/WindowFactory.java | 442 +++++ .../dsheirer/dsp/window/WindowType.java | 38 + .../dsheirer/edac/trellis/ViterbiDecoder.java | 74 +- .../dsheirer/gui/JavaFxWindowManager.java | 37 +- .../java/io/github/dsheirer/gui/SDRTrunk.java | 55 +- .../gui/channelizer/ChannelizerViewer.java | 100 +- .../gui/channelizer/ChannelizerViewer2.java | 79 +- .../HeterodyneChannelizerViewer.java | 566 ++++++ .../gui/channelizer/SynthesizerViewer.java | 115 +- .../gui/instrument/PlaybackController.java | 68 +- .../gui/instrument/ViewerDesktop.java | 27 +- .../chart/ComplexPhaseLineChart.java | 124 -- .../chart/ComplexSampleLineChart.java | 43 +- .../chart/FMDemodulatedLineChart.java | 121 -- .../instrument/chart/RealSampleLineChart.java | 36 +- .../gui/instrument/chart/SampleXYChart.java | 43 +- .../decoder/AbstractAFSK1200Pane.java | 30 +- .../decoder/AbstractDecoderPane.java | 30 +- .../decoder/ComplexDecoderPane.java | 47 +- .../gui/instrument/decoder/DMRPane.java | 57 +- .../instrument/decoder/P25Phase1C4FMPane.java | 46 +- .../instrument/decoder/P25Phase1LSMPane.java | 46 +- .../decoder/P25Phase2HDQPSKPane.java | 46 +- .../instrument/decoder/RealDecoderPane.java | 42 +- .../dsheirer/gui/playlist/PlaylistEditor.java | 31 +- .../gui/playlist/alias/action/BeepEditor.java | 4 +- .../gui/playlist/alias/action/ClipEditor.java | 4 +- .../playlist/alias/action/ScriptEditor.java | 4 +- .../channel/ChannelConfigurationEditor.java | 33 +- .../playlist/radioreference/AgencyEditor.java | 4 +- .../radioreference/AgencyFrequencyEditor.java | 31 +- .../radioreference/FrequencyEditor.java | 4 +- .../radioreference/RadioReferenceEditor.java | 47 +- .../playlist/radioreference/SystemEditor.java | 4 +- .../playlist/streaming/StreamingEditor.java | 31 +- .../preference/CalibrateRequest.java} | 19 +- .../preference/PreferenceEditorFactory.java | 5 +- .../gui/preference/PreferenceEditorType.java | 3 +- .../gui/preference/UserPreferencesEditor.java | 7 +- .../calibration/CalibrationDialog.java | 73 + .../VectorCalibrationPreferenceEditor.java | 347 ++++ .../decoder/JmbeLibraryPreferenceEditor.java | 9 +- .../gui/preference/playback/ToneUtil.java | 9 +- .../io/github/dsheirer/jmbe/JmbeCreator.java | 4 +- .../dsheirer/log/TextAreaLogAppender.java | 50 + .../dsheirer/module/ProcessingChain.java | 80 +- .../decode/afsk/AbstractAFSKDecoder.java | 38 +- .../module/decode/dmr/DMRDecoder.java | 75 +- .../decode/dmr/DMRDecoderInstrumented.java | 33 +- .../module/decode/dmr/DMRMessageFramer.java | 17 +- .../Fleetsync2DecoderInstrumented.java | 32 +- .../lj1200/LJ1200DecoderInstrumented.java | 32 +- .../module/decode/ltrnet/LTRNetDecoder.java | 36 +- .../ltrnet/LTRNetDecoderInstrumented.java | 32 +- .../ltrstandard/LTRStandardDecoder.java | 38 +- .../mdc1200/MDCDecoderInstrumented.java | 12 +- .../module/decode/mpt1327/MPT1327Decoder.java | 18 +- .../mpt1327/MPT1327DecoderInstrumented.java | 12 +- .../module/decode/nbfm/NBFMDecoder.java | 95 +- .../decode/p25/phase1/C4FMSymbolFilter.java | 471 ----- .../decode/p25/phase1/P25P1Decoder.java | 17 +- .../decode/p25/phase1/P25P1DecoderC4FM.java | 48 +- .../phase1/P25P1DecoderC4FMInstrumented.java | 33 +- .../decode/p25/phase1/P25P1DecoderLSM.java | 97 +- .../phase1/P25P1DecoderLSMInstrumented.java | 33 +- .../decode/p25/phase1/P25P1MessageFramer.java | 17 +- .../decode/p25/phase2/P25P2Decoder.java | 17 +- .../decode/p25/phase2/P25P2DecoderHDQPSK.java | 132 +- .../P25P2DecoderHDQPSKInstrumented.java | 55 +- .../decode/p25/phase2/P25P2MessageFramer.java | 15 +- .../decode/passport/PassportDecoder.java | 18 +- .../tait/Tait1200DecoderInstrumented.java | 12 +- .../demodulate/am/AMDemodulatorModule.java | 208 +- .../demodulate/fm/FMDemodulatorModule.java | 119 +- .../dsheirer/preference/PreferenceType.java | 3 +- .../dsheirer/preference/UserPreferences.java | 10 +- .../VectorCalibrationPreference.java | 103 + .../dsheirer/record/RecorderFactory.java | 21 +- .../dsheirer/record/binary/BinaryReader.java | 130 +- .../record/binary/BinaryRecorder.java | 71 +- ...r.java => ComplexSamplesWaveRecorder.java} | 90 +- .../record/wave/NativeBufferWaveRecorder.java | 208 ++ .../dsheirer/sample/ConversionUtils.java | 46 +- .../io/github/dsheirer/sample/Provider.java | 10 +- .../github/dsheirer/sample/SampleUtils.java | 145 ++ .../sample/adapter/AbstractSampleAdapter.java | 23 - .../sample/adapter/ComplexSampleAdapter.java | 39 - .../sample/adapter/ComplexShortAdapter.java | 46 +- .../sample/adapter/ISampleAdapter.java | 24 + .../adapter/RealChannelShortAdapter.java | 39 +- .../sample/adapter/RealSampleAdapter.java | 44 +- .../sample/adapter/RealShortAdapter.java | 41 +- .../sample/buffer/AbstractReusableBuffer.java | 177 -- .../buffer/AbstractReusableBufferQueue.java | 129 -- .../sample/buffer/IByteBufferListener.java | 28 + .../sample/buffer/IByteBufferProvider.java | 41 + .../IReusableBufferDisposedListener.java | 25 - .../buffer/IReusableBufferProvider.java | 31 - .../buffer/IReusableByteBufferListener.java | 23 - .../IReusableComplexBufferListener.java | 23 - .../IReusableComplexBufferProvider.java | 36 - .../dsheirer/sample/buffer/ITimestamped.java | 28 + ...erflowableReusableBufferTransferQueue.java | 69 - .../buffer/ReusableBufferAssembler.java | 80 - .../buffer/ReusableBufferBroadcaster.java | 48 - .../sample/buffer/ReusableBufferQueue.java | 80 - .../sample/buffer/ReusableByteBuffer.java | 97 - .../buffer/ReusableByteBufferQueue.java | 54 - .../buffer/ReusableChannelResultsBuffer.java | 164 -- .../ReusableChannelResultsBufferQueue.java | 55 - .../sample/buffer/ReusableComplexBuffer.java | 72 - .../ReusableComplexBufferAssembler.java | 174 -- .../buffer/ReusableComplexBufferQueue.java | 94 - .../sample/buffer/ReusableFloatBuffer.java | 140 -- .../complex/ComplexSampleAssembler.java | 72 - .../complex/ComplexSampleBroadcaster.java | 76 - .../complex/ComplexSampleListListener.java | 25 - .../sample/complex/ComplexSampleUtils.java | 51 - .../sample/complex/ComplexSamples.java | 44 + .../complex/ComplexSamplesRepackager.java | 95 + .../IComplexSamplesListener.java} | 6 +- .../IComplexSamplesProvider.java} | 8 +- .../IInterleavedComplexSamplesListener.java | 29 + .../IInterleavedComplexSamplesProvider.java | 42 + .../complex/InterleavedComplexSamples.java | 46 + .../dsheirer/sample/complex/ThreadKiller.java | 33 - .../sample/decimator/ComplexDecimator.java | 75 - .../dsheirer/sample/decimator/Decimator.java | 72 - .../sample/decimator/RealDecimator.java | 58 - .../sample/real/IRealBufferListener.java | 26 + .../sample/real/IRealBufferProvider.java | 34 + .../sample/real/RealSampleBroadcaster.java | 76 - .../sample/real/RealSampleProvider.java | 8 - .../github/dsheirer/source/ComplexSource.java | 4 +- .../io/github/dsheirer/source/RealSource.java | 22 +- .../dsheirer/source/mixer/ComplexMixer.java | 42 +- .../dsheirer/source/mixer/MixerManager.java | 31 +- .../dsheirer/source/mixer/MixerReader.java | 54 +- .../source/mixer/RealMixerSource.java | 36 +- .../source/tuner/TunerController.java | 42 +- .../tuner/airspy/AirspySampleConverter.java | 160 -- .../tuner/airspy/AirspyTunerController.java | 36 +- .../tuner/channel/CICTunerChannelSource.java | 150 +- .../channel/HalfBandTunerChannelSource.java | 244 +++ .../MultiFrequencyTunerChannelSource.java | 41 +- .../channel/PassThroughChannelSource.java | 114 +- .../tuner/channel/TunerChannelSource.java | 225 +-- .../source/tuner/fcd/FCDTunerController.java | 20 +- .../tuner/hackrf/HackRFTunerController.java | 36 +- .../HeterodyneChannelSourceManager.java | 79 +- .../PolyphaseChannelSourceManager.java | 32 +- .../recording/RecordingTunerController.java | 49 +- .../tuner/rtl/RTL2832TunerController.java | 48 +- .../tuner/rtl/e4k/E4KTunerController.java | 31 +- .../source/tuner/test/SampleGenerator.java | 145 +- .../tuner/test/TestTunerController.java | 24 +- .../source/tuner/usb/USBMasterProcessor.java | 43 +- .../tuner/usb/USBTransferProcessor.java | 70 +- .../source/tuner/usb/USBTunerController.java | 16 +- .../usb/converter/NativeBufferConverter.java | 49 +- .../usb/converter/NativeSampleBuffer.java | 51 + .../converter/SignedByteSampleConverter.java | 33 +- .../source/wave/ComplexWaveSource.java | 58 +- .../dsheirer/source/wave/RealWaveSource.java | 57 +- .../spectrum/ChannelSpectrumPanel.java | 518 ----- ...rocessor.java => ComplexDftProcessor.java} | 211 +- .../spectrum/IDFTWidthChangeProcessor.java | 23 +- .../spectrum/NativeBufferManager.java | 150 ++ .../spectrum/OverflowableBufferStream.java | 111 +- .../spectrum/SpectralDisplayPanel.java | 231 +-- .../spectrum/menu/FFTWindowTypeItem.java | 62 +- .../dsheirer/spectrum/menu/FrameRateItem.java | 29 +- .../io/github/dsheirer/util/Dispatcher.java | 200 ++ .../io/github/dsheirer/util/ThreadPool.java | 29 +- .../dsheirer/vector/VectorUtilities.java | 194 ++ .../vector/calibrate/Calibration.java | 143 ++ .../calibrate/CalibrationException.java | 17 + .../vector/calibrate/CalibrationManager.java | 294 +++ .../vector/calibrate/CalibrationType.java | 92 + .../vector/calibrate/Implementation.java | 15 + .../AirspySampleConverterCalibration.java | 138 ++ .../airspy/AirspyUnpackedCalibration.java | 309 +++ .../AirspyUnpackedInterleavedCalibration.java | 321 +++ .../demodulator/FmDemodulatorCalibration.java | 142 ++ .../SquelchingFmDemodulatorCalibration.java | 148 ++ .../filter/FirFilterCalibration.java | 378 ++++ .../filter/RealDcRemovalCalibration.java | 144 ++ .../RealHalfBand11TapFilterCalibration.java | 316 +++ .../RealHalfBand15TapFilterCalibration.java | 315 +++ .../RealHalfBand23TapFilterCalibration.java | 315 +++ .../RealHalfBand63TapFilterCalibration.java | 315 +++ .../RealHalfBandDefaultFilterCalibration.java | 361 ++++ .../gain/ComplexGainCalibration.java | 150 ++ .../gain/ComplexGainControlCalibration.java | 143 ++ .../calibrate/hilbert/HilbertCalibration.java | 350 ++++ .../mixer/ComplexMixerCalibration.java | 141 ++ .../ComplexOscillatorCalibration.java | 154 ++ .../oscillator/RealOscillatorCalibration.java | 146 ++ .../calibrate/window/WindowCalibration.java | 144 ++ 403 files changed, 22916 insertions(+), 15088 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java create mode 100644 src/main/java/io/github/dsheirer/buffer/ByteNativeBufferFactory.java create mode 100644 src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java create mode 100644 src/main/java/io/github/dsheirer/buffer/INativeBuffer.java create mode 100644 src/main/java/io/github/dsheirer/buffer/INativeBufferFactory.java rename src/main/java/io/github/dsheirer/{dsp/filter/decimate/IComplexDecimationFilter.java => buffer/INativeBufferProvider.java} (61%) create mode 100644 src/main/java/io/github/dsheirer/buffer/NativeBufferPoisonPill.java rename src/main/java/io/github/dsheirer/{sample/buffer/ReusableComplexDelayBuffer.java => buffer/NativeSampleDelayBuffer.java} (55%) create mode 100644 src/main/java/io/github/dsheirer/buffer/SignedByteNativeBuffer.java create mode 100644 src/main/java/io/github/dsheirer/buffer/SignedByteNativeBufferFactory.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector64Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java create mode 100644 src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/afsk/AFSKTest.java create mode 100644 src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/Filters.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandNoDecimateFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/Window.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/MixerAssembler.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelMixerAssembler.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/SampleAssembler.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/cic/PrimeCicDecimationFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/cic/RealPrimeCICDecimate.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/correction/IQCorrectionFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/dc/DcRemovalFilterFactory.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/dc/IDcRemovalFilter.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/dc/ScalarDcRemovalFilter.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/dc/VectorDcRemovalFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX1024Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX128Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX16Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX256Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX2Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX32Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX4Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX512Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX64Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX8Filter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter2.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/complex/IComplexFilter.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ScalarComplexFIRFilter.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter128Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter64Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilterDefaultBit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/real/IRealFilter.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter128Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter256Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter512Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter64Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilterDefaultBit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/RealHalfBandDecimationFilter.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap128Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap256Bit.java rename src/main/java/io/github/dsheirer/dsp/filter/halfband/{real/RealHalfBandDecimationFilter.java => VectorRealHalfBandDecimationFilter11Tap512Bit.java} (51%) create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap64Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter128Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap128Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap256Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap512Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap64Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap128Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap256Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap512Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap64Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter256Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter512Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap128Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap256Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap512Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap64Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter64Bit.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilterDefaultBit.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransformFactory.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/hilbert/LegacyHilbertTransform.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/hilbert/ScalarHilbertTransform.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform128Bits.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform256Bits.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform512Bits.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform64Bits.java create mode 100644 src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransformDefaultBits.java create mode 100644 src/main/java/io/github/dsheirer/dsp/fm/FmDemodulatorFactory.java create mode 100644 src/main/java/io/github/dsheirer/dsp/fm/IFmDemodulator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/fm/ISquelchingFmDemodulator.java rename src/main/java/io/github/dsheirer/dsp/fm/{FMDemodulator.java => ScalarFMDemodulator.java} (63%) create mode 100644 src/main/java/io/github/dsheirer/dsp/fm/ScalarSquelchingFMDemodulator.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/fm/SquelchingFMDemodulator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/fm/VectorFMDemodulator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/fm/VectorSquelchingFMDemodulator.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/gain/ComplexAutomaticGainControl.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java rename src/main/java/io/github/dsheirer/dsp/gain/{ComplexGain.java => LegacyComplexGain.java} (86%) create mode 100644 src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java create mode 100644 src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainControl.java create mode 100644 src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainFactory.java create mode 100644 src/main/java/io/github/dsheirer/dsp/gain/complex/IComplexGainControl.java create mode 100644 src/main/java/io/github/dsheirer/dsp/gain/complex/ScalarComplexGain.java create mode 100644 src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGain.java create mode 100644 src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGainControl.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java create mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixerFactory.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java delete mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/ScalarComplexMixer.java create mode 100644 src/main/java/io/github/dsheirer/dsp/mixer/VectorComplexMixer.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/AbstractOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/FS4DownConverter.java rename src/main/java/io/github/dsheirer/dsp/{mixer => oscillator}/FS4UpConverter.java (98%) create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/IComplexOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/IOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/IRealOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/OscillatorFactory.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/ScalarComplexOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/ScalarRealOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/VectorComplexOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/oscillator/VectorRealOscillator.java create mode 100644 src/main/java/io/github/dsheirer/dsp/window/ScalarWindow.java create mode 100644 src/main/java/io/github/dsheirer/dsp/window/VectorWindow.java create mode 100644 src/main/java/io/github/dsheirer/dsp/window/Window.java create mode 100644 src/main/java/io/github/dsheirer/dsp/window/WindowFactory.java create mode 100644 src/main/java/io/github/dsheirer/dsp/window/WindowType.java create mode 100644 src/main/java/io/github/dsheirer/gui/channelizer/HeterodyneChannelizerViewer.java delete mode 100644 src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexPhaseLineChart.java delete mode 100644 src/main/java/io/github/dsheirer/gui/instrument/chart/FMDemodulatedLineChart.java rename src/main/java/io/github/dsheirer/{dsp/filter/decimate/ComplexDecimateX0Filter.java => gui/preference/CalibrateRequest.java} (67%) create mode 100644 src/main/java/io/github/dsheirer/gui/preference/calibration/CalibrationDialog.java create mode 100644 src/main/java/io/github/dsheirer/gui/preference/calibration/VectorCalibrationPreferenceEditor.java create mode 100644 src/main/java/io/github/dsheirer/log/TextAreaLogAppender.java delete mode 100644 src/main/java/io/github/dsheirer/module/decode/p25/phase1/C4FMSymbolFilter.java create mode 100644 src/main/java/io/github/dsheirer/preference/calibration/VectorCalibrationPreference.java rename src/main/java/io/github/dsheirer/record/wave/{ComplexBufferWaveRecorder.java => ComplexSamplesWaveRecorder.java} (61%) create mode 100644 src/main/java/io/github/dsheirer/record/wave/NativeBufferWaveRecorder.java create mode 100644 src/main/java/io/github/dsheirer/sample/SampleUtils.java delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java create mode 100644 src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java create mode 100644 src/main/java/io/github/dsheirer/sample/buffer/IByteBufferListener.java create mode 100644 src/main/java/io/github/dsheirer/sample/buffer/IByteBufferProvider.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferDisposedListener.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferListener.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferProvider.java create mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ITimestamped.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/OverflowableReusableBufferTransferQueue.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferAssembler.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBuffer.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBufferQueue.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBuffer.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBufferQueue.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferQueue.java delete mode 100644 src/main/java/io/github/dsheirer/sample/buffer/ReusableFloatBuffer.java delete mode 100644 src/main/java/io/github/dsheirer/sample/complex/ComplexSampleAssembler.java delete mode 100644 src/main/java/io/github/dsheirer/sample/complex/ComplexSampleBroadcaster.java delete mode 100644 src/main/java/io/github/dsheirer/sample/complex/ComplexSampleListListener.java delete mode 100644 src/main/java/io/github/dsheirer/sample/complex/ComplexSampleUtils.java create mode 100644 src/main/java/io/github/dsheirer/sample/complex/ComplexSamples.java create mode 100644 src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesRepackager.java rename src/main/java/io/github/dsheirer/sample/{buffer/IReusableBufferListener.java => complex/IComplexSamplesListener.java} (86%) rename src/main/java/io/github/dsheirer/sample/{buffer/IReusableByteBufferProvider.java => complex/IComplexSamplesProvider.java} (84%) create mode 100644 src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesListener.java create mode 100644 src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesProvider.java create mode 100644 src/main/java/io/github/dsheirer/sample/complex/InterleavedComplexSamples.java delete mode 100644 src/main/java/io/github/dsheirer/sample/complex/ThreadKiller.java delete mode 100644 src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java delete mode 100644 src/main/java/io/github/dsheirer/sample/decimator/Decimator.java delete mode 100644 src/main/java/io/github/dsheirer/sample/decimator/RealDecimator.java create mode 100644 src/main/java/io/github/dsheirer/sample/real/IRealBufferListener.java create mode 100644 src/main/java/io/github/dsheirer/sample/real/IRealBufferProvider.java delete mode 100644 src/main/java/io/github/dsheirer/sample/real/RealSampleBroadcaster.java delete mode 100644 src/main/java/io/github/dsheirer/sample/real/RealSampleProvider.java delete mode 100644 src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeSampleBuffer.java delete mode 100644 src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java rename src/main/java/io/github/dsheirer/spectrum/{DFTProcessor.java => ComplexDftProcessor.java} (52%) create mode 100644 src/main/java/io/github/dsheirer/spectrum/NativeBufferManager.java create mode 100644 src/main/java/io/github/dsheirer/util/Dispatcher.java create mode 100644 src/main/java/io/github/dsheirer/vector/VectorUtilities.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/CalibrationException.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/Implementation.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/demodulator/FmDemodulatorCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/demodulator/SquelchingFmDemodulatorCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/filter/FirFilterCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/filter/RealDcRemovalCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand11TapFilterCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand15TapFilterCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand23TapFilterCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand63TapFilterCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBandDefaultFilterCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainControlCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/hilbert/HilbertCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/mixer/ComplexMixerCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/oscillator/ComplexOscillatorCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/oscillator/RealOscillatorCalibration.java create mode 100644 src/main/java/io/github/dsheirer/vector/calibrate/window/WindowCalibration.java diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml index 2eb640e2..99c24efd 100644 --- a/.idea/dictionaries/denny.xml +++ b/.idea/dictionaries/denny.xml @@ -2,6 +2,7 @@ bahroo + basebanded broadcastify correlator deinterleave diff --git a/.idea/misc.xml b/.idea/misc.xml index 7976fb14..1db132d2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index fcd1e883..9aac098a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -20,7 +20,6 @@ plugins { id 'application' id 'java' id 'idea' - id 'org.openjfx.javafxplugin' version '0.0.10' id 'org.beryx.runtime' version '1.12.7' } @@ -32,12 +31,6 @@ repositories { version = '0.5.0-alpha7' sourceCompatibility = '17' -javafx { - version = "17.0.1" - modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing'] - configuration = "implementation" -} - sourceSets { main.java.srcDirs 'src/main' test.java.srcDirs 'src/test' @@ -47,12 +40,6 @@ test { useJUnitPlatform() } -configurations { - javafx_libs_linux - javafx_libs_osx - javafx_libs_win -} - dependencies { // JUnit Tests @@ -100,53 +87,21 @@ dependencies { implementation 'org.usb4java:usb4java:1.3.0' implementation 'org.usb4java:usb4java-javax:1.3.0' implementation 'pl.edu.icm:JLargeArrays:1.6' - - //JavaFX libraries for Linux - javafx_libs_linux "org.openjfx:javafx-base:$javafx.version:linux" - javafx_libs_linux "org.openjfx:javafx-controls:$javafx.version:linux" - javafx_libs_linux "org.openjfx:javafx-graphics:$javafx.version:linux" - javafx_libs_linux "org.openjfx:javafx-swing:$javafx.version:linux" - - //JavaFX libraries for OSX - javafx_libs_osx "org.openjfx:javafx-base:$javafx.version:mac" - javafx_libs_osx "org.openjfx:javafx-controls:$javafx.version:mac" - javafx_libs_osx "org.openjfx:javafx-graphics:$javafx.version:mac" - javafx_libs_osx "org.openjfx:javafx-swing:$javafx.version:mac" - - //JavaFX libraries for Windows - javafx_libs_win "org.openjfx:javafx-base:$javafx.version:win" - javafx_libs_win "org.openjfx:javafx-controls:$javafx.version:win" - javafx_libs_win "org.openjfx:javafx-graphics:$javafx.version:win" - javafx_libs_win "org.openjfx:javafx-swing:$javafx.version:win" -} - -application { - mainClass.set("io.github.dsheirer.gui.SDRTrunk") - - //Note: jide-oss.jar requires access to hidden windows look & feel - //Note: controlsfx.jar requires access to hidden javaFX classes - applicationDefaultJvmArgs = ['--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', - '--add-exports=javafx.base/com.sun.javafx.event=org.controlsfx.controls'] } /** - * Runtime JVM arguments. JDK 17 has hidden a number of classes that are no longer exported from the - * modules. Some legacy support libraries still require access to these classes, so we explicitly - * add them to be exported from those modules here. + * This is needed for the JDK17 vector API ... until it moves out of incubation */ -import org.gradle.internal.os.OperatingSystem; +tasks.withType(JavaCompile) { + options.compilerArgs.add("--add-modules=jdk.incubator.vector") +} -run { - if(OperatingSystem.current().isWindows()) { - //Note: jide-oss.jar requires access to hidden windows look & feel - //Note: controlsfx.jar requires access to hidden javaFX classes - jvmArgs = ['--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', - '--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED'] - } - else { - //Note: controlsfx.jar requires access to hidden javaFX classes - jvmArgs = ['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED'] - } +application { + mainClassName = "io.github.dsheirer.gui.SDRTrunk" + applicationDefaultJvmArgs = + ['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar + '--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator + '--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library } jar { @@ -177,13 +132,13 @@ idea { */ def jdk_base = '/home/denny/java_jdks/' -def jdk_linux_arm64 = jdk_base + 'linux-arm64/jdk-17.0.1-full' -def jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-17.0.1-full' -def jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-17.0.1-full.jdk' -def jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-17.0.1-full' +def jdk_linux_arm64 = jdk_base + 'linux-arm64/jdk-17.0.2-full' +def jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-17.0.2-full' +def jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-17.0.2-full.jdk' +def jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-17.0.2-full' def hasTargetJdk = file(jdk_linux_x86_64).exists() || - file(jdk_linux_arm64) || + file(jdk_linux_arm64).exists() || file(jdk_osx_x86_64).exists() || file(jdk_windows_x86_64).exists() @@ -230,66 +185,5 @@ runtime { } options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'] - modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http', - 'java.sql', 'jdk.crypto.ec'] imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip") } - -def image_lib_linux_arm64 = file("$buildDir/image/sdr-trunk-linux-arm64-v" + version + "/lib") -def image_lib_linux_x86_64 = file("$buildDir/image/sdr-trunk-linux-x86_64-v" + version + "/lib") -def image_lib_osx_x86_64 = file("$buildDir/image/sdr-trunk-osx-x86_64-v" + version + "/lib") -def image_lib_win_x86_64 = file("$buildDir/image/sdr-trunk-windows-x86_64-v" + version + "/lib") - -/** - * Copy JavaFX OS-specific libraries for each JDK to each platform target directory - */ -tasks.runtime.doLast -{ - if(file(jdk_linux_arm64).exists()) - { - //Delete any host system OS-specific JavaFX JDK modules that were inserted as part of default build - delete(fileTree(image_lib_linux_arm64).include { it.name ==~ /javafx.*-(win|mac|linux)\.jar/ }) - - //Copy OS-specific JavaFX libraries - copy { - from configurations.javafx_libs_linux - into image_lib_linux_arm64 - } - } - - if(file(jdk_linux_x86_64).exists()) - { - //Delete any host system OS-specific JavaFX JDK modules that were inserted as part of default build - delete(fileTree(image_lib_linux_x86_64).include { it.name ==~ /javafx.*-(win|mac|linux)\.jar/ }) - - //Copy OS-specific JavaFX libraries - copy { - from configurations.javafx_libs_linux - into image_lib_linux_x86_64 - } - } - - if(file(jdk_osx_x86_64).exists()) - { - //Delete any host system OS-specific JavaFX JDK modules that were inserted as part of default build - delete(fileTree(image_lib_osx_x86_64).include { it.name ==~ /javafx.*-(win|mac|linux)\.jar/ }) - - //Copy OS-specific JavaFX libraries - copy { - from configurations.javafx_libs_osx - into image_lib_osx_x86_64 - } - } - - if(file(jdk_windows_x86_64).exists()) - { - //Delete any host system OS-specific JavaFX JDK modules that were inserted as part of default build - delete(fileTree(image_lib_win_x86_64).include { it.name ==~ /javafx.*-(win|mac|linux)\.jar/ }) - - //Copy OS-specific JavaFX libraries - copy { - from configurations.javafx_libs_win - into image_lib_win_x86_64 - } - } -} diff --git a/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java b/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java index 31535c64..7a5391e9 100644 --- a/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java +++ b/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.alias.action; @@ -26,8 +25,8 @@ import io.github.dsheirer.alias.Alias; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.util.ThreadPool; -import javax.swing.JOptionPane; -import java.awt.EventQueue; +import javax.swing.*; +import java.awt.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -92,7 +91,7 @@ public abstract class RecurringAction extends AliasAction */ private void performThreadedAction(final Alias alias, final IMessage message) { - ThreadPool.SCHEDULED.schedule(() -> performAction(alias, message), 0, TimeUnit.SECONDS); + ThreadPool.CACHED.execute(() -> performAction(alias, message)); } @Override diff --git a/src/main/java/io/github/dsheirer/audio/AudioModule.java b/src/main/java/io/github/dsheirer/audio/AudioModule.java index 13d3d2e3..2d984e68 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioModule.java +++ b/src/main/java/io/github/dsheirer/audio/AudioModule.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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; @@ -25,13 +22,13 @@ import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.audio.squelch.ISquelchStateListener; import io.github.dsheirer.audio.squelch.SquelchState; import io.github.dsheirer.audio.squelch.SquelchStateEvent; +import io.github.dsheirer.dsp.filter.FilterFactory; 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.real.IRealFilter; import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,8 +38,8 @@ import org.slf4j.LoggerFactory; * * Incorporates audio squelch state listener to control if audio packets are broadcast or ignored. */ -public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IReusableBufferListener, - Listener +public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IRealBufferListener, + Listener { private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class); private static float[] sHighPassFilterCoefficients; @@ -73,7 +70,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis } } - private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients); + private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients); private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private SquelchState mSquelchState = SquelchState.SQUELCH; @@ -120,22 +117,17 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis } @Override - public void receive(ReusableFloatBuffer audioBuffer) + public void receive(float[] audioBuffer) { if(mSquelchState == SquelchState.UNSQUELCH) { - ReusableFloatBuffer highPassFilteredAudio = mHighPassFilter.filter(audioBuffer); - addAudio(highPassFilteredAudio.getSamplesCopy()); - highPassFilteredAudio.decrementUserCount(); - } - else - { - audioBuffer.decrementUserCount(); + audioBuffer = mHighPassFilter.filter(audioBuffer); + addAudio(audioBuffer); } } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { //Redirect received reusable buffers to the receive(buffer) method return this; diff --git a/src/main/java/io/github/dsheirer/audio/AudioSegmentBroadcaster.java b/src/main/java/io/github/dsheirer/audio/AudioSegmentBroadcaster.java index 43e65f34..b7c54da0 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioSegmentBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/AudioSegmentBroadcaster.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -20,7 +20,6 @@ package io.github.dsheirer.audio; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.AbstractReusableBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java index ab97502e..fb452d64 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.broadcast.icecast; @@ -100,7 +97,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate final String metadataUpdateURL = sb.toString(); URI uri = URI.create(metadataUpdateURL); - ThreadPool.SCHEDULED.submit(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java index 4ef5f396..6530410f 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.broadcast.icecast; @@ -45,7 +42,6 @@ import java.net.ConnectException; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster @@ -164,8 +160,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster } }; - ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS); - + ThreadPool.CACHED.submit(runnable); } return connected(); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java index fe496762..9e8dc9e6 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2016 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,8 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.audio.broadcast.icecast; import io.github.dsheirer.alias.AliasModel; @@ -167,7 +167,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster } }; - ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS); + ThreadPool.CACHED.submit(runnable); } return connected(); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java index 996ef24b..7fe52d40 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2016 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,8 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.audio.broadcast.shoutcast.v1; import io.github.dsheirer.alias.AliasModel; @@ -37,7 +37,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.ConnectException; import java.net.InetSocketAddress; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster @@ -179,7 +178,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster } }; - ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS); + ThreadPool.CACHED.submit(runnable); } return connected(); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java index ce80c707..541c46f3 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.broadcast.shoutcast.v1; @@ -53,7 +50,6 @@ import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.LinkedTransferQueue; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater @@ -138,7 +134,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp if(updateRequest != null) { - ThreadPool.SCHEDULED.schedule(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() @@ -176,7 +172,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp } } } - }, 0l, TimeUnit.SECONDS); + }); } //Fetch next metadata update to send diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java index 9dbf88dc..474b6071 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.broadcast.shoutcast.v2; @@ -67,7 +64,6 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedTransferQueue; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcaster implements IBroadcastMetadataUpdater @@ -225,7 +221,7 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas } }; - ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS); + ThreadPool.CACHED.submit(runnable); } return connected(); diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java index 3f17a518..30ca020e 100644 --- a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java +++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.convert.thumbdv; @@ -36,8 +33,6 @@ import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechRes import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse; import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,11 +77,10 @@ public class ThumbDv implements AutoCloseable private ScheduledFuture mAudioDecodeProcessorHandle; private LinkedTransferQueue mDecodeSpeechRequests = new LinkedTransferQueue<>(); private AudioProtocol mAudioProtocol; - private Listener mAudioBufferListener; - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("ThumbDv"); + private Listener mAudioBufferListener; private boolean mStarted; - public ThumbDv(AudioProtocol audioProtocol, Listener listener) + public ThumbDv(AudioProtocol audioProtocol, Listener listener) { mAudioProtocol = audioProtocol; mAudioBufferListener = listener; @@ -209,8 +203,7 @@ public class ThumbDv implements AutoCloseable if(message instanceof DecodeSpeechResponse && mAudioBufferListener != null) { float[] samples = ((DecodeSpeechResponse)message).getSamples(); - ReusableFloatBuffer audioBuffer = mReusableBufferQueue.getBuffer(samples, System.currentTimeMillis()); - mAudioBufferListener.receive(audioBuffer); + mAudioBufferListener.receive(samples); } else if(message instanceof ReadyResponse && mAudioDecodeProcessorHandle == null) { @@ -456,9 +449,8 @@ public class ThumbDv implements AutoCloseable mLog.debug("Starting thumb dv thread(s)"); - final Listener listener = reusableAudioPacket -> { + final Listener listener = packet -> { mLog.info("Got an audio packet!"); - reusableAudioPacket.decrementUserCount(); }; try(ThumbDv thumbDv = new ThumbDv(AudioProtocol.P25_PHASE2, listener)) diff --git a/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java b/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java index d3e1d269..ba9c3b2d 100644 --- a/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java +++ b/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java @@ -1,27 +1,28 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.invert; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.FloatFIRFilter; -import io.github.dsheirer.dsp.filter.Window.WindowType; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.Oscillator; +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; +import io.github.dsheirer.dsp.window.WindowType; /** * Based on the AudioType applied, produces normal, muted, or audio invert @@ -30,7 +31,7 @@ import io.github.dsheirer.dsp.mixer.Oscillator; public class AudioAdapter { private static int mGain = 5; - private IOscillator mSineWaveGenerator; + private IRealOscillator mSineWaveGenerator; private FloatFIRFilter mPostInversionFilter; private FloatFIRFilter mAudioHighPassFilter; private AudioType mAudioType = AudioType.NORMAL; @@ -39,7 +40,7 @@ public class AudioAdapter public AudioAdapter( int sampleRate ) { mSampleRate = sampleRate; - mSineWaveGenerator = new Oscillator( 3000, mSampleRate ); + mSineWaveGenerator = OscillatorFactory.getRealOscillator(3000, mSampleRate ); mAudioHighPassFilter = new FloatFIRFilter( FilterFactory.getHighPass( mSampleRate, 200, 1600, 48, WindowType.HAMMING, true ), mGain ); @@ -58,7 +59,7 @@ public class AudioAdapter if( inversionFrequency != 0 ) { - mSineWaveGenerator.setFrequency( inversionFrequency );; + mSineWaveGenerator.setFrequency( inversionFrequency ); } } } @@ -89,9 +90,7 @@ public class AudioAdapter default: retVal = (float)( mPostInversionFilter.get( mAudioHighPassFilter.get( sample ) * - mSineWaveGenerator.inphase() ) ); - - mSineWaveGenerator.rotate(); + mSineWaveGenerator.generate(1)[0] ) ); break; } diff --git a/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java b/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java index f91ae5f7..aae21921 100644 --- a/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java +++ b/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java @@ -1,26 +1,26 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.invert; -import io.github.dsheirer.dsp.filter.Filters; import io.github.dsheirer.dsp.filter.FloatFIRFilter; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.Oscillator; +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; import io.github.dsheirer.sample.real.RealSampleListener; /** @@ -29,17 +29,33 @@ import io.github.dsheirer.sample.real.RealSampleListener; */ public class AudioInverter implements RealSampleListener { - private IOscillator mSineWaveGenerator; + private static final float[] LOW_PASS_FILTER = new float[] { -0.0052419787903715655f, + -0.0003789909443307852f, 0.0012970137854134386f, 0.004210460544067679f, + 0.008221266221665309f, 0.012951576579356689f, 0.017794312853913318f, + 0.021959871931790928f, 0.024583800118549094f, 0.024878144443784132f, + 0.022296319647647538f, 0.016685495012933472f, 0.008390268267799136f, + -0.001718264469387386f, -0.012288389660616603f, -0.02161633130497537f, + -0.027882128567957774f, -0.02943377233328899f, -0.025006516193566884f, + -0.014040074880106284f, 0.0032217508351258103f, 0.025660031586092195f, + 0.05136009984280478f, 0.0778336106888181f, 0.10230926486919244f, + 0.12209961841343948f, 0.13496813439824185f, 0.13943117253534673f, + 0.13496813439824185f, 0.12209961841343948f, 0.10230926486919244f, + 0.0778336106888181f, 0.05136009984280478f, 0.025660031586092195f, + 0.0032217508351258103f, -0.014040074880106284f, -0.025006516193566884f, + -0.02943377233328899f, -0.027882128567957774f, -0.02161633130497537f, + -0.012288389660616603f, -0.001718264469387386f, 0.008390268267799136f, + 0.016685495012933472f, 0.022296319647647538f, 0.024878144443784132f, + 0.024583800118549094f, 0.021959871931790928f, 0.017794312853913318f, + 0.012951576579356689f, 0.008221266221665309f, 0.004210460544067679f, + 0.0012970137854134386f, -0.0003789909443307852f, -0.0052419787903715655f }; + private IRealOscillator mSineWaveGenerator; private FloatFIRFilter mPostInversionLowPassFilter; private RealSampleListener mListener; public AudioInverter( int inversionFrequency, int sampleRate ) { - mSineWaveGenerator = new Oscillator( inversionFrequency, sampleRate ); - - mPostInversionLowPassFilter = new FloatFIRFilter( - Filters.FIRLP_55TAP_48000FS_3000FC.getCoefficients(), 1.04f ); - + mSineWaveGenerator = OscillatorFactory.getRealOscillator( inversionFrequency, sampleRate ); + mPostInversionLowPassFilter = new FloatFIRFilter(LOW_PASS_FILTER, 1.04f ); mPostInversionLowPassFilter.setListener( new FilteredSampleProcessor() ); } @@ -60,10 +76,7 @@ public class AudioInverter implements RealSampleListener //the low pass filter if( mPostInversionLowPassFilter != null ) { - mPostInversionLowPassFilter.receive( - sample * mSineWaveGenerator.inphase() ); - - mSineWaveGenerator.rotate(); + mPostInversionLowPassFilter.receive(sample * mSineWaveGenerator.generate(1)[0] ); } } diff --git a/src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java new file mode 100644 index 00000000..f275a95f --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java @@ -0,0 +1,154 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.util.Iterator; + +/** + * Native buffer sample array wrapper class that provides access to a stream of either interleaved or + * non-interleaved complex sample buffers converted from the raw byte sample array. + */ +public class ByteNativeBuffer implements INativeBuffer +{ + private static final int FRAGMENT_SIZE = 2048; + private final static float[] LOOKUP_VALUES; + private float mAverageDc; + + //Creates a static lookup table that converts the 8-bit valued range from 0 - 255 into scaled float values + //of -1.0 to 0 to 1.0 + static + { + LOOKUP_VALUES = new float[256]; + + for(int x = 0; x < 256; x++) + { + LOOKUP_VALUES[x] = (float)(x - 127) / 128.0f; + } + } + + private byte[] mSamples; + private long mTimestamp; + + /** + * Constructs an instance + * @param samples to process + * @param timestamp of the samples + * @param averageDc measured from sample stream + */ + public ByteNativeBuffer(byte[] samples, long timestamp, float averageDc) + { + //Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k + if(samples.length % FRAGMENT_SIZE != 0) + { + throw new IllegalArgumentException("Samples byte[] length [" + samples.length + "] must be an even multiple of " + FRAGMENT_SIZE); + } + + mSamples = samples; + mTimestamp = timestamp; + mAverageDc = averageDc; + } + + @Override + public int sampleCount() + { + return mSamples.length / 2; + } + + @Override + public long getTimestamp() + { + return mTimestamp; + } + + @Override + public Iterator iterator() + { + return new ComplexSamplesIterator(); + } + + @Override + public Iterator iteratorInterleaved() + { + return new InterleavedComplexSamplesIterator(); + } + + private class ComplexSamplesIterator implements Iterator + { + private int mSamplesPointer = 0; + + @Override + public boolean hasNext() + { + return mSamplesPointer < mSamples.length; + } + + @Override + public ComplexSamples next() + { + float[] i = new float[FRAGMENT_SIZE]; + float[] q = new float[FRAGMENT_SIZE]; + int samplesOffset = mSamplesPointer; + + for(int pointer = 0; pointer < i.length; pointer++) + { + i[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mAverageDc; + q[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mAverageDc; + } + + mSamplesPointer = samplesOffset; + + return new ComplexSamples(i, q); + } + } + + /** + * Iterator of interleaved complex samples over the native byte buffer array. + */ + private class InterleavedComplexSamplesIterator implements Iterator + { + private int mSamplesPointer = 0; + + @Override + public boolean hasNext() + { + return mSamplesPointer < mSamples.length; + } + + @Override + public InterleavedComplexSamples next() + { + float[] converted = new float[FRAGMENT_SIZE * 2]; + + int samplesPointer = mSamplesPointer; + + for(int pointer = 0; pointer < converted.length; pointer++) + { + converted[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesPointer++])] - mAverageDc; + } + + mSamplesPointer = samplesPointer; + + return new InterleavedComplexSamples(converted, mTimestamp); + } + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/ByteNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/ByteNativeBufferFactory.java new file mode 100644 index 00000000..489b258a --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/ByteNativeBufferFactory.java @@ -0,0 +1,61 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer; + +import java.nio.ByteBuffer; + +/** + * Implements a factory for creating ByteNativeBuffer instances + */ +public class ByteNativeBufferFactory implements INativeBufferFactory +{ + private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers + private float mAverageDc = 0.0f; + + @Override + public INativeBuffer getBuffer(ByteBuffer samples, long timestamp) + { + byte[] copy = new byte[samples.capacity()]; + samples.get(copy); + calculateDc(copy); + return new ByteNativeBuffer(copy, timestamp, mAverageDc); + } + + /** + * Calculates the average DC in the sample stream so that it can be subtracted from the samples when the + * native buffer is used. + * @param samples containing DC offset + */ + private void calculateDc(byte[] samples) + { + double dcAccumulator = 0; + + for(byte sample: samples) + { + dcAccumulator += (sample & 0xFF); + } + + dcAccumulator /= samples.length; + dcAccumulator -= 127.0f; + dcAccumulator /= 128.0f; + dcAccumulator -= mAverageDc; + mAverageDc += (dcAccumulator * DC_FILTER_GAIN); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java new file mode 100644 index 00000000..e1a9fbf1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java @@ -0,0 +1,145 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.util.Iterator; + +/** + * Simple native buffer implementation that simply wraps a single, existing, non-native sample buffer. + */ +public class FloatNativeBuffer implements INativeBuffer +{ + private float[] mInterleavedComplexSamples; + private long mTimestamp; + + /** + * Constructs an instance + * @param complexSamples to wrap + * @param timestamp for the buffer + */ + public FloatNativeBuffer(float[] complexSamples, long timestamp) + { + mInterleavedComplexSamples = complexSamples; + mTimestamp = timestamp; + } + + /** + * Constructs an instance + * @param complexSamples to wrap + */ + public FloatNativeBuffer(float[] complexSamples) + { + this(complexSamples, System.currentTimeMillis()); + } + + /** + * Constructs an instance + * @param samples to wrap + * @param timestamp for the buffer + */ + public FloatNativeBuffer(ComplexSamples samples, long timestamp) + { + this(SampleUtils.interleave(samples), timestamp); + } + + /** + * Constructs an instance + * @param samples to wrap + */ + public FloatNativeBuffer(InterleavedComplexSamples samples) + { + this(samples.samples(), samples.timestamp()); + } + + @Override + public Iterator iterator() + { + return new ComplexSamplesIterator(); + } + + @Override + public Iterator iteratorInterleaved() + { + return new InterleavedComplexSamplesIterator(); + } + + @Override + public int sampleCount() + { + return mInterleavedComplexSamples.length / 2; + } + + @Override + public long getTimestamp() + { + return mTimestamp; + } + + private class ComplexSamplesIterator implements Iterator + { + private boolean mEmpty; + + @Override + public boolean hasNext() + { + return !mEmpty; + } + + @Override + public ComplexSamples next() + { + if(mEmpty) + { + throw new IllegalStateException("No more samples"); + } + + mEmpty = true; + return SampleUtils.deinterleave(mInterleavedComplexSamples); + } + } + + private class InterleavedComplexSamplesIterator implements Iterator + { + private boolean mEmpty; + + + @Override + public boolean hasNext() + { + return !mEmpty; + } + + @Override + public InterleavedComplexSamples next() + { + if(mEmpty) + { + throw new IllegalStateException("No more samples"); + } + + mEmpty = true; + return new InterleavedComplexSamples(mInterleavedComplexSamples, mTimestamp); + } + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/INativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/INativeBuffer.java new file mode 100644 index 00000000..6122ec44 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/INativeBuffer.java @@ -0,0 +1,53 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.util.Iterator; + +/** + * A buffer of raw samples copied from native (ie outside of the JVM) memory into the JVM heap and + * convertible to an iterable stream of interleaved or non-interleaved sample instances. + */ +public interface INativeBuffer +{ + /** + * Provides an iterator that converts the raw samples to complex samples format with separate (non-interleaved) I and Q sample arrays. + */ + Iterator iterator(); + + /** + * Provides an iterator that converts the raw samples to interleaved complex samples format. + */ + Iterator iteratorInterleaved(); + + /** + * Total number of complex samples for this buffer + */ + int sampleCount(); + + /** + * Timestamp for this buffer + * @return millis since epoch + */ + long getTimestamp(); +} diff --git a/src/main/java/io/github/dsheirer/buffer/INativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/INativeBufferFactory.java new file mode 100644 index 00000000..fb15bb54 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/INativeBufferFactory.java @@ -0,0 +1,39 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer; + +import java.nio.ByteBuffer; + +/** + * Factory for creating native buffer wrapper instances + */ +public interface INativeBufferFactory +{ + /** + * Create a native buffer implementation that wraps the samples argument. + * + * Note: implementations of this factory should treat the samples argument as read-only and only make data copies. + * + * @param samples byte array copied from native memory + * @param timestamp of the samples + * @return instance + */ + INativeBuffer getBuffer(ByteBuffer samples, long timestamp); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java b/src/main/java/io/github/dsheirer/buffer/INativeBufferProvider.java similarity index 61% rename from src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java rename to src/main/java/io/github/dsheirer/buffer/INativeBufferProvider.java index 1779ece9..82b3ad5a 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java +++ b/src/main/java/io/github/dsheirer/buffer/INativeBufferProvider.java @@ -16,27 +16,27 @@ * along with this program. If not, see * **************************************************************************** */ +package io.github.dsheirer.buffer; -package io.github.dsheirer.dsp.filter.decimate; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.Listener; /** - * Interface for a decimation filter for float array sample buffers + * Interface for a provider of interleaved complex samples */ -public interface IComplexDecimationFilter +public interface INativeBufferProvider { /** - * Decimates an array of float complex samples - * @param samples to decimate - * @return decimated samples + * Adds the listener to receive buffers */ - float[] decimateComplex(float[] samples); + void addBufferListener(Listener listener); /** - * Decimates an buffer of float complex samples - * @param buffer to decimate - * @return decimated samples + * Removes the listener from receiving buffers */ - ReusableComplexBuffer decimate(ReusableComplexBuffer buffer); + void removeBufferListener(Listener listener); + + /** + * Indicates if there are any buffer listeners registered + */ + boolean hasBufferListeners(); } diff --git a/src/main/java/io/github/dsheirer/buffer/NativeBufferPoisonPill.java b/src/main/java/io/github/dsheirer/buffer/NativeBufferPoisonPill.java new file mode 100644 index 00000000..0ad836a1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/NativeBufferPoisonPill.java @@ -0,0 +1,55 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.util.Iterator; + +/** + * Poison pill implementation for use with Dispatcher. + */ +public class NativeBufferPoisonPill implements INativeBuffer +{ + @Override + public Iterator iterator() + { + return null; + } + + @Override + public Iterator iteratorInterleaved() + { + return null; + } + + @Override + public int sampleCount() + { + return 0; + } + + @Override + public long getTimestamp() + { + return 0; + } +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexDelayBuffer.java b/src/main/java/io/github/dsheirer/buffer/NativeSampleDelayBuffer.java similarity index 55% rename from src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexDelayBuffer.java rename to src/main/java/io/github/dsheirer/buffer/NativeSampleDelayBuffer.java index f196dcc6..c25792bf 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexDelayBuffer.java +++ b/src/main/java/io/github/dsheirer/buffer/NativeSampleDelayBuffer.java @@ -1,20 +1,24 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ +package io.github.dsheirer.buffer; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,7 +26,7 @@ import org.slf4j.LoggerFactory; import java.util.concurrent.LinkedTransferQueue; /** - * Delay buffer/queue for reusable complex buffers with support for pre-loading new buffer listeners (ie channels) + * Delay buffer/queue with support for pre-loading new listeners (ie channels) * with delayed sample buffers and broadcasting of incoming sample buffers to a list of listeners. * * This class is designed to compensate/account for any delays in processing of a control channel and subsequent @@ -39,15 +43,13 @@ import java.util.concurrent.LinkedTransferQueue; * so as not to delay the stream of sample buffers. Channel listeners are expected to implement buffer queue processing * on another thread. */ -public class ReusableComplexDelayBuffer implements Listener +public class NativeSampleDelayBuffer implements Listener { - private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexDelayBuffer.class); + private final static Logger mLog = LoggerFactory.getLogger(NativeSampleDelayBuffer.class); - private ReusableBufferBroadcaster mBroadcaster = new ReusableBufferBroadcaster<>(); - private LinkedTransferQueue mActionRequestQueue = new LinkedTransferQueue<>(); - private ActionRequest mActionRequest; - - private ReusableComplexBuffer[] mDelayBuffer; + private Broadcaster mBroadcaster = new Broadcaster<>(); + private LinkedTransferQueue mActionQueue = new LinkedTransferQueue<>(); + private INativeBuffer[] mDelayBuffer; private int mDelayBufferPointer = 0; private long mBufferDuration; @@ -57,9 +59,9 @@ public class ReusableComplexDelayBuffer implements Listener= mDelayBuffer.length) - { - mDelayBufferPointer = 0; - } + mDelayBufferPointer %= mDelayBuffer.length; } /** @@ -153,23 +139,18 @@ public class ReusableComplexDelayBuffer implements Listener= listenerToAdd.getTimestamp() || - bufferToEvaluate.getTimestamp() + mBufferDuration >= listenerToAdd.getTimestamp())) + if(toEvaluate != null && + ((toEvaluate.getTimestamp() + mBufferDuration) >= listenerToAdd.getTimestamp())) { - bufferToEvaluate.incrementUserCount(); - listenerToAdd.getListener().receive(bufferToEvaluate); - preloadedBufferCount++; + listenerToAdd.getListener().receive(toEvaluate); } pointer++; @@ -190,28 +171,28 @@ public class ReusableComplexDelayBuffer implements Listener listener, long timestamp) + public void addListener(Listener listener, long timestamp) { - mActionRequestQueue.add(new ActionRequest(listener, timestamp)); + mActionQueue.add(new ActionRequest(listener, timestamp)); } /** * Removes the listener from receiving sample buffers */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { - mActionRequestQueue.add(new ActionRequest(listener)); + mActionQueue.add(new ActionRequest(listener)); } - private enum Action{ADD_USER, REMOVE_USER, CLEAR_BUFFER}; + private enum Action{ADD_LISTENER, REMOVE_LISTENER, CLEAR_BUFFER}; /** * Actions that must be completed on the incoming sample stream thread. */ - public class ActionRequest + public class ActionRequest { private Action mAction; - private Listener mListener; + private Listener mListener; private long mTimestamp; /** @@ -219,9 +200,9 @@ public class ReusableComplexDelayBuffer implements Listener listener, long timestamp) + public ActionRequest(Listener listener, long timestamp) { - mAction = Action.ADD_USER; + mAction = Action.ADD_LISTENER; mListener = listener; mTimestamp = timestamp; } @@ -230,9 +211,9 @@ public class ReusableComplexDelayBuffer implements Listener listener) + public ActionRequest(Listener listener) { - mAction = Action.REMOVE_USER; + mAction = Action.REMOVE_LISTENER; mListener = listener; } @@ -252,7 +233,7 @@ public class ReusableComplexDelayBuffer implements Listener getListener() + public Listener getListener() { return mListener; } diff --git a/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBuffer.java new file mode 100644 index 00000000..0d363213 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBuffer.java @@ -0,0 +1,161 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.util.Iterator; + +/** + * Native buffer sample array wrapper class that provides access to a stream of either interleaved or + * non-interleaved complex sample buffers converted from the raw byte sample array. + */ +public class SignedByteNativeBuffer implements INativeBuffer +{ + private static final int FRAGMENT_SIZE = 2048; + private final static float[] LOOKUP_VALUES; + + //Creates a static lookup table that converts the signed byte values as: + // Index 0 - 127: 0.0f to 1.0f + // Index 128 - 255: -1.0f to -0.0f + static + { + LOOKUP_VALUES = new float[256]; + + for(int x = 0; x < 256; x++) + { + LOOKUP_VALUES[x] = (float)((byte)x) / 128.0f; + } + } + + private byte[] mSamples; + private long mTimestamp; + private float mIAverageDc; + private float mQAverageDc; + + /** + * Constructs an instance + * @param samples to process + * @param timestamp of the samples + * @param iAverageDc of the sample stream + * @param qAverageDc of the sample stream + */ + public SignedByteNativeBuffer(byte[] samples, long timestamp, float iAverageDc, float qAverageDc) + { + //Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k + if(samples.length % FRAGMENT_SIZE != 0) + { + throw new IllegalArgumentException("Samples byte[] length [" + samples.length + "] must be an even multiple of " + FRAGMENT_SIZE); + } + + mSamples = samples; + mTimestamp = timestamp; + mIAverageDc = iAverageDc; + mQAverageDc = qAverageDc; + } + + @Override + public long getTimestamp() + { + return mTimestamp; + } + + @Override + public int sampleCount() + { + return mSamples.length / 2; + } + + @Override + public Iterator iterator() + { + return new ComplexSamplesIterator(); + } + + @Override + public Iterator iteratorInterleaved() + { + return new InterleavedComplexSamplesIterator(); + } + + /** + * Iterator of complex samples over the native byte buffer array + */ + private class ComplexSamplesIterator implements Iterator + { + private int mSamplesPointer = 0; + + @Override + public boolean hasNext() + { + return mSamplesPointer < mSamples.length; + } + + @Override + public ComplexSamples next() + { + float[] i = new float[FRAGMENT_SIZE]; + float[] q = new float[FRAGMENT_SIZE]; + int samplesOffset = mSamplesPointer; + + for(int pointer = 0; pointer < i.length; pointer++) + { + i[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mIAverageDc; + q[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mQAverageDc; + } + + mSamplesPointer = samplesOffset; + return new ComplexSamples(i, q); + } + } + + /** + * Iterator of interleaved complex samples over the native byte buffer array. + */ + private class InterleavedComplexSamplesIterator implements Iterator + { + private int mSamplesPointer = 0; + + @Override + public boolean hasNext() + { + return mSamplesPointer < mSamples.length; + } + + @Override + public InterleavedComplexSamples next() + { + float[] converted = new float[FRAGMENT_SIZE * 2]; + + int samplesOffset = mSamplesPointer; + + for(int pointer = 0; pointer < converted.length; pointer += 2) + { + converted[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mIAverageDc; + converted[pointer + 1] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mQAverageDc; + } + + mSamplesPointer = samplesOffset; + + return new InterleavedComplexSamples(converted, mTimestamp); + } + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBufferFactory.java new file mode 100644 index 00000000..f7a63587 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBufferFactory.java @@ -0,0 +1,68 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer; + +import java.nio.ByteBuffer; + +/** + * Implements a factory for creating SignedByteNativeBuffer instances + */ +public class SignedByteNativeBufferFactory implements INativeBufferFactory +{ + private static final float DC_FILTER_GAIN = 0.02f; //Normalizes DC over period of ~50 (1 / .02) buffers + private float mIAverageDc = 0.0f; + private float mQAverageDc = 0.0f; + + @Override + public INativeBuffer getBuffer(ByteBuffer samples, long timestamp) + { + byte[] copy = new byte[samples.capacity()]; + samples.get(copy); + calculateDc(copy); + return new SignedByteNativeBuffer(copy, timestamp, mIAverageDc, mQAverageDc); + } + + /** + * Calculates the average DC in the sample stream so that it can be subtracted from the samples when the + * native buffer is used. + * @param samples containing DC offset + */ + private void calculateDc(byte[] samples) + { + double iDcAccumulator = 0; + double qDcAccumulator = 0; + + for(int x = 0; x < samples.length; x += 2) + { + iDcAccumulator += samples[x]; + qDcAccumulator += samples[x + 1]; + } + + iDcAccumulator /= (samples.length / 2); + iDcAccumulator /= 128.0f; + iDcAccumulator -= mIAverageDc; + mIAverageDc += (iDcAccumulator * DC_FILTER_GAIN); + + qDcAccumulator /= (samples.length / 2); + qDcAccumulator /= 128.0f; + qDcAccumulator -= mQAverageDc; + mQAverageDc += (qDcAccumulator * DC_FILTER_GAIN); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java new file mode 100644 index 00000000..e41bec7b --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java @@ -0,0 +1,105 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; + +import java.util.Iterator; + +/** + * Base complex samples iterator for raw Airspy sample buffers. Incorporates a Hilbert transform filter + * for converting the real sample array to complex samples. + * @param either ComplexSamples or InterleavedComplexSamples + */ +public abstract class AirspyBufferIterator implements Iterator +{ + protected static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f; + protected static final int FRAGMENT_SIZE = 2048; + protected static final float[] COEFFICIENTS = + HilbertTransform.convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP); + + public static final int I_OVERLAP = 11; + public static final int Q_OVERLAP = 23; + + protected float[] mIBuffer = new float[FRAGMENT_SIZE + I_OVERLAP]; + protected float[] mQBuffer = new float[FRAGMENT_SIZE + Q_OVERLAP]; + protected short[] mSamples; + protected int mSamplesPointer = 0; + protected long mTimestamp; + protected float mAverageDc; + + /** + * Constructs an instance + * @param samples from the airspy. + * @param residualI samples from previous buffer + * @param residualQ samples from previous buffer + * @param averageDc as measured + * @param timestamp of the buffer + */ + public AirspyBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp) + { + if(residualI.length != I_OVERLAP || residualQ.length != Q_OVERLAP) + { + throw new IllegalArgumentException("Residual I length [" + residualI.length + + "] must be " + I_OVERLAP + " and Residual Q length [" + residualQ.length + + "] must be " + Q_OVERLAP); + } + + int requiredInterval = FRAGMENT_SIZE * 2; //requires 4 bytes (2 samples) per fragment + + if(samples.length % requiredInterval != 0) + { + throw new IllegalArgumentException("Samples short array length [" + mSamples.length + + "]must be an integer multiple of " + requiredInterval); + } + + mAverageDc = averageDc; + mSamples = samples; + mTimestamp = timestamp; + + //Transfer and scale the residual I & Q samples from the previous buffer + for(int i = 0; i < residualI.length; i++) + { + mIBuffer[i] = scale(residualI[i], averageDc); + } + + for(int q = 0; q < residualQ.length; q++) + { + mQBuffer[q] = scale(residualQ[q], averageDc); + } + } + + @Override + public boolean hasNext() + { + return mSamplesPointer < mSamples.length; + } + + /** + * Scales the short sample value to a 12-bit floating point and subtracts the average DC offset + * @param value to scale + * @param averageDc detected + * @return scaled and normalized sample + */ + public static float scale(short value, float averageDc) + { + return ((float)value - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT - averageDc; + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java new file mode 100644 index 00000000..805f8f43 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java @@ -0,0 +1,96 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +import java.util.Arrays; + +/** + * Scalar implementation for non-packed Airspy native buffers + */ +public class AirspyBufferIteratorScalar extends AirspyBufferIterator +{ + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + } + + @Override + public ComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc); + mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc); + } + + mSamplesPointer = offset; + + //The i line is a simple delay line, so we'll just copy those as a new array here. + float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); + float[] q = new float[FRAGMENT_SIZE]; + + float accumulator; + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = 0; + + for(int tap = 0; tap < COEFFICIENTS.length; tap++) + { + accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap]; + } + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + q[x] = accumulator; + } + else + { + i[x] = -i[x]; + q[x] = -accumulator; + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java new file mode 100644 index 00000000..caa10e65 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java @@ -0,0 +1,126 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +import java.util.Arrays; + +/** + * Vector SIMD implementation for non-packed Airspy native buffers + */ +public class AirspyBufferIteratorVector128Bits extends AirspyBufferIterator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + } + + @Override + public ComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + int fragmentPointer = 0; + float[] scaledSamples = new float[VECTOR_SPECIES.length()]; + + while(fragmentPointer < FRAGMENT_SIZE) + { + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + //Couldn't figure out an easy way to load as a short vector and recast to a float vector + scaledSamples[y] = mSamples[offset++]; + } + + //SIMD scaling operation + FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) + .sub(2048.0f) + .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) + .sub(mAverageDc) + .intoArray(scaledSamples, 0); + + for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) + { + mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; + mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; + } + } + + mSamplesPointer = offset; + + //The i line is a simple delay line, so we'll just copy those as a new array here. + float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); + float[] q = new float[FRAGMENT_SIZE]; + + FloatVector accumulator; + FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); + FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4); + FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); + FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12); + FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); + FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20); + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); + accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); + accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + else + { + i[x] = -i[x]; + q[x] = -accumulator.reduceLanes(VectorOperators.ADD); + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java new file mode 100644 index 00000000..573fb699 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java @@ -0,0 +1,120 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +import java.util.Arrays; + +/** + * Vector SIMD implementation for non-packed Airspy native buffers + */ +public class AirspyBufferIteratorVector256Bits extends AirspyBufferIterator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + } + + @Override + public ComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + int fragmentPointer = 0; + float[] scaledSamples = new float[VECTOR_SPECIES.length()]; + + while(fragmentPointer < FRAGMENT_SIZE) + { + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + //Couldn't figure out an easy way to load as a short vector and recast to a float vector + scaledSamples[y] = mSamples[offset++]; + } + + //SIMD scaling operation + FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) + .sub(2048.0f) + .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) + .sub(mAverageDc) + .intoArray(scaledSamples, 0); + + for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) + { + mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; + mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; + } + } + + mSamplesPointer = offset; + + //The i line is a simple delay line, so we'll just copy those as a new array here. + float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); + float[] q = new float[FRAGMENT_SIZE]; + + FloatVector accumulator; + FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); + FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); + FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + else + { + i[x] = -i[x]; + q[x] = -accumulator.reduceLanes(VectorOperators.ADD); + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java new file mode 100644 index 00000000..bce6aa31 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java @@ -0,0 +1,122 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +import java.util.Arrays; + +/** + * Vector SIMD implementation for non-packed Airspy native buffers + */ +public class AirspyBufferIteratorVector512Bits extends AirspyBufferIterator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private float[] mFilterPart2; + + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + //Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD + mFilterPart2 = new float[16]; + System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8); + } + + @Override + public ComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + int fragmentPointer = 0; + float[] scaledSamples = new float[VECTOR_SPECIES.length()]; + + while(fragmentPointer < FRAGMENT_SIZE) + { + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + //Couldn't figure out an easy way to load as a short vector and recast to a float vector + scaledSamples[y] = mSamples[offset++]; + } + + //SIMD scaling operation + FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) + .sub(2048.0f) + .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) + .sub(mAverageDc) + .intoArray(scaledSamples, 0); + + for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) + { + mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; + mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; + } + } + + mSamplesPointer = offset; + + //The i line is a simple delay line, so we'll just copy those as a new array here. + float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); + float[] q = new float[FRAGMENT_SIZE]; + + FloatVector accumulator; + FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); + FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0); + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + else + { + i[x] = -i[x]; + q[x] = -accumulator.reduceLanes(VectorOperators.ADD); + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector64Bits.java new file mode 100644 index 00000000..48e34c6a --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector64Bits.java @@ -0,0 +1,138 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +import java.util.Arrays; + +/** + * Vector SIMD implementation for non-packed Airspy native buffers + */ +public class AirspyBufferIteratorVector64Bits extends AirspyBufferIterator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + } + + @Override + public ComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + int fragmentPointer = 0; + float[] scaledSamples = new float[VECTOR_SPECIES.length()]; + + while(fragmentPointer < FRAGMENT_SIZE) + { + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + //Couldn't figure out an easy way to load as a short vector and recast to a float vector + scaledSamples[y] = mSamples[offset++]; + } + + //SIMD scaling operation + FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) + .sub(2048.0f) + .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) + .sub(mAverageDc) + .intoArray(scaledSamples, 0); + + for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) + { + mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; + mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; + } + } + + mSamplesPointer = offset; + + //The i line is a simple delay line, so we'll just copy those as a new array here. + float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE); + float[] q = new float[FRAGMENT_SIZE]; + + FloatVector accumulator; + FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); + FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2); + FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4); + FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6); + FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); + FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10); + FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12); + FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14); + FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); + FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18); + FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20); + FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22); + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator); + accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); + accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator); + accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator); + accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); + accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator); + accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator); + accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); + accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator); + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + else + { + i[x] = -i[x]; + q[x] = -accumulator.reduceLanes(VectorOperators.ADD); + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java new file mode 100644 index 00000000..1e1e1e77 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java @@ -0,0 +1,94 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +/** + * Scalar implementation for non-packed Airspy native buffers + */ +public class AirspyInterleavedBufferIteratorScalar extends AirspyBufferIterator +{ + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, + long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + } + + @Override + public InterleavedComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc); + mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc); + } + + mSamplesPointer = offset; + + float[] samples = new float[FRAGMENT_SIZE * 2]; + + float accumulator; + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = 0; + + for(int tap = 0; tap < COEFFICIENTS.length; tap++) + { + accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap]; + } + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + samples[2 * x] = mIBuffer[x]; + samples[2 * x + 1] = accumulator; + } + else + { + samples[2 * x] = -mIBuffer[x]; + samples[2 * x + 1] = -accumulator; + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new InterleavedComplexSamples(samples, mTimestamp); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java new file mode 100644 index 00000000..9c244231 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java @@ -0,0 +1,123 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector SIMD implementation for non-packed Airspy native buffers + */ +public class AirspyInterleavedBufferIteratorVector128Bits extends AirspyBufferIterator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, + long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + } + + @Override + public InterleavedComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + int fragmentPointer = 0; + float[] scaledSamples = new float[VECTOR_SPECIES.length()]; + + while(fragmentPointer < FRAGMENT_SIZE) + { + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + //Couldn't figure out an easy way to load as a short vector and recast to a float vector + scaledSamples[y] = mSamples[offset++]; + } + + //SIMD scaling operation + FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) + .sub(2048.0f) + .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) + .sub(mAverageDc) + .intoArray(scaledSamples, 0); + + for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) + { + mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; + mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; + } + } + + mSamplesPointer = offset; + + float[] samples = new float[FRAGMENT_SIZE * 2]; + FloatVector accumulator; + FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); + FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4); + FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); + FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12); + FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); + FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20); + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); + accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); + accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + samples[2 * x] = mIBuffer[x]; + samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD); + } + else + { + samples[2 * x] = -mIBuffer[x]; + samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD); + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new InterleavedComplexSamples(samples, mTimestamp); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java new file mode 100644 index 00000000..0cf7b8ac --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java @@ -0,0 +1,117 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector SIMD implementation for non-packed Airspy native buffers + */ +public class AirspyInterleavedBufferIteratorVector256Bits extends AirspyBufferIterator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, + long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + } + + @Override + public InterleavedComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + int fragmentPointer = 0; + float[] scaledSamples = new float[VECTOR_SPECIES.length()]; + + while(fragmentPointer < FRAGMENT_SIZE) + { + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + //Couldn't figure out an easy way to load as a short vector and recast to a float vector + scaledSamples[y] = mSamples[offset++]; + } + + //SIMD scaling operation + FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) + .sub(2048.0f) + .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) + .sub(mAverageDc) + .intoArray(scaledSamples, 0); + + for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) + { + mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; + mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; + } + } + + mSamplesPointer = offset; + + float[] samples = new float[FRAGMENT_SIZE * 2]; + FloatVector accumulator; + FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); + FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); + FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + samples[2 * x] = mIBuffer[x]; + samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD); + } + else + { + samples[2 * x] = -mIBuffer[x]; + samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD); + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new InterleavedComplexSamples(samples, mTimestamp); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java new file mode 100644 index 00000000..1ee39ca1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java @@ -0,0 +1,120 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector SIMD implementation for non-packed Airspy native buffers + */ +public class AirspyInterleavedBufferIteratorVector512Bits extends AirspyBufferIterator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private float[] mFilterPart2; + + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, + long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + + //Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD + mFilterPart2 = new float[16]; + System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8); + } + + @Override + public InterleavedComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + int fragmentPointer = 0; + float[] scaledSamples = new float[VECTOR_SPECIES.length()]; + + while(fragmentPointer < FRAGMENT_SIZE) + { + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + //Couldn't figure out an easy way to load as a short vector and recast to a float vector + scaledSamples[y] = mSamples[offset++]; + } + + //SIMD scaling operation + FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) + .sub(2048.0f) + .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) + .sub(mAverageDc) + .intoArray(scaledSamples, 0); + + for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) + { + mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; + mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; + } + } + + mSamplesPointer = offset; + + float[] samples = new float[FRAGMENT_SIZE * 2]; + FloatVector accumulator; + FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); + FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0); + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + samples[2 * x] = mIBuffer[x]; + samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD); + } + else + { + samples[2 * x] = -mIBuffer[x]; + samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD); + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new InterleavedComplexSamples(samples, mTimestamp); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java new file mode 100644 index 00000000..cdc714af --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java @@ -0,0 +1,135 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector SIMD implementation for non-packed Airspy native buffers + */ +public class AirspyInterleavedBufferIteratorVector64Bits extends AirspyBufferIterator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + + /** + * Constructs an instance + * + * @param samples from the airspy, either packed or unpacked. + * @param residualI samples from last buffer + * @param residualQ samples from last buffer + * @param averageDc measured + * @param timestamp of the buffer + */ + public AirspyInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, + long timestamp) + { + super(samples, residualI, residualQ, averageDc, timestamp); + } + + @Override + public InterleavedComplexSamples next() + { + if(mSamplesPointer >= mSamples.length) + { + throw new IllegalStateException("End of buffer exceeded"); + } + + int offset = mSamplesPointer; + int fragmentPointer = 0; + float[] scaledSamples = new float[VECTOR_SPECIES.length()]; + + while(fragmentPointer < FRAGMENT_SIZE) + { + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + //Couldn't figure out an easy way to load as a short vector and recast to a float vector + scaledSamples[y] = mSamples[offset++]; + } + + //SIMD scaling operation + FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0) + .sub(2048.0f) + .mul(SCALE_SIGNED_12_BIT_TO_FLOAT) + .sub(mAverageDc) + .intoArray(scaledSamples, 0); + + for(int y = 0; y < VECTOR_SPECIES.length(); y += 2) + { + mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y]; + mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1]; + } + } + + mSamplesPointer = offset; + + float[] samples = new float[FRAGMENT_SIZE * 2]; + FloatVector accumulator; + FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0); + FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2); + FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4); + FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6); + FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8); + FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10); + FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12); + FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14); + FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16); + FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18); + FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20); + FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22); + + for(int x = 0; x < FRAGMENT_SIZE; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator); + accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); + accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator); + accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator); + accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); + accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator); + accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator); + accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); + accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator); + + //Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc. + if(x % 2 == 0) + { + samples[2 * x] = mIBuffer[x]; + samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD); + } + else + { + samples[2 * x] = -mIBuffer[x]; + samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD); + } + } + + //Copy residual end samples to beginning of buffers for the next iteration + System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP); + System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP); + + return new InterleavedComplexSamples(samples, mTimestamp); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java new file mode 100644 index 00000000..7459331b --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java @@ -0,0 +1,109 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; +import io.github.dsheirer.vector.calibrate.Implementation; + +import java.util.Iterator; + +/** + * Native buffer scalar implementation for Airspy non-packed samples. + */ +public class AirspyNativeBuffer implements INativeBuffer +{ + private short[] mSamples; + private short[] mResidualI; + private short[] mResidualQ; + private float mAverageDc; + private long mTimestamp; + private Implementation mInterleavedImplementation; + private Implementation mNonInterleavedImplementation; + + /** + * Constructs an instance + * @param samples (non-packed) from the airspy device + * @param residualI samples from previous buffer + * @param residualQ samples from previous buffer + * @param averageDc measured + * @param timestamp of the buffer + * @param interleavedImplementation optimal, scalar vs vector SIMD + * @param nonInterleavedImplementation optimal, scalar vs vector SIMD + */ + public AirspyNativeBuffer(short[] samples, short[] residualI, short[] residualQ, float averageDc, + long timestamp, Implementation interleavedImplementation, + Implementation nonInterleavedImplementation) + { + //Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k + if(samples.length % AirspyBufferIterator.FRAGMENT_SIZE != 0) + { + throw new IllegalArgumentException("Samples short[] length [" + samples.length + + "] must be an even multiple of " + AirspyBufferIterator.FRAGMENT_SIZE); + } + + mSamples = samples; + mResidualI = residualI; + mResidualQ = residualQ; + mAverageDc = averageDc; + mTimestamp = timestamp; + mInterleavedImplementation = interleavedImplementation; + mNonInterleavedImplementation = nonInterleavedImplementation; + } + + @Override + public int sampleCount() + { + return mSamples.length / 2; + } + + @Override + public long getTimestamp() + { + return mTimestamp; + } + + @Override + public Iterator iterator() + { + return switch(mInterleavedImplementation) + { + case VECTOR_SIMD_512 -> new AirspyBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + case VECTOR_SIMD_256-> new AirspyBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + case VECTOR_SIMD_128-> new AirspyBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + case VECTOR_SIMD_64 ->new AirspyBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + default -> new AirspyBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + }; + } + + @Override + public Iterator iteratorInterleaved() + { + return switch(mInterleavedImplementation) + { + case VECTOR_SIMD_512 -> new AirspyInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + case VECTOR_SIMD_256-> new AirspyInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + case VECTOR_SIMD_128-> new AirspyInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + case VECTOR_SIMD_64 ->new AirspyInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + default -> new AirspyInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp); + }; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java new file mode 100644 index 00000000..b8c349eb --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java @@ -0,0 +1,136 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.INativeBufferFactory; +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * Implements a factory for creating SignedByteNativeBuffer instances + */ +public class AirspyNativeBufferFactory implements INativeBufferFactory +{ + private boolean mSamplePacking = false; + private short[] mResidualI = new short[AirspyBufferIterator.I_OVERLAP]; + private short[] mResidualQ = new short[AirspyBufferIterator.Q_OVERLAP]; + private IAirspySampleConverter mConverter; + private Implementation mInterleavedIteratorImplementation; + private Implementation mNonInterleavedIteratorImplementation; + + /** + * Constructs an instance + */ + public AirspyNativeBufferFactory() + { + updateConverter(); + + mInterleavedIteratorImplementation = CalibrationManager.getInstance() + .getImplementation(CalibrationType.AIRSPY_UNPACKED_INTERLEAVED_ITERATOR); + mNonInterleavedIteratorImplementation = CalibrationManager.getInstance() + .getImplementation(CalibrationType.AIRSPY_UNPACKED_ITERATOR); + } + + /** + * Sample packing places two 12-bit samples into 3 bytes when enabled or + * places two 12-bit samples into 4 bytes when disabled. + * + * @param enabled + */ + public void setSamplePacking(boolean enabled) + { + mSamplePacking = enabled; + updateConverter(); + } + + /** + * Creates an optimal sample converter instance. + */ + private void updateConverter() + { + if(mSamplePacking) + { + if(!(mConverter instanceof ScalarPackedSampleConverter)) + { + mConverter = new ScalarPackedSampleConverter(); + } + } + else + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.AIRSPY_SAMPLE_CONVERTER); + + if(implementation == Implementation.VECTOR_SIMD_PREFERRED) + { + if(!(mConverter instanceof VectorUnpackedSampleConverter)) + { + mConverter = new VectorUnpackedSampleConverter(); + } + } + else + { + if(!(mConverter instanceof ScalarUnpackedSampleConverter)) + { + mConverter = new ScalarUnpackedSampleConverter(); + } + } + } + } + + @Override + public INativeBuffer getBuffer(ByteBuffer buffer, long timestamp) + { + short[] samples = mConverter.convert(buffer); + + INativeBuffer nativeBuffer = new AirspyNativeBuffer(samples, + Arrays.copyOf(mResidualI, mResidualI.length), + Arrays.copyOf(mResidualQ, mResidualQ.length), mConverter.getAverageDc(), timestamp, + mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation); + + extractResidual(samples); + + return nativeBuffer; + } + + /** + * Extracts the residual overlap samples needed for continuity in the Hilbert transform filter. + * @param samples to extract residual from + */ + private void extractResidual(short[] samples) + { + int offset = samples.length - (AirspyBufferIterator.I_OVERLAP * 2); + + for(int i = 0; i < AirspyBufferIterator.I_OVERLAP; i++) + { + mResidualI[i] = samples[offset + (2 * i)]; + } + + offset = samples.length - (AirspyBufferIterator.Q_OVERLAP * 2) + 1; + + for(int q = 0; q < AirspyBufferIterator.Q_OVERLAP; q++) + { + mResidualQ[q] = samples[offset + (2 * q)]; + } + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java b/src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java new file mode 100644 index 00000000..8164f37c --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java @@ -0,0 +1,40 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import java.nio.ByteBuffer; + +/** + * Interface to convert airspy samples from a byte buffer to an array of shorts. + */ +public interface IAirspySampleConverter +{ + /** + * Converts the airspy byte samples contained in the byte buffer to their short-valued representation + * @param buffer of airspy samples + * @return converted samples + */ + short[] convert(ByteBuffer buffer); + + /** + * Current DC average of the samples that have been processed thus far. + */ + float getAverageDc(); +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java new file mode 100644 index 00000000..0f1e0d60 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java @@ -0,0 +1,98 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import java.nio.ByteBuffer; + +/** + * Scalar implementation of airspy sample converter for un-packed samples. + */ +public class ScalarPackedSampleConverter implements IAirspySampleConverter +{ + private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers + private float mAverageDc = 0.0f; + + @Override + public short[] convert(ByteBuffer buffer) + { + int offset = 0; + long dcAccumulator = 0; + short sample; + short[] samples; + byte b1, b2, b3; + + samples = new short[buffer.capacity() / 3 * 2]; + + for(int x = 0; x < samples.length; x += 2) + { + b1 = buffer.get(offset++); + b2 = buffer.get(offset++); + b3 = buffer.get(offset++); + + sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4)); + samples[x] = sample; + dcAccumulator += sample; + + sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF)); + samples[x] = sample; + dcAccumulator += sample; + } + + //Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples + float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f; + averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT; + averageDcNow -= mAverageDc; + mAverageDc += (averageDcNow * DC_FILTER_GAIN); + + return samples; + } + + @Override + public float getAverageDc() + { + return mAverageDc; + } + + public static void main(String[] args) + { + byte[] raw = new byte[262144]; + ByteBuffer buffer = ByteBuffer.wrap(raw); + + ScalarPackedSampleConverter scalar = new ScalarPackedSampleConverter(); + VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter(); + + long accumulator = 0; + int iterations = 1_000_000; + + System.out.println("Starting ..."); + long start = System.currentTimeMillis(); + for(int count = 0; count < iterations; count++) + { +// short[] samples = scalar.convert(buffer); + short[] samples = vector.convert(buffer); + accumulator += samples[1]; + } + long elapsed = System.currentTimeMillis() - start; + + System.out.println("Accumulator: " + accumulator); + System.out.println("Elapsed: " + (elapsed / 1000.0f)); + System.out.println("Finished"); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java new file mode 100644 index 00000000..e2a11c4a --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java @@ -0,0 +1,66 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import java.nio.ByteBuffer; + +/** + * Scalar implementation of airspy sample converter for un-packed samples. + */ +public class ScalarUnpackedSampleConverter implements IAirspySampleConverter +{ + private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers + private float mAverageDc = 0.0f; + + @Override + public short[] convert(ByteBuffer buffer) + { + int offset = 0; + long dcAccumulator = 0; + short sample; + short[] samples; + byte b1, b2; + + samples = new short[buffer.capacity() / 2]; + + for(int x = 0; x < samples.length; x++) + { + b1 = buffer.get(offset++); + b2 = buffer.get(offset++); + sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF)); + samples[x] = sample; + dcAccumulator += sample; + } + + //Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples + float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f; + averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT; + averageDcNow -= mAverageDc; + mAverageDc += (averageDcNow * DC_FILTER_GAIN); + + return samples; + } + + @Override + public float getAverageDc() + { + return mAverageDc; + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java new file mode 100644 index 00000000..f94acf1e --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java @@ -0,0 +1,96 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.airspy; + +import jdk.incubator.vector.ShortVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +import java.nio.ByteBuffer; + +/** + * Scalar implementation of airspy sample converter for un-packed samples. + */ +public class VectorUnpackedSampleConverter implements IAirspySampleConverter +{ + private static final VectorSpecies VECTOR_SPECIES = ShortVector.SPECIES_PREFERRED; + private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers + private float mAverageDc = 0.0f; + + @Override + public short[] convert(ByteBuffer buffer) + { + long dcAccumulator = 0; + short sample; + short[] samples; + byte b1, b2; + + samples = new short[buffer.capacity() / 2]; + + int bytesOffset; + int rawPointer = 0; + ShortVector vector; + + int samplesOffset = 0; + + short[] bytes1 = new short[VECTOR_SPECIES.length()]; + short[] bytes2 = new short[VECTOR_SPECIES.length()]; + + for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length()) + { + bytesOffset = 0; + + while(bytesOffset < VECTOR_SPECIES.length()) + { + bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF); + bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F); + } + + vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0) + .or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8)); + dcAccumulator += vector.reduceLanes(VectorOperators.ADD); + vector.intoArray(samples, samplesOffset); + } + + for(; samplesOffset < samples.length; samplesOffset++) + { + b1 = buffer.get(rawPointer++); + b2 = buffer.get(rawPointer++); + + sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF)); + samples[samplesOffset] = sample; + dcAccumulator += sample; + } + + //Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples + float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f; + averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT; + averageDcNow -= mAverageDc; + mAverageDc += (averageDcNow * DC_FILTER_GAIN); + + return samples; + } + + @Override + public float getAverageDc() + { + return mAverageDc; + } +} diff --git a/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java b/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java index 10a5772c..e8b28120 100644 --- a/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java +++ b/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.channel.state; @@ -57,6 +54,9 @@ public abstract class AbstractChannelState extends Module implements IChannelEve protected boolean mSourceOverflow = false; private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver(); + + //TODO: remove the IOverflowListener code from this class + /** * Constructs an instance * @param channel configuration diff --git a/src/main/java/io/github/dsheirer/controller/NamingThreadFactory.java b/src/main/java/io/github/dsheirer/controller/NamingThreadFactory.java index 7bb8ac44..1f675ac4 100644 --- a/src/main/java/io/github/dsheirer/controller/NamingThreadFactory.java +++ b/src/main/java/io/github/dsheirer/controller/NamingThreadFactory.java @@ -1,3 +1,22 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.controller; import io.github.dsheirer.util.ThreadPool; @@ -25,8 +44,7 @@ public class NamingThreadFactory implements ThreadFactory public NamingThreadFactory( String prefix ) { mThreadGroup = Thread.currentThread().getThreadGroup(); - - mNamePrefix = prefix + " pool-" + mPoolNumber.getAndIncrement() + "-thread-"; + mNamePrefix = prefix + " thread "; } public Thread newThread( Runnable runnable ) diff --git a/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java b/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java index f2b76925..0f11bf73 100644 --- a/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java +++ b/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,17 +14,16 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.dsp.afsk; import io.github.dsheirer.bits.IBinarySymbolProcessor; import io.github.dsheirer.buffer.FloatAveragingBuffer; import io.github.dsheirer.dsp.filter.resample.RealResampler; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.Oscillator; +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +37,7 @@ import org.slf4j.LoggerFactory; * * Provides normal or inverted decoded output. */ -public class AFSK1200Decoder implements Listener +public class AFSK1200Decoder implements Listener { private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class); @@ -71,7 +69,7 @@ public class AFSK1200Decoder implements Listener private boolean mSampleDecision; //Resample to an integral of the baud rate 1200 baud * 6 samples per symbol = 7200.0 Hertz - private RealResampler mResampler = new RealResampler(8000.0, SAMPLE_RATE, 2000, 1); + private RealResampler mResampler = new RealResampler(8000.0, SAMPLE_RATE, 8192, 512); /** * Constructs a decoder using the provided arguments. @@ -115,7 +113,7 @@ public class AFSK1200Decoder implements Listener * @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling. */ @Override - public void receive(ReusableFloatBuffer buffer) + public void receive(float[] buffer) { mResampler.resample(buffer); } @@ -147,17 +145,15 @@ public class AFSK1200Decoder implements Listener } - public class Decoder implements Listener + public class Decoder implements Listener { @Override - public void receive(ReusableFloatBuffer buffer) + public void receive(float[] buffer) { //Calculate correlation values against each 1200/1800 reference signal mCorrelationValuesMark = mCorrelatorMark.process(buffer); mCorrelationValuesSpace = mCorrelatorSpace.process(buffer); - buffer.decrementUserCount(); - for(int x = 0; x < mCorrelationValuesMark.length; x++) { //1200 = Mark (1) and 1800 = Space (0) @@ -207,8 +203,8 @@ public class AFSK1200Decoder implements Listener { mAveragingBuffer = new FloatAveragingBuffer(averagingPeriod); - IOscillator referenceSignalGenerator = new Oscillator(frequency, sampleRate); - mReferenceSamples = referenceSignalGenerator.generateReal(correlationPeriod); + IRealOscillator referenceSignalGenerator = OscillatorFactory.getRealOscillator(frequency, sampleRate); + mReferenceSamples = referenceSignalGenerator.generate(correlationPeriod); mDemodulatedSamples = new float[correlationPeriod]; } @@ -217,18 +213,16 @@ public class AFSK1200Decoder implements Listener * Processes each sample in the incoming sample buffer against a generated reference sample set for one symbol * period to derive a correlation value that is in-turn averaged over one symbol period. * - * @param reusableFloatBuffer containing FM demodulated samples + * @param samples containing FM demodulated samples * @return a reusable array of correlation values for each sample */ - public float[] process(ReusableFloatBuffer reusableFloatBuffer) + public float[] process(float[] samples) { - if(mCorrelationValues == null || mCorrelationValues.length != reusableFloatBuffer.getSampleCount()) + if(mCorrelationValues == null || mCorrelationValues.length != samples.length) { - mCorrelationValues = new float[reusableFloatBuffer.getSampleCount()]; + mCorrelationValues = new float[samples.length]; } - float[] samples = reusableFloatBuffer.getSamples(); - int y; for(int x = 0; x < samples.length; x++) diff --git a/src/main/java/io/github/dsheirer/dsp/afsk/AFSKTest.java b/src/main/java/io/github/dsheirer/dsp/afsk/AFSKTest.java deleted file mode 100644 index 8c44b959..00000000 --- a/src/main/java/io/github/dsheirer/dsp/afsk/AFSKTest.java +++ /dev/null @@ -1,123 +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.dsp.afsk; - -import io.github.dsheirer.buffer.FloatAveragingBuffer; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.Oscillator; -import io.github.dsheirer.sample.complex.Complex; -import org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.DecimalFormat; - -public class AFSKTest -{ - private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class); - - private static final DecimalFormat df = new DecimalFormat("00.000"); - - public static String format(double value) - { - if(value > 0) - { - return " " + df.format(value); - } - else - { - return df.format(value); - } - } - - public static float[] generateAFSK1200(boolean[] symbols, int samplesPerSymbol, float gain) - { - float[] samples = new float[symbols.length * samplesPerSymbol]; - - Oscillator oscillator = new Oscillator(1200.0, (double)samplesPerSymbol * 1200); - - for(int x = 0; x < symbols.length; x++) - { - oscillator.setFrequency(symbols[x] ? 1200.0 : 1800.0); - - for(int y = 0; y < samplesPerSymbol; y++) - { - Complex sample = new Complex(oscillator.inphase(), oscillator.quadrature()); - sample.multiply(gain); - samples[x * samplesPerSymbol + y] = sample.inphase(); - oscillator.rotate(); - } - } - - return samples; - } - - public static void main(String[] args) - { - int sampleSize = 6; - - FloatAveragingBuffer avg1200 = new FloatAveragingBuffer(sampleSize); - FloatAveragingBuffer avg1800 = new FloatAveragingBuffer(sampleSize); - - IOscillator oscillator1200 = new Oscillator(1200.0, 7200.0); - IOscillator oscillator1800 = new Oscillator(1800.0, 7200.0); - oscillator1200.generateReal(1); - oscillator1800.generateReal(1); - - float[] reference1200 = oscillator1200.generateReal(sampleSize); - float[] reference1800 = oscillator1800.generateReal(sampleSize); - - boolean[] symbols = new boolean[]{true,false,true,false,true,false,true,false,true,true,true,false,true,false, - false,false,true,false,false,false,false,true,false,true,true,true,true,false,true,true,false,true,false, - true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true, - false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false, - true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true, - false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false, - true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true, - false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false, - true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, - false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false}; - - float[] samples = generateAFSK1200(symbols, 6, 0.5f); - - mLog.debug("Length:" + samples.length); - - int offset = 0; - - float correlation1200 = 0.0f; - float correlation1800 = 0.0f; - - while(offset + sampleSize < samples.length) - { - correlation1200 = 0.0f; - correlation1800 = 0.0f; - - for(int x = 0; x < sampleSize; x++) - { - correlation1200 += reference1200[x] * samples[x + offset]; - correlation1800 += reference1800[x] * samples[x + offset]; - } - - correlation1200 = avg1200.get(FastMath.abs(correlation1200)); - correlation1800 = avg1800.get(FastMath.abs(correlation1800)); - - mLog.debug(offset + " 1200:" + format(correlation1200) + " 1800:" + format(correlation1800) + - " Decision: " + (correlation1200 > correlation1800 ? "0" : "1")); - - offset++; - } - } -} 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 7e540441..6c5ddf0f 100644 --- a/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java @@ -1,32 +1,30 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.am; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import org.apache.commons.math3.util.FastMath; /** * Performs AM demodulation on baseband I/Q samples to produce demodulated float output. */ -public class AMDemodulator +public class AMDemodulator implements IAmDemodulator { - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("AMDemodulator"); - private int mOutputBufferPointer; private float mGain; /** @@ -59,25 +57,15 @@ public class AMDemodulator return (float) FastMath.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 ReusableFloatBuffer demodulate(ReusableComplexBuffer complexBuffer) + @Override public float[] demodulate(float[] i, float[] q) { - ReusableFloatBuffer reusableFloatBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount()); - float[] input = complexBuffer.getSamples(); - float[] output = reusableFloatBuffer.getSamples(); - mOutputBufferPointer = 0; + float[] demodulated = new float[i.length]; - for(int x= 0; x < complexBuffer.getSamples().length; x += 2) + for(int x = 0; x < i.length; x++) { - output[mOutputBufferPointer++] = demodulate(input[x], input[x + 1]); + demodulated[x] = (float)FastMath.sqrt((i[x] * i[x]) + (q[x] * q[x])) * mGain; } - complexBuffer.decrementUserCount(); - - return reusableFloatBuffer; + return demodulated; } } diff --git a/src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java b/src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java new file mode 100644 index 00000000..a6c5bb25 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java @@ -0,0 +1,15 @@ +package io.github.dsheirer.dsp.am; + +/** + * AM Demodulator interface + */ +public interface IAmDemodulator +{ + /** + * Demodulate the complex I & Q sample arrays + * @param i samples + * @param q samples + * @return demodulated samples. + */ + float[] demodulate(float[] i, float[] q); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java index 94b29a63..21d31990 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -18,10 +18,44 @@ */ package io.github.dsheirer.dsp.filter; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter128Bit; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter256Bit; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter512Bit; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter64Bit; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilterDefaultBit; import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesignerWithLagrange; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap64Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap64Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap64Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap64Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter64Bit; +import io.github.dsheirer.dsp.window.WindowFactory; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; import org.apache.commons.math3.util.FastMath; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; @@ -47,9 +81,9 @@ public class FilterFactory * @param frequency - cutoff in hertz * @param length - filter length * @param window - to apply against the coefficients - * @return + * @return coefficients */ - public static float[] getSinc(double sampleRate, long frequency, int length, Window.WindowType window) + public static float[] getSinc(double sampleRate, long frequency, int length, WindowType window) { int evenLength = length % 2 == 0 ? length : length + 1; @@ -90,7 +124,7 @@ public class FilterFactory } //Apply the window against the coefficients - coefficients = Window.apply(window, coefficients); + coefficients = WindowFactory.apply(window, coefficients); return coefficients; } @@ -223,7 +257,7 @@ public class FilterFactory * @param windowType - window to apply against the generated coefficients * @return */ - public static float[] getLowPass(double sampleRate, long cutoff, int filterLength, Window.WindowType windowType) + public static float[] getLowPass(double sampleRate, long cutoff, int filterLength, WindowType windowType) { return getSinc(sampleRate, cutoff, filterLength, windowType); } @@ -243,7 +277,7 @@ public class FilterFactory * - stopFrequency <= sampleRate/2 */ public static float[] getLowPass(double sampleRate, int passFrequency, int stopFrequency, int attenuation, - Window.WindowType windowType, boolean forceOddLength) + WindowType windowType, boolean forceOddLength) { if(stopFrequency < passFrequency || stopFrequency > (sampleRate / 2)) { @@ -276,7 +310,7 @@ public class FilterFactory * @param windowType - window to apply against the generated coefficients * @return */ - public static float[] getHighPass(int sampleRate, long cutoff, int filterLength, Window.WindowType windowType) + public static float[] getHighPass(int sampleRate, long cutoff, int filterLength, WindowType windowType) { //Convert the high frequency cutoff to its low frequency cutoff //equivalent, so that when we generate the low pass filter, prior to @@ -287,7 +321,7 @@ public class FilterFactory } public static float[] getHighPass(int sampleRate, long stopFrequency, long passFrequency, int attenuation, - Window.WindowType windowType, boolean forceOddLength) + WindowType windowType, boolean forceOddLength) { /* reverse the stop and pass frequency to get the low pass variant */ int tapCount = getTapCount(sampleRate, stopFrequency, passFrequency, attenuation); @@ -508,7 +542,7 @@ public class FilterFactory * @param outputSampleRate * @return */ - public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, Window.WindowType window) + public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, WindowType window) { int taps = getTapCount(outputSampleRate, passFrequency, passFrequency + 1500, attenuation); @@ -760,7 +794,7 @@ public class FilterFactory double cutoff = (channelBandwidth * 1.10) / (channelSampleRate * (double)channels); //Design the prototype synthesizer with 105% of the channel bandwidth produced by the channelizer. - float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0); + float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0f); //This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient float[] extendedTaps = new float[taps.length + 1]; @@ -819,7 +853,7 @@ public class FilterFactory //Get an initial filter and band edge frequency response using the channel bandwidth as a cutoff float[] taps = null; - taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0); + taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0f); double response = FilterFactory.evaluate(taps, bandEdge); //Set cutoff adjustment threshold - we'll test cutoff frequencies to around 1 hertz resolution @@ -832,7 +866,7 @@ public class FilterFactory if(matchesObjective(response, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE, MARGIN_OF_ERROR) && (cutoffFrequency + increment <= bandEdge)) { - float[] higherCutoffTaps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency + increment, 80.0); + float[] higherCutoffTaps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency + increment, 80.0f); double higherCutoffResponse = FilterFactory.evaluate(higherCutoffTaps, bandEdge); if(matchesObjective(higherCutoffResponse, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE, MARGIN_OF_ERROR)) @@ -878,7 +912,7 @@ public class FilterFactory increment = cutoffFrequency * 0.1; } - taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0); + taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0f); response = FilterFactory.evaluate(taps, bandEdge); } } @@ -900,7 +934,7 @@ public class FilterFactory mLog.debug("Input Sample Rate: " + sampleRate); mLog.debug("Channel Bandwidth: " + channelBandwidth); mLog.debug("Channels: " + channels); - mLog.debug("Window Type: " + Window.WindowType.KAISER.name()); + mLog.debug("Window Type: " + WindowType.KAISER.name()); mLog.debug("Taps Per Channel - Requested:" + tapsPerChannel + " Actual:" + ((double)extendedTaps.length / (double)channels)); mLog.debug("Filter Length: " + (extendedTaps.length)); mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge)); @@ -928,7 +962,7 @@ public class FilterFactory * @return filter coefficients. * @throws FilterDesignException if the requested length is not odd */ - public static float[] getSinc(double cutoff, int length, Window.WindowType windowType) throws FilterDesignException + public static float[] getSinc(double cutoff, int length, WindowType windowType) throws FilterDesignException { if(length % 2 == 0) { @@ -938,7 +972,7 @@ public class FilterFactory float[] coefficients = new float[length]; int half = length / 2; - double[] window = Window.getWindow(windowType, length); + float[] window = WindowFactory.getWindow(windowType, length); double scalor = 2.0 * cutoff; double piScalor = FastMath.PI * scalor; @@ -967,7 +1001,7 @@ public class FilterFactory * @return filter coefficients. * @throws FilterDesignException if the requested length is not odd */ - public static float[] getKaiserSinc(int length, double cutoff, double attenuation) throws FilterDesignException + public static float[] getKaiserSinc(int length, double cutoff, float attenuation) throws FilterDesignException { if(length % 2 == 0) { @@ -977,7 +1011,7 @@ public class FilterFactory float[] coefficients = new float[length]; int half = length / 2; - double[] window = Window.getKaiser(length, attenuation); + float[] window = WindowFactory.getKaiser(length, attenuation); double scalor = 2.0 * cutoff; double piScalor = FastMath.PI * scalor; @@ -1004,7 +1038,7 @@ public class FilterFactory * @param windowType to apply to the filter. * @return coefficients */ - public static float[] getHalfBand(int length, Window.WindowType windowType) + public static float[] getHalfBand(int length, WindowType windowType) { if((length - 3) % 4 != 0) { @@ -1012,7 +1046,7 @@ public class FilterFactory "integer multiple in (N=4m+3), e.g. 7, 11, 15, etc"); } - double[] window = Window.getWindow(windowType, length); + float[] window = WindowFactory.getWindow(windowType, length); float[] taps = new float[length]; int halfLength = length / 2; @@ -1035,6 +1069,131 @@ public class FilterFactory return taps; } + /** + * Creates the optimal FIR filter implementation using calibration data to select + * from among the scalar and vector implementations. + * @param coefficients for the filter + * @return fir filter implementation + */ + public static IRealFilter getRealFilter(float[] coefficients) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_FIR); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorRealFIRFilterDefaultBit(coefficients); + case VECTOR_SIMD_64: + return new VectorRealFIRFilter64Bit(coefficients); + case VECTOR_SIMD_128: + return new VectorRealFIRFilter128Bit(coefficients); + case VECTOR_SIMD_256: + return new VectorRealFIRFilter256Bit(coefficients); + case VECTOR_SIMD_512: + return new VectorRealFIRFilter512Bit(coefficients); + case UNCALIBRATED: + case SCALAR: + default: + return new RealFIRFilter(coefficients); + } + } + + /** + * Constructs the optimal decimation filter implementation for filter length and window type + * using calibration data to select among scalar and vector implementation options. + * @param length of decimation filter + * @param windowType for designing the filter + * @return filter implementation + */ + public static IRealDecimationFilter getRealDecimationFilter(int length, WindowType windowType) + { + float[] coefficients = getHalfBand(length, windowType); + + switch(length) + { + case 11: + switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_11_TAP)) + { + case VECTOR_SIMD_64: + return new VectorRealHalfBandDecimationFilter11Tap64Bit(coefficients); + case VECTOR_SIMD_128: + return new VectorRealHalfBandDecimationFilter11Tap128Bit(coefficients); + case VECTOR_SIMD_256: + return new VectorRealHalfBandDecimationFilter11Tap256Bit(coefficients); + case VECTOR_SIMD_512: + return new VectorRealHalfBandDecimationFilter11Tap512Bit(coefficients); + case SCALAR: + case UNCALIBRATED: + default: + return new RealHalfBandDecimationFilter(coefficients); + } + case 15: + switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_15_TAP)) + { + case VECTOR_SIMD_64: + return new VectorRealHalfBandDecimationFilter15Tap64Bit(coefficients); + case VECTOR_SIMD_128: + return new VectorRealHalfBandDecimationFilter15Tap128Bit(coefficients); + case VECTOR_SIMD_256: + return new VectorRealHalfBandDecimationFilter15Tap256Bit(coefficients); + case VECTOR_SIMD_512: + return new VectorRealHalfBandDecimationFilter15Tap512Bit(coefficients); + case SCALAR: + case UNCALIBRATED: + default: + return new RealHalfBandDecimationFilter(coefficients); + } + case 23: + switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_23_TAP)) + { + case VECTOR_SIMD_64: + return new VectorRealHalfBandDecimationFilter23Tap64Bit(coefficients); + case VECTOR_SIMD_128: + return new VectorRealHalfBandDecimationFilter23Tap128Bit(coefficients); + case VECTOR_SIMD_256: + return new VectorRealHalfBandDecimationFilter23Tap256Bit(coefficients); + case VECTOR_SIMD_512: + return new VectorRealHalfBandDecimationFilter23Tap512Bit(coefficients); + case SCALAR: + case UNCALIBRATED: + default: + return new RealHalfBandDecimationFilter(coefficients); + } + case 63: + switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_63_TAP)) + { + case VECTOR_SIMD_64: + return new VectorRealHalfBandDecimationFilter63Tap64Bit(coefficients); + case VECTOR_SIMD_128: + return new VectorRealHalfBandDecimationFilter63Tap128Bit(coefficients); + case VECTOR_SIMD_256: + return new VectorRealHalfBandDecimationFilter63Tap256Bit(coefficients); + case VECTOR_SIMD_512: + return new VectorRealHalfBandDecimationFilter63Tap512Bit(coefficients); + case SCALAR: + case UNCALIBRATED: + default: + return new RealHalfBandDecimationFilter(coefficients); + } + default: + switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_DEFAULT)) + { + case VECTOR_SIMD_64: + return new VectorRealHalfBandDecimationFilter64Bit(coefficients); + case VECTOR_SIMD_128: + return new VectorRealHalfBandDecimationFilter128Bit(coefficients); + case VECTOR_SIMD_256: + return new VectorRealHalfBandDecimationFilter256Bit(coefficients); + case VECTOR_SIMD_512: + return new VectorRealHalfBandDecimationFilter512Bit(coefficients); + case SCALAR: + case UNCALIBRATED: + default: + return new RealHalfBandDecimationFilter(coefficients); + } + } + } + /** * Compares two doubles for equals and avoids any rounding error that are present. * @@ -1053,7 +1212,7 @@ public class FilterFactory int length = 15; - float [] taps = FilterFactory.getHalfBand(length, Window.WindowType.HAMMING); + float [] taps = FilterFactory.getHalfBand(length, WindowType.HAMMING); for(int x = 0; x < length; x++) { diff --git a/src/main/java/io/github/dsheirer/dsp/filter/Filters.java b/src/main/java/io/github/dsheirer/dsp/filter/Filters.java deleted file mode 100644 index fe4cd96a..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/Filters.java +++ /dev/null @@ -1,1760 +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 Foundationf, either version 3 of the Licensef, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be usefulf, - * 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 notf, see - ******************************************************************************/ -package io.github.dsheirer.dsp.filter; - -public enum Filters -{ - /** - * Designed with t-filter: - * - * 48000 Fsf, 243 taps - * 0 - 200 Hz: gain=0f, -60 db attenuation - * 500 - 3500 Hz: gain=1f, 5 db ripple - * 3800 - 24000 Hz: gain=0f, -60 db attenuation - */ - FIR_BANDPASS_AUDIO_48KHZ( new float[] - { - 0.0011306590506720503f, - 0.0017695582496011488f, - 0.0029288606290379564f, - 0.004343691928519147f, - 0.005911947383484083f, - 0.007475952925152455f, - 0.008834973531280245f, - 0.009767639642827303f, - 0.010062783359359985f, - 0.009554323352836855f, - 0.008154040477083785f, - 0.005876856248820146f, - 0.002853293692694345f, - -0.0006754620931569513f, - -0.004380476393750235f, - -0.007883982012534343f, - -0.010810030687940836f, - -0.01283857015700313f, - -0.013754750291231674f, - -0.013485225355167158f, - -0.012113350628392553f, - -0.009870878494518202f, - -0.007105858136673649f, - -0.00423044627271302f, - -0.0016581171943216698f, - 0.00026139558898877413f, - 0.0012960093376189904f, - 0.001367153398534282f, - 0.0005589404072224782f, - -0.0008998262476139348f, - -0.002678680933513012f, - -0.004405628120562205f, - -0.005733894494038466f, - -0.006403098522792899f, - -0.0062840458452382f, - -0.005398740409728341f, - -0.003912212267810966f, - -0.0020979501642753354f, - -0.0002835213596901672f, - 0.0012129422773069776f, - 0.002143441512259471f, - 0.0023762621098547794f, - 0.0019187818063353935f, - 0.000912904484557303f, - -0.0003959227311914178f, - -0.0017094242365984f, - -0.0027368218536981716f, - -0.0032529110615098964f, - -0.003141865590241452f, - -0.0024186267939756956f, - -0.0012232782401918676f, - 0.00020964495297755235f, - 0.0016007876062091302f, - 0.0026840078948525774f, - 0.003261066093523398f, - 0.0032416075292524913f, - 0.0026604426307957627f, - 0.0016685677475029945f, - 0.0005002180750227251f, - -0.0005771658431263752f, - -0.001319472490248709f, - -0.0015570243575186143f, - -0.001229709577267077f, - -0.00039860814610671267f, - 0.000768061742498229f, - 0.0020316064815484648f, - 0.0031342758646814173f, - 0.00385441982862473f, - 0.004053691192616693f, - 0.003706243244244987f, - 0.0029036666957800066f, - 0.0018348347602710623f, - 0.0007447703354245662f, - -0.00011835819081745851f, - -0.000556901991979898f, - -0.0004664033507391891f, - 0.00014190043902796031f, - 0.001143626440427841f, - 0.0023282929683617563f, - 0.0034451626872284316f, - 0.004257806518147909f, - 0.004595703675845672f, - 0.00439150448243702f, - 0.0036959141111179176f, - 0.0026669294496819663f, - 0.00153556329292172f, - 0.0005555624481582027f, - -0.000052181722788229754f, - -0.00014822483506696354f, - 0.0002944283222452884f, - 0.0011838275788186557f, - 0.0023285887949390117f, - 0.003479479143540588f, - 0.004383876725492502f, - 0.004841511744105689f, - 0.004748927884234896f, - 0.0041230326070274305f, - 0.003098290748206268f, - 0.0018980051891599843f, - 0.0007858270723442799f, - 0.000007808226096047636f, - -0.00026221415675647226f, - 0.000038194038926661374f, - 0.0008450198034699045f, - 0.001980683488085903f, - 0.0031920946811098026f, - 0.004205885473834353f, - 0.004788786880449399f, - 0.004799721085068433f, - 0.004222198149342628f, - 0.0031694947167750084f, - 0.0018613072553364282f, - 0.0005764055253590191f, - -0.00040837636254631156f, - -0.0008797864800588528f, - -0.0007362748788250025f, - -0.000012335427115590454f, - 0.001127487196311246f, - 0.0024230493118988245f, - 0.0035750267398429276f, - 0.004310745684369589f, - 0.004445118905106224f, - 0.003923338081120285f, - 0.0028351651513023415f, - 0.0013974479410391886f, - -0.00009204763249292721f, - -0.0013196387156487223f, - -0.0020251151156756176f, - -0.0020605167563326203f, - -0.001424951849329983f, - -0.00026755861117644484f, - 0.0011426434337099476f, - 0.00247453898902279f, - 0.0034075979415458474f, - 0.0037034647048603147f, - 0.003260419292510863f, - 0.002138889696298502f, - 0.0005507292550901969f, - -0.0011837011788563476f, - -0.002705803369109791f, - -0.0036965273158039683f, - -0.003950008183602666f, - -0.0034202362142058866f, - -0.002235167107431455f, - -0.0006724174233870484f, - 0.0008995473194704798f, - 0.0021008342984556796f, - 0.0026253485580778205f, - 0.002310873178862991f, - 0.0011772529466448514f, - -0.0005730908358460114f, - -0.002594671172069867f, - -0.0044734850087020245f, - -0.005818409323341712f, - -0.006347432664306618f, - -0.005952913139489849f, - -0.004732154819606038f, - -0.0029702110984690744f, - -0.0010791651288411521f, - 0.00048707995367597095f, - 0.0013335442378021411f, - 0.0012177475494058294f, - 0.00010122252104995588f, - -0.0018287043085791768f, - -0.004200030123435349f, - -0.006529365603823069f, - -0.00832894491185446f, - -0.009214117900210558f, - -0.008992727392755416f, - -0.007713940553513187f, - -0.005665102752109283f, - -0.0033154814998344715f, - -0.001217542957348092f, - 0.00011370751541866088f, - 0.00031672583066655f, - -0.0007305199337543714f, - -0.002874449900177386f, - -0.005710232857479199f, - -0.008662772551611736f, - -0.01110852438766387f, - -0.01251284869187753f, - -0.012552561484583724f, - -0.011196629466395516f, - -0.008725049876131727f, - -0.005679263313240144f, - -0.0027527053850089833f, - -0.0006423534393274008f, - 0.00010613666306157791f, - -0.0007726767835356292f, - -0.0031920086018739423f, - -0.006715187307484388f, - -0.010635986592925665f, - -0.014122526644297223f, - -0.016396230121947006f, - -0.016907881072255753f, - -0.015472266669959437f, - -0.01232951904769663f, - -0.008115919582767236f, - -0.0037455665411447506f, - -0.00022384102223090357f, - 0.0015702170495901357f, - 0.0010871601110870473f, - -0.0017492942655052172f, - -0.006493113668388964f, - -0.012237738308695591f, - -0.017782884779579476f, - -0.021877188332801504f, - -0.02348982727475883f, - -0.022055126496704366f, - -0.017635878927117044f, - -0.010965170848193264f, - -0.0033482781673564166f, - 0.003565343786600591f, - 0.008103372983477226f, - 0.008926637620116478f, - 0.005350637848922081f, - -0.002434639433805644f, - -0.013297914897997802f, - -0.025277752058596798f, - -0.03586329253291671f, - -0.042403748965243175f, - -0.04257882777767779f, - -0.034847147989454305f, - -0.018789707031704826f, - 0.004719508132752991f, - 0.033554756785942955f, - 0.06459420543249415f, - 0.09415420005424562f, - 0.11853369816477846f, - 0.13457931451963423f, - 0.14017594101985517f, - 0.13457931451963423f, - 0.11853369816477848f, - 0.09415420005424562f, - 0.06459420543249415f, - 0.033554756785942955f, - 0.004719508132752991f, - -0.018789707031704822f, - -0.034847147989454305f, - -0.04257882777767779f, - -0.042403748965243175f, - -0.03586329253291671f, - -0.025277752058596798f, - -0.01329791489799781f, - -0.002434639433805644f, - 0.005350637848922081f, - 0.008926637620116478f, - 0.008103372983477226f, - 0.003565343786600591f, - -0.0033482781673564166f, - -0.01096517084819326f, - -0.017635878927117044f, - -0.022055126496704366f, - -0.02348982727475883f, - -0.021877188332801504f, - -0.017782884779579476f, - -0.012237738308695591f, - -0.006493113668388964f, - -0.0017492942655052172f, - 0.0010871601110870439f, - 0.0015702170495901396f, - -0.00022384102223090309f, - -0.0037455665411447506f, - -0.00811591958276724f, - -0.01232951904769663f, - -0.015472266669959437f, - -0.016907881072255753f, - -0.016396230121947006f, - -0.014122526644297223f, - -0.010635986592925665f, - -0.006715187307484388f, - -0.0031920086018739444f, - -0.0007726767835356292f, - 0.0001061366630615779f, - -0.0006423534393274008f, - -0.0027527053850089833f, - -0.005679263313240145f, - -0.00872504987613173f, - -0.011196629466395517f, - -0.012552561484583724f, - -0.01251284869187753f, - -0.011108524387663866f, - -0.008662772551611736f, - -0.005710232857479197f, - -0.002874449900177386f, - -0.0007305199337543743f, - 0.00031672583066655f, - 0.0001137075154186604f, - -0.001217542957348092f, - -0.0033154814998344715f, - -0.005665102752109283f, - -0.007713940553513191f, - -0.008992727392755416f, - -0.009214117900210558f, - -0.008328944911854458f, - -0.006529365603823069f, - -0.004200030123435349f, - -0.0018287043085791778f, - 0.00010122252104995396f, - 0.0012177475494058294f, - 0.0013335442378021411f, - 0.00048707995367597095f, - -0.0010791651288411521f, - -0.002970211098469072f, - -0.004732154819606038f, - -0.005952913139489849f, - -0.0063474326643066185f, - -0.005818409323341712f, - -0.004473485008702026f, - -0.002594671172069867f, - -0.0005730908358460094f, - 0.0011772529466448514f, - 0.002310873178862991f, - 0.002625348558077821f, - 0.0021008342984556796f, - 0.0008995473194704788f, - -0.0006724174233870484f, - -0.002235167107431454f, - -0.0034202362142058866f, - -0.003950008183602666f, - -0.0036965273158039683f, - -0.002705803369109791f, - -0.0011837011788563506f, - 0.0005507292550901969f, - 0.002138889696298502f, - 0.003260419292510862f, - 0.0037034647048603147f, - 0.0034075979415458474f, - 0.00247453898902279f, - 0.0011426434337099476f, - -0.00026755861117644543f, - -0.0014249518493299838f, - -0.0020605167563326203f, - -0.0020251151156756176f, - -0.001319638715648723f, - -0.00009204763249292625f, - 0.0013974479410391886f, - 0.0028351651513023415f, - 0.003923338081120284f, - 0.004445118905106224f, - 0.00431074568436959f, - 0.0035750267398429276f, - 0.0024230493118988228f, - 0.001127487196311246f, - -0.000012335427115590454f, - -0.0007362748788250025f, - -0.0008797864800588528f, - -0.0004083763625463096f, - 0.000576405525359021f, - 0.0018613072553364282f, - 0.0031694947167750084f, - 0.0042221981493426285f, - 0.004799721085068431f, - 0.004788786880449399f, - 0.004205885473834353f, - 0.0031920946811098017f, - 0.001980683488085902f, - 0.0008450198034699026f, - 0.00003819403892666041f, - -0.0002622141567564703f, - 0.000007808226096045708f, - 0.0007858270723442809f, - 0.001898005189159986f, - 0.003098290748206268f, - 0.004123032607027432f, - 0.004748927884234897f, - 0.004841511744105689f, - 0.004383876725492502f, - 0.003479479143540588f, - 0.0023285887949390117f, - 0.0011838275788186557f, - 0.00029442832224528743f, - -0.00014822483506696354f, - -0.000052181722788230235f, - 0.0005555624481582017f, - 0.0015355632929217181f, - 0.002666929449681966f, - 0.0036959141111179176f, - 0.004391504482437019f, - 0.004595703675845674f, - 0.004257806518147909f, - 0.0034451626872284316f, - 0.002328292968361755f, - 0.0011436264404278407f, - 0.0001419004390279603f, - -0.00046640335073919057f, - -0.000556901991979898f, - -0.00011835819081745802f, - 0.0007447703354245662f, - 0.0018348347602710623f, - 0.0029036666957800066f, - 0.0037062432442449863f, - 0.0040536911926166925f, - 0.003854419828624731f, - 0.0031342758646814165f, - 0.0020316064815484656f, - 0.00076806174249823f, - -0.00039860814610671267f, - -0.001229709577267077f, - -0.0015570243575186143f, - -0.001319472490248709f, - -0.0005771658431263752f, - 0.000500218075022725f, - 0.001668567747502994f, - 0.002660442630795762f, - 0.0032416075292524913f, - 0.0032610660935233986f, - 0.002684007894852579f, - 0.0016007876062091293f, - 0.00020964495297755235f, - -0.0012232782401918676f, - -0.002418626793975698f, - -0.003141865590241452f, - -0.003252911061509897f, - -0.002736821853698173f, - -0.0017094242365984003f, - -0.0003959227311914187f, - 0.0009129044845573019f, - 0.0019187818063353941f, - 0.0023762621098547807f, - 0.00214344151225947f, - 0.0012129422773069776f, - -0.0002835213596901672f, - -0.0020979501642753354f, - -0.003912212267810966f, - -0.005398740409728341f, - -0.0062840458452382f, - -0.006403098522792899f, - -0.005733894494038467f, - -0.004405628120562203f, - -0.002678680933513013f, - -0.0008998262476139358f, - 0.0005589404072224788f, - 0.001367153398534282f, - 0.0012960093376189895f, - 0.0002613955889887717f, - -0.0016581171943216698f, - -0.00423044627271302f, - -0.007105858136673649f, - -0.009870878494518204f, - -0.012113350628392553f, - -0.013485225355167158f, - -0.01375475029123167f, - -0.01283857015700313f, - -0.010810030687940836f, - -0.007883982012534343f, - -0.004380476393750235f, - -0.0006754620931569513f, - 0.0028532936926943457f, - 0.005876856248820146f, - 0.008154040477083787f, - 0.009554323352836855f, - 0.010062783359359985f, - 0.009767639642827303f, - 0.008834973531280245f, - 0.007475952925152455f, - 0.005911947383484083f, - 0.004343691928519149f, - 0.0029288606290379547f, - 0.00176955824960115f, - 0.0011306590506720503f - } ), - - FIRHP_LTR_DCBLOCK_750FS_50FC( new float[] - { - 0.0679294122464217f, - -0.07324095797199973f, - -0.03838823381974882f, - -0.013430897335300878f, - 0.006449207638138327f, - 0.022750490924149575f, - 0.034175688012529584f, - 0.037600969376502945f, - 0.030002787357015913f, - 0.009961076769098255f, - -0.02214359076495753f, - -0.062375656373163425f, - -0.10487871348895818f, - -0.14241968373731975f, - -0.16818470634383087f, - 0.8226518956829582f, - -0.16818470634383087f, - -0.14241968373731975f, - -0.10487871348895818f, - -0.062375656373163425f, - -0.02214359076495753f, - 0.009961076769098255f, - 0.030002787357015913f, - 0.037600969376502945f, - 0.034175688012529584f, - 0.022750490924149575f, - 0.006449207638138327f, - -0.013430897335300878f, - -0.03838823381974882f, - -0.07324095797199973f, - 0.0679294122464217f - }), - - FIRLP_LTR_1500FS( new float[] - { - -0.008272676875607624f, - -0.0024939020178146617f, - 0.010376092557732189f, - 0.011962525412519345f, - -0.003977368587769054f, - -0.013289773851060555f, - -0.0029497606301098858f, - 0.003941811083306652f, - -0.003154954069081114f, - 0.005485070321679545f, - 0.03171909301543344f, - 0.015387284536986508f, - -0.055467824110903244f, - -0.07246950346172203f, - 0.03466378905394898f, - 0.1322297692443333f, - 0.04683098737206109f, - -0.13469992806577527f, - -0.14450919452286415f, - 0.0578348928408703f, - 0.18926196545202326f, - 0.0578348928408703f, - -0.14450919452286415f, - -0.13469992806577527f, - 0.04683098737206109f, - 0.1322297692443333f, - 0.03466378905394898f, - -0.07246950346172203f, - -0.055467824110903244f, - 0.015387284536986508f, - 0.03171909301543344f, - 0.005485070321679545f, - -0.003154954069081114f, - 0.003941811083306652f, - -0.0029497606301098858f, - -0.013289773851060555f, - -0.003977368587769054f, - 0.011962525412519345f, - 0.010376092557732189f, - -0.0024939020178146617f, - -0.008272676875607624f - }), - - FIRBP_1200FSK_24000FS( new float[] - { - 0.004792788663425844f, - 0.004034377807422182f, - 0.004699808237099943f, - 0.00443210843891432f, - 0.0028638644082473f, - -0.00015292185838515035f, - -0.004394278846594682f, - -0.009212560275995686f, - -0.013596659741332163f, - -0.016383584186535002f, - -0.016573550574018152f, - -0.013657566836436204f, - -0.007851047182221552f, - -0.00014425394402752814f, - 0.007885453072038891f, - 0.014465082677564993f, - 0.01811703090261803f, - 0.01810997536219774f, - 0.014727244087571567f, - 0.009244939917342813f, - 0.0036035605020527947f, - -0.00014781830127321208f, - -0.0004913597198315713f, - 0.0030136991548897693f, - 0.009453462203915387f, - 0.01666055505335309f, - 0.02170180561798826f, - 0.021656701887523472f, - 0.014498683513932197f, - -0.0001565221289617459f, - -0.02065343799220559f, - -0.04339467795574746f, - -0.06347784859804807f, - -0.07576298936541592f, - -0.07612281593093975f, - -0.06256712023008996f, - -0.03596397841747506f, - -0.00012170810308311971f, - 0.03885147059625151f, - 0.07377979316631962f, - 0.09792980519752477f, - 0.10655596332800969f, - 0.09792980519752477f, - 0.07377979316631962f, - 0.03885147059625151f, - -0.00012170810308311971f, - -0.03596397841747506f, - -0.06256712023008996f, - -0.07612281593093975f, - -0.07576298936541592f, - -0.06347784859804807f, - -0.04339467795574746f, - -0.02065343799220559f, - -0.0001565221289617459f, - 0.014498683513932197f, - 0.021656701887523472f, - 0.02170180561798826f, - 0.01666055505335309f, - 0.009453462203915387f, - 0.0030136991548897693f, - -0.0004913597198315713f, - -0.00014781830127321208f, - 0.0036035605020527947f, - 0.009244939917342813f, - 0.014727244087571567f, - 0.01810997536219774f, - 0.01811703090261803f, - 0.014465082677564993f, - 0.007885453072038891f, - -0.00014425394402752814f, - -0.007851047182221552f, - -0.013657566836436204f, - -0.016573550574018152f, - -0.016383584186535002f, - -0.013596659741332163f, - -0.009212560275995686f, - -0.004394278846594682f, - -0.00015292185838515035f, - 0.0028638644082473f, - 0.00443210843891432f, - 0.004699808237099943f, - 0.004034377807422182f, - 0.004792788663425844f - }), - - FIRLP_3K( new float[] - { - /* - - FIR filter designed with - http://t-filter.appspot.com - - sampling frequency: 48000 Hz - - * 0 Hz - 3000 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 4.1593978370041 dB - - * 4000 Hz - 24000 Hz - gain = 0 - desired attenuation = -80 dB - actual attenuation = -80.03322246183818 dB - - */ - - -0.00011443124614437417f, - -0.00017293152659867192f, - -0.0002537395679787903f, - -0.0002960101735353313f, - -0.00023955416024142116f, - -0.000005428557963321982f, - 0.0004964100334261843f, - 0.0013538842757409063f, - 0.002634918910611058f, - 0.00436693575408337f, - 0.0065175161697321555f, - 0.00897952475800971f, - 0.011566379161822652f, - 0.014018876408451886f, - 0.016028053953955006f, - 0.017270887068042688f, - 0.01745751423179922f, - 0.0163833187291431f, - 0.013977100662194252f, - 0.010337169842377791f, - 0.005746282323474722f, - 0.0006589019184637925f, - -0.004341600521758248f, - -0.008613131831428159f, - -0.011545791615276019f, - -0.012660543617859996f, - -0.011700549440186059f, - -0.008698639621450092f, - -0.004006684845818583f, - 0.00172317360274727f, - 0.007608807120062409f, - 0.012654244324839246f, - 0.015898391911284164f, - 0.016575938784747404f, - 0.014265883702010186f, - 0.009002241493722784f, - 0.0013255862247370318f, - -0.007739007276591945f, - -0.0167810834076902f, - -0.024172675639705345f, - -0.02828537119270343f, - -0.027727506101182933f, - -0.02156733481426441f, - -0.009507700677949931f, - 0.008016566866990643f, - 0.02983764491633055f, - 0.05415546347475946f, - 0.0787307236559999f, - 0.10114644750135711f, - 0.11910360136213424f, - 0.13071022495045617f, - 0.13472344727490876f, - 0.13071022495045617f, - 0.11910360136213424f, - 0.10114644750135711f, - 0.0787307236559999f, - 0.05415546347475946f, - 0.02983764491633055f, - 0.008016566866990643f, - -0.009507700677949931f, - -0.02156733481426441f, - -0.027727506101182933f, - -0.02828537119270343f, - -0.024172675639705345f, - -0.0167810834076902f, - -0.007739007276591945f, - 0.0013255862247370318f, - 0.009002241493722784f, - 0.014265883702010186f, - 0.016575938784747404f, - 0.015898391911284164f, - 0.012654244324839246f, - 0.007608807120062409f, - 0.00172317360274727f, - -0.004006684845818583f, - -0.008698639621450092f, - -0.011700549440186059f, - -0.012660543617859996f, - -0.011545791615276019f, - -0.008613131831428159f, - -0.004341600521758248f, - 0.0006589019184637925f, - 0.005746282323474722f, - 0.010337169842377791f, - 0.013977100662194252f, - 0.0163833187291431f, - 0.01745751423179922f, - 0.017270887068042688f, - 0.016028053953955006f, - 0.014018876408451886f, - 0.011566379161822652f, - 0.00897952475800971f, - 0.0065175161697321555f, - 0.00436693575408337f, - 0.002634918910611058f, - 0.0013538842757409063f, - 0.0004964100334261843f, - -0.000005428557963321982f, - -0.00023955416024142116f, - -0.0002960101735353313f, - -0.0002537395679787903f, - -0.00017293152659867192f, - -0.00011443124614437417f - } ), - - FIRLP_8K( new float[] { - - /* - - FIR filter designed with - http://t-filter.appspot.com - - sampling frequency: 48000 Hz - - * 0 Hz - 4000 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 4.166465745565044 dB - - * 4600 Hz - 24000 Hz - gain = 0 - desired attenuation = -60 dB - actual attenuation = -60.01902518862098 dB - - */ - - -0.0005876043141107231f, - 0.0000395798795893033f, - 0.0007936108691418746f, - 0.002271220583758663f, - 0.0045690708238380244f, - 0.007632833240537176f, - 0.011210445195711924f, - 0.014848077882921171f, - 0.01794155760934873f, - 0.019840991376714804f, - 0.01999155424676768f, - 0.018081523929298803f, - 0.01415831726867514f, - 0.008676730505642374f, - 0.0024562153938831365f, - -0.0034571527303437705f, - -0.008001879410852286f, - -0.010340843984508226f, - -0.010072395969237698f, - -0.007354895192546896f, - -0.0029039203557233197f, - 0.0021492484218134564f, - 0.006514352308475102f, - 0.009056570176096615f, - 0.009090754245710363f, - 0.006576743943510026f, - 0.002156484338606342f, - -0.0029881335341335434f, - -0.007431040256860951f, - -0.009881347307264159f, - -0.009547761214420764f, - -0.006387716720906473f, - -0.00116384277476187f, - 0.004722257199180802f, - 0.0095865822102477f, - 0.011924484237795421f, - 0.010856600363787754f, - 0.006429626923814576f, - -0.00032219256325637487f, - -0.007591488679896054f, - -0.01325346967699737f, - -0.015451428687349951f, - -0.013159262174429455f, - -0.006567549275313882f, - 0.0028476895325932673f, - 0.012598008810195174f, - 0.019781804328972176f, - 0.02186025227953089f, - 0.017422058628364847f, - 0.006738234527549823f, - -0.008066547704296321f, - -0.023306710233840622f, - -0.0344567035523893f, - -0.037158269971142174f, - -0.028289474602001076f, - -0.006838858354697991f, - 0.02563946671507246f, - 0.06512400470969f, - 0.10578445793560229f, - 0.141076536562424f, - 0.16504202130157974f, - 0.17352664849913732f, - 0.16504202130157974f, - 0.141076536562424f, - 0.10578445793560229f, - 0.06512400470969f, - 0.02563946671507246f, - -0.006838858354697991f, - -0.028289474602001076f, - -0.037158269971142174f, - -0.0344567035523893f, - -0.023306710233840622f, - -0.008066547704296321f, - 0.006738234527549823f, - 0.017422058628364847f, - 0.02186025227953089f, - 0.019781804328972176f, - 0.012598008810195174f, - 0.0028476895325932673f, - -0.006567549275313882f, - -0.013159262174429455f, - -0.015451428687349951f, - -0.01325346967699737f, - -0.007591488679896054f, - -0.00032219256325637487f, - 0.006429626923814576f, - 0.010856600363787754f, - 0.011924484237795421f, - 0.0095865822102477f, - 0.004722257199180802f, - -0.00116384277476187f, - -0.006387716720906473f, - -0.009547761214420764f, - -0.009881347307264159f, - -0.007431040256860951f, - -0.0029881335341335434f, - 0.002156484338606342f, - 0.006576743943510026f, - 0.009090754245710363f, - 0.009056570176096615f, - 0.006514352308475102f, - 0.0021492484218134564f, - -0.0029039203557233197f, - -0.007354895192546896f, - -0.010072395969237698f, - -0.010340843984508226f, - -0.008001879410852286f, - -0.0034571527303437705f, - 0.0024562153938831365f, - 0.008676730505642374f, - 0.01415831726867514f, - 0.018081523929298803f, - 0.01999155424676768f, - 0.019840991376714804f, - 0.01794155760934873f, - 0.014848077882921171f, - 0.011210445195711924f, - 0.007632833240537176f, - 0.0045690708238380244f, - 0.002271220583758663f, - 0.0007936108691418746f, - 0.0000395798795893033f, - -0.0005876043141107231f - } ), - - FIRLP_15K( new float[] { - /* - FIR filter designed with http://t-filter.appspot.com - sampling frequency: 48000 Hz - * 0 Hz - 7000 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 3.6923464109664326 dB - * 9000 Hz - 24000 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -41.03342332843521 dB - */ - -0.014082901225560598f, - -0.02333969799770533f, - -0.022033345340200808f, - -0.0004492742003176776f, - 0.034217272170844526f, - 0.05847499164066433f, - 0.04845184454729058f, - 0.0029885635174003813f, - -0.045972124010673326f, - -0.04957432533335844f, - 0.02225593631403953f, - 0.15189293741405197f, - 0.27718300156351594f, - 0.32893125787628796f, - 0.27718300156351594f, - 0.15189293741405197f, - 0.02225593631403953f, - -0.04957432533335844f, - -0.045972124010673326f, - 0.0029885635174003813f, - 0.04845184454729058f, - 0.05847499164066433f, - 0.034217272170844526f, - -0.0004492742003176776f, - -0.022033345340200808f, - -0.02333969799770533f, - -0.014082901225560598f - } ), - - //FS=192000 FC=40000 WIN: Kaiser-Bessel - FIR_HALF_BAND_31T_ONE_EIGHTH_FCO( new float[] { - -0.000045886616046836f, 0f, - 0.000591369980811269f, 0f, - -0.002865894676690263f, 0f, - 0.008258847333251895f, 0f, - -0.019555838964709603f, 0f, - 0.041885910942306370f, 0f, - -0.091494773870957058f, 0f, - 0.313146427083389300f, 0.5f, //.499981677577289670f, - 0.313146427083389300f, 0f, - -0.091494773870957058f, 0f, - 0.041885910942306370f, 0f, - -0.019555838964709603f, 0f, - 0.008258847333251895f, 0f, - -0.002865894676690263f, 0f, - 0.000591369980811269f, 0f, - -0.000045886616046836f - } ), - FIRHP_31T_48KFS_300FCO( new float[] { 0.079454311268354116f, - -0.431252134555296540f, -0.080866558157993096f, -0.018201087229858796f, - -0.006959451230405745f, -0.005082182047181806f, -0.004827982359353442f, - -0.004678622485731351f, -0.004518783884113521f, -0.004590986714193629f, - -0.004788367734908081f, -0.004864687584309190f, -0.004847881778381928f, - -0.004819115432733451f, -0.004899065399109821f, 0.995074461482826860f, - -0.004899065399109821f, -0.004819115432733451f, -0.004847881778381928f, - -0.004864687584309190f, -0.004788367734908081f, -0.004590986714193629f, - -0.004518783884113521f, -0.004678622485731351f, -0.004827982359353442f, - -0.005082182047181806f, -0.006959451230405745f, -0.018201087229858796f, - -0.080866558157993096f, -0.431252134555296540f, 0.079454311268354116f } ), - - FIRHP_31T_12KFS_400FCO( new float[] { 0.053389156955298771f, - 0.182002884434506210f, -0.086127791245869528f, -0.016516534724731153f, - -0.036968390772931715f, -0.035475484228869217f, -0.040734954194563675f, - -0.044789081155425826f, -0.049513180269900807f, -0.054086500917566893f, - -0.058563271660686450f, -0.062449865988963169f, -0.065441928590436960f, - -0.068170371167388846f, -0.069540820637351994f, 0.930065488549365990f, - -0.069540820637351994f, -0.068170371167388846f, -0.065441928590436960f, - -0.062449865988963169f, -0.058563271660686450f, -0.054086500917566893f, - -0.049513180269900807f, -0.044789081155425826f, -0.040734954194563675f, - -0.035475484228869217f, -0.036968390772931715f, -0.016516534724731153f, - -0.086127791245869528f, 0.182002884434506210f, 0.053389156955298771f } ), - - /** - * FIR Low Pass Filter - * Sample Rate: 48000 - * Taps: 55 - * Cutoff Freq: 3000 Hz - * Designer: http://t-filter.appspot.com/fir/index.html - */ - - FIRLP_55TAP_48000FS_3000FC( new float[] { -0.0052419787903715655f, - -0.0003789909443307852f, 0.0012970137854134386f, 0.004210460544067679f, - 0.008221266221665309f, 0.012951576579356689f, 0.017794312853913318f, - 0.021959871931790928f, 0.024583800118549094f, 0.024878144443784132f, - 0.022296319647647538f, 0.016685495012933472f, 0.008390268267799136f, - -0.001718264469387386f, -0.012288389660616603f, -0.02161633130497537f, - -0.027882128567957774f, -0.02943377233328899f, -0.025006516193566884f, - -0.014040074880106284f, 0.0032217508351258103f, 0.025660031586092195f, - 0.05136009984280478f, 0.0778336106888181f, 0.10230926486919244f, - 0.12209961841343948f, 0.13496813439824185f, 0.13943117253534673f, - 0.13496813439824185f, 0.12209961841343948f, 0.10230926486919244f, - 0.0778336106888181f, 0.05136009984280478f, 0.025660031586092195f, - 0.0032217508351258103f, -0.014040074880106284f, -0.025006516193566884f, - -0.02943377233328899f, -0.027882128567957774f, -0.02161633130497537f, - -0.012288389660616603f, -0.001718264469387386f, 0.008390268267799136f, - 0.016685495012933472f, 0.022296319647647538f, 0.024878144443784132f, - 0.024583800118549094f, 0.021959871931790928f, 0.017794312853913318f, - 0.012951576579356689f, 0.008221266221665309f, 0.004210460544067679f, - 0.0012970137854134386f, -0.0003789909443307852f, -0.0052419787903715655f } ), - - /* - - FIR filter designed with - http://t-filter.appspot.com - - sampling frequency: 24000 Hz - - * 0 Hz - 1400 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 4.175011388566792 dB - - * 1500 Hz - 12000 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -40.00189415137373 dB - - */ - FIRLP_24000FS_TO_3000FS( new float[] { - - /* - - FIR filter designed with - http://t-filter.appspot.com - - sampling frequency: 48000 Hz - - * 0 Hz - 3000 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 3.939100294265316 dB - - * 3500 Hz - 24000 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -40.489198844529355 dB - - */ - - .2f,.2f,.2f,.2f,.2f,.2f,.2f - }), - - FIRLP_3000FS_TO_150FC( new float[] { - /* FIR filter designed with http://t-filter.appspot.com sampling frequency: 3000 Hz - * 0 Hz - 155 Hz gain = 1 desired ripple = 5 dB actual ripple = 4.1713329390800755 dB - * 160 Hz - 1500 Hz gain = 0 desired attenuation = -40 dB actual attenuation = -40.00926353319547 dB - */ - -0.006122920352830965f, - -0.0023884346335371165f, - -0.002599310268302821f, - -0.002619226598223891f, - -0.0024058915455907196f, - -0.0019288301578398123f, - -0.0011732914244696805f, - -0.00014348647168516363f, - 0.0011360279713313209f, - 0.002620018618160862f, - 0.004244279668461443f, - 0.005929041211240287f, - 0.007583700132903604f, - 0.009112816853143859f, - 0.010422968037142797f, - 0.011429759533610568f, - 0.012064461696610605f, - 0.01227985703081096f, - 0.012054670302231996f, - 0.011395973228693276f, - 0.010339492321850014f, - 0.008947826461137941f, - 0.007306289027941143f, - 0.005516680433668245f, - 0.003689888284206567f, - 0.0019376716631701368f, - 0.00036406537300421297f, - -0.0009423273945619157f, - -0.0019147615431778597f, - -0.0025137861299055595f, - -0.00272885000805846f, - -0.002579780077995427f, - -0.00211370881796881f, - -0.001401086778987431f, - -0.0005294931107453994f, - 0.00040506185948651675f, - 0.0013056441129537509f, - 0.0020838354221374305f, - 0.0026667562654551246f, - 0.003003052919640715f, - 0.003066845756573675f, - 0.002858826770125135f, - 0.002405247908616018f, - 0.0017550758096436146f, - 0.0009750187684900944f, - 0.0001428944628485573f, - -0.0006600057708364918f, - -0.0013572018806166464f, - -0.0018849613428995964f, - -0.0021983440926621103f, - -0.002274533536918245f, - -0.0021136819231921474f, - -0.0017380160407736585f, - -0.0011899674040168643f, - -0.0005293364966829129f, - 0.00017203581592664565f, - 0.0008397052861045773f, - 0.0014076463254305174f, - 0.0018238726340835366f, - 0.0020485384828633777f, - 0.002054368304440125f, - 0.00184518937028339f, - 0.001463759939291864f, - 0.0009254779772329933f, - 0.00030908834306052194f, - -0.00033072041687547837f, - -0.0009242244673079876f, - -0.001411151838185113f, - -0.0017423327816480623f, - -0.001885184212057672f, - -0.001827180245955061f, - -0.0015767572410517868f, - -0.0011623018660948076f, - -0.0006290757127071664f, - -0.000034277486241399186f, - 0.0005590116846630576f, - 0.0010883547994854647f, - 0.0014984460140958136f, - 0.0017469004431845741f, - 0.0018085733127357138f, - 0.00167799070404902f, - 0.0013699023886079994f, - 0.0009178041117923993f, - 0.00037029761935479554f, - -0.00021414834948363428f, - -0.0007733385753572961f, - -0.0012479991812573157f, - -0.0015880719715790936f, - -0.0017577706864892022f, - -0.0017393843977854672f, - -0.0015352614436320124f, - -0.0011668823340746201f, - -0.0006738084260047715f, - -0.00010821707107113083f, - 0.00046927876230741587f, - 0.0009969768021073388f, - 0.001418713685742809f, - 0.001689241246422699f, - 0.0017799814611489096f, - 0.0016808579160732414f, - 0.001401938565514821f, - 0.0009725070918705811f, - 0.00043789676780500817f, - -0.00014493157532811753f, - -0.0007134285789517371f, - -0.0012066408059786802f, - -0.0015718235937901522f, - -0.0017696752225057275f, - -0.0017782857455891608f, - -0.0015955792180418218f, - -0.001239958862296361f, - -0.00074891505028209f, - -0.0001750373426426684f, - 0.0004203972508663324f, - 0.0009743368473658559f, - 0.001427583798294352f, - 0.0017300695180374488f, - 0.0018474313131084748f, - 0.0017665803736364497f, - 0.0014959523321337515f, - 0.0010620969968500548f, - 0.0005087929550228872f, - -0.00010287888971484906f, - -0.0007071978327354896f, - -0.0012439856702169039f, - -0.0016485023632017621f, - -0.0018816779265696613f, - -0.0019137822019732152f, - -0.0017410363220249359f, - -0.0013797190792108685f, - -0.0008669516848493666f, - -0.0002564820252824784f, - 0.00038702570052167175f, - 0.0009946245737452108f, - 0.0015005576753828536f, - 0.0018494543772917837f, - 0.002002271293795752f, - 0.0019405399480025168f, - 0.001668583653638679f, - 0.0012133648938024923f, - 0.0006218226272857692f, - -0.00004396586555373278f, - -0.0007131888239163173f, - -0.0013137753201852866f, - -0.0017802301804471922f, - -0.002060772342951284f, - -0.0021230164816546412f, - -0.0019576726289922586f, - -0.001579698202436061f, - -0.0010269473735399154f, - -0.0003566571442405154f, - 0.00036046532149158213f, - 0.001047910209033917f, - 0.0016308959748307101f, - 0.0020457879850275806f, - 0.0022452826272775055f, - 0.0022053677261764473f, - 0.0019268362424314605f, - 0.001436104198970914f, - 0.000783301048300022f, - 0.000036170113001030666f, - -0.0007258491765731604f, - -0.0014208542690678963f, - -0.001973351045206107f, - -0.0023218012876373207f, - -0.002425430974535461f, - -0.0022692307984843695f, - -0.0018659822196923194f, - -0.0012553733554405261f, - -0.0005001275988053344f, - 0.0003207533398910953f, - 0.0011202470135660719f, - 0.0018119245687932325f, - 0.0023191095738080646f, - 0.0025840361579153536f, - 0.0025746366972740037f, - 0.0022874947808971076f, - 0.0017482590981993708f, - 0.001010469529718797f, - 0.00015080284754578053f, - -0.0007401321025391216f, - -0.0015677281601321103f, - -0.002241555313336647f, - -0.002685049904781321f, - -0.002847415468166842f, - -0.002706332216634437f, - -0.0022693572465456347f, - -0.0015807551810501244f, - -0.0007073681818317356f, - 0.00025858155245919815f, - 0.0012156374711696657f, - 0.0020601250277244316f, - 0.002698864465909288f, - 0.003058968365749582f, - 0.003095930462187589f, - 0.002799180323785052f, - 0.0021938662312239255f, - 0.0013387749941891575f, - 0.00032073673872905727f, - -0.0007540149497279023f, - -0.0017707823148707424f, - -0.0026186149816637165f, - -0.0032023981947833136f, - -0.0034533803843191375f, - -0.0033370666249195324f, - -0.002857634299302074f, - -0.0020583398437455315f, - -0.0010178803764328268f, - 0.00015714303240366978f, - 0.00134312158248896f, - 0.0024123674642028033f, - 0.0032466971416879246f, - 0.003750429521389635f, - 0.0038610298842211605f, - 0.0035563826341158213f, - 0.0028590450497876694f, - 0.0018334905123878731f, - 0.0005825119501486656f, - -0.0007653431253644171f, - -0.002066921044075508f, - -0.00318050748566926f, - -0.003981099599522876f, - -0.004372950526822947f, - -0.004302245531766623f, - -0.0037634623427922525f, - -0.0028010281264617036f, - -0.0015063684237781073f, - -0.000009312999857163947f, - 0.0015348340498211675f, - 0.002960705851903475f, - 0.00411025264482529f, - 0.004850497409323919f, - 0.00508852374186094f, - 0.004782051229146126f, - 0.003945677268175109f, - 0.002651861057270526f, - 0.001024987337059874f, - -0.000770898857558077f, - -0.0025480549808695363f, - -0.0041134371938038455f, - -0.005289473467472686f, - -0.0059345294154884536f, - -0.005958790652492603f, - -0.0053348632172493865f, - -0.004104133685973202f, - -0.0023757889373754375f, - -0.0003154863263286177f, - 0.0018679968592255536f, - 0.003942664171895544f, - 0.005680643711722206f, - 0.006877057686831076f, - 0.007378348952494577f, - 0.007096715159487296f, - 0.0060250187066667035f, - 0.004239992353086172f, - 0.0018984740602875209f, - -0.0007749973681354589f, - -0.0035085807133030038f, - -0.0060094188052075385f, - -0.007993440148818328f, - -0.009215708970605762f, - -0.00949819102645701f, - -0.008752165260963618f, - -0.006992771039067642f, - -0.004343750499151247f, - -0.0010313778997952953f, - 0.002632475569255834f, - 0.006276994042887084f, - 0.009506525921431109f, - 0.011939275012512792f, - 0.013247115357734634f, - 0.013192709905840089f, - 0.011660378948982f, - 0.008677354388180201f, - 0.0044226259943339384f, - -0.0007776763994276021f, - -0.006468598011493521f, - -0.01209866234178173f, - -0.01705984630504337f, - -0.020740073978089375f, - -0.022572656974485978f, - -0.022091348052909576f, - -0.018974044064189968f, - -0.01307870343426492f, - -0.004467461602914141f, - 0.006588374127832776f, - 0.019617756508396658f, - 0.03397630130130575f, - 0.04888871785298119f, - 0.06350208150118342f, - 0.07694602080488765f, - 0.08839667839951919f, - 0.09713857345310689f, - 0.10261800054825827f, - 0.10448449122868045f, - 0.10261800054825827f, - 0.09713857345310689f, - 0.08839667839951919f, - 0.07694602080488765f, - 0.06350208150118342f, - 0.04888871785298119f, - 0.03397630130130575f, - 0.019617756508396658f, - 0.006588374127832776f, - -0.004467461602914141f, - -0.01307870343426492f, - -0.018974044064189968f, - -0.022091348052909576f, - -0.022572656974485978f, - -0.020740073978089375f, - -0.01705984630504337f, - -0.01209866234178173f, - -0.006468598011493521f, - -0.0007776763994276021f, - 0.0044226259943339384f, - 0.008677354388180201f, - 0.011660378948982f, - 0.013192709905840089f, - 0.013247115357734634f, - 0.011939275012512792f, - 0.009506525921431109f, - 0.006276994042887084f, - 0.002632475569255834f, - -0.0010313778997952953f, - -0.004343750499151247f, - -0.006992771039067642f, - -0.008752165260963618f, - -0.00949819102645701f, - -0.009215708970605762f, - -0.007993440148818328f, - -0.0060094188052075385f, - -0.0035085807133030038f, - -0.0007749973681354589f, - 0.0018984740602875209f, - 0.004239992353086172f, - 0.0060250187066667035f, - 0.007096715159487296f, - 0.007378348952494577f, - 0.006877057686831076f, - 0.005680643711722206f, - 0.003942664171895544f, - 0.0018679968592255536f, - -0.0003154863263286177f, - -0.0023757889373754375f, - -0.004104133685973202f, - -0.0053348632172493865f, - -0.005958790652492603f, - -0.0059345294154884536f, - -0.005289473467472686f, - -0.0041134371938038455f, - -0.0025480549808695363f, - -0.000770898857558077f, - 0.001024987337059874f, - 0.002651861057270526f, - 0.003945677268175109f, - 0.004782051229146126f, - 0.00508852374186094f, - 0.004850497409323919f, - 0.00411025264482529f, - 0.002960705851903475f, - 0.0015348340498211675f, - -0.000009312999857163947f, - -0.0015063684237781073f, - -0.0028010281264617036f, - -0.0037634623427922525f, - -0.004302245531766623f, - -0.004372950526822947f, - -0.003981099599522876f, - -0.00318050748566926f, - -0.002066921044075508f, - -0.0007653431253644171f, - 0.0005825119501486656f, - 0.0018334905123878731f, - 0.0028590450497876694f, - 0.0035563826341158213f, - 0.0038610298842211605f, - 0.003750429521389635f, - 0.0032466971416879246f, - 0.0024123674642028033f, - 0.00134312158248896f, - 0.00015714303240366978f, - -0.0010178803764328268f, - -0.0020583398437455315f, - -0.002857634299302074f, - -0.0033370666249195324f, - -0.0034533803843191375f, - -0.0032023981947833136f, - -0.0026186149816637165f, - -0.0017707823148707424f, - -0.0007540149497279023f, - 0.00032073673872905727f, - 0.0013387749941891575f, - 0.0021938662312239255f, - 0.002799180323785052f, - 0.003095930462187589f, - 0.003058968365749582f, - 0.002698864465909288f, - 0.0020601250277244316f, - 0.0012156374711696657f, - 0.00025858155245919815f, - -0.0007073681818317356f, - -0.0015807551810501244f, - -0.0022693572465456347f, - -0.002706332216634437f, - -0.002847415468166842f, - -0.002685049904781321f, - -0.002241555313336647f, - -0.0015677281601321103f, - -0.0007401321025391216f, - 0.00015080284754578053f, - 0.001010469529718797f, - 0.0017482590981993708f, - 0.0022874947808971076f, - 0.0025746366972740037f, - 0.0025840361579153536f, - 0.0023191095738080646f, - 0.0018119245687932325f, - 0.0011202470135660719f, - 0.0003207533398910953f, - -0.0005001275988053344f, - -0.0012553733554405261f, - -0.0018659822196923194f, - -0.0022692307984843695f, - -0.002425430974535461f, - -0.0023218012876373207f, - -0.001973351045206107f, - -0.0014208542690678963f, - -0.0007258491765731604f, - 0.000036170113001030666f, - 0.000783301048300022f, - 0.001436104198970914f, - 0.0019268362424314605f, - 0.0022053677261764473f, - 0.0022452826272775055f, - 0.0020457879850275806f, - 0.0016308959748307101f, - 0.001047910209033917f, - 0.00036046532149158213f, - -0.0003566571442405154f, - -0.0010269473735399154f, - -0.001579698202436061f, - -0.0019576726289922586f, - -0.0021230164816546412f, - -0.002060772342951284f, - -0.0017802301804471922f, - -0.0013137753201852866f, - -0.0007131888239163173f, - -0.00004396586555373278f, - 0.0006218226272857692f, - 0.0012133648938024923f, - 0.001668583653638679f, - 0.0019405399480025168f, - 0.002002271293795752f, - 0.0018494543772917837f, - 0.0015005576753828536f, - 0.0009946245737452108f, - 0.00038702570052167175f, - -0.0002564820252824784f, - -0.0008669516848493666f, - -0.0013797190792108685f, - -0.0017410363220249359f, - -0.0019137822019732152f, - -0.0018816779265696613f, - -0.0016485023632017621f, - -0.0012439856702169039f, - -0.0007071978327354896f, - -0.00010287888971484906f, - 0.0005087929550228872f, - 0.0010620969968500548f, - 0.0014959523321337515f, - 0.0017665803736364497f, - 0.0018474313131084748f, - 0.0017300695180374488f, - 0.001427583798294352f, - 0.0009743368473658559f, - 0.0004203972508663324f, - -0.0001750373426426684f, - -0.00074891505028209f, - -0.001239958862296361f, - -0.0015955792180418218f, - -0.0017782857455891608f, - -0.0017696752225057275f, - -0.0015718235937901522f, - -0.0012066408059786802f, - -0.0007134285789517371f, - -0.00014493157532811753f, - 0.00043789676780500817f, - 0.0009725070918705811f, - 0.001401938565514821f, - 0.0016808579160732414f, - 0.0017799814611489096f, - 0.001689241246422699f, - 0.001418713685742809f, - 0.0009969768021073388f, - 0.00046927876230741587f, - -0.00010821707107113083f, - -0.0006738084260047715f, - -0.0011668823340746201f, - -0.0015352614436320124f, - -0.0017393843977854672f, - -0.0017577706864892022f, - -0.0015880719715790936f, - -0.0012479991812573157f, - -0.0007733385753572961f, - -0.00021414834948363428f, - 0.00037029761935479554f, - 0.0009178041117923993f, - 0.0013699023886079994f, - 0.00167799070404902f, - 0.0018085733127357138f, - 0.0017469004431845741f, - 0.0014984460140958136f, - 0.0010883547994854647f, - 0.0005590116846630576f, - -0.000034277486241399186f, - -0.0006290757127071664f, - -0.0011623018660948076f, - -0.0015767572410517868f, - -0.001827180245955061f, - -0.001885184212057672f, - -0.0017423327816480623f, - -0.001411151838185113f, - -0.0009242244673079876f, - -0.00033072041687547837f, - 0.00030908834306052194f, - 0.0009254779772329933f, - 0.001463759939291864f, - 0.00184518937028339f, - 0.002054368304440125f, - 0.0020485384828633777f, - 0.0018238726340835366f, - 0.0014076463254305174f, - 0.0008397052861045773f, - 0.00017203581592664565f, - -0.0005293364966829129f, - -0.0011899674040168643f, - -0.0017380160407736585f, - -0.0021136819231921474f, - -0.002274533536918245f, - -0.0021983440926621103f, - -0.0018849613428995964f, - -0.0013572018806166464f, - -0.0006600057708364918f, - 0.0001428944628485573f, - 0.0009750187684900944f, - 0.0017550758096436146f, - 0.002405247908616018f, - 0.002858826770125135f, - 0.003066845756573675f, - 0.003003052919640715f, - 0.0026667562654551246f, - 0.0020838354221374305f, - 0.0013056441129537509f, - 0.00040506185948651675f, - -0.0005294931107453994f, - -0.001401086778987431f, - -0.00211370881796881f, - -0.002579780077995427f, - -0.00272885000805846f, - -0.0025137861299055595f, - -0.0019147615431778597f, - -0.0009423273945619157f, - 0.00036406537300421297f, - 0.0019376716631701368f, - 0.003689888284206567f, - 0.005516680433668245f, - 0.007306289027941143f, - 0.008947826461137941f, - 0.010339492321850014f, - 0.011395973228693276f, - 0.012054670302231996f, - 0.01227985703081096f, - 0.012064461696610605f, - 0.011429759533610568f, - 0.010422968037142797f, - 0.009112816853143859f, - 0.007583700132903604f, - 0.005929041211240287f, - 0.004244279668461443f, - 0.002620018618160862f, - 0.0011360279713313209f, - -0.00014348647168516363f, - -0.0011732914244696805f, - -0.0019288301578398123f, - -0.0024058915455907196f, - -0.002619226598223891f, - -0.002599310268302821f, - -0.0023884346335371165f, - -0.006122920352830965f - } ), - - /* Half-band filter coefficients retrieved October 2015 from: - * https://github.com/airspy/host/libairspy/src/filters.h - * - * Copyright (C) 2014, Youssef Touil - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - ******************************************************************************/ - HALF_BAND_FILTER_47T( new float[] - { - -0.000998606272947510f, 0.0f, 0.001695637278417295f, 0.0f, - -0.003054430179754289f, 0.0f, 0.005055504379767936f, 0.0f, - -0.007901319195893647f, 0.0f, 0.011873357051047719f, 0.0f, - -0.017411159379930066f, 0.0f, 0.025304817427568772f, 0.0f, - -0.037225225204559217f, 0.0f, 0.057533286997004301f, 0.0f, - -0.102327462004259350f, 0.0f, 0.317034472508947400f, 0.5f, - 0.317034472508947400f, 0.0f, -0.102327462004259350f, 0.0f, - 0.057533286997004301f, 0.0f, -0.037225225204559217f, 0.0f, - 0.025304817427568772f, 0.0f, -0.017411159379930066f, 0.0f, - 0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f, - 0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f, - 0.001695637278417295f, 0.0f, -0.000998606272947510f - }), - - /** - * FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - * .sampleRate(100) - * .gridDensity(16) - * .oddLength(true) - * .passBandCutoff(21) - * .passBandAmplitude(1.0) - * .passBandRipple(0.01) - * .stopBandStart(29) - * .stopBandAmplitude(0.0) - * .stopBandRipple(0.01) //Approximately -85 dB suppression - * .build(); - */ - HALF_BAND_FILTER_27T(new float[] - { - 0.007495115f, 0.0f, -0.011045938f, 0.0f, - 0.019129561f, 0.0f, -0.031747308f, 0.0f, - 0.053220540f, 0.0f, -0.099569574f, 0.0f, - 0.316083550f, 0.5f, 0.316083550f, 0.0f, - -0.099569574f, 0.0f, 0.053220540f, 0.0f, - -0.031747308f, 0.0f, 0.019129561f, 0.0f, - -0.011045938f, 0.0f, 0.007495115f - - }); - - float[] mCoefficients; - - private Filters( float[] coefficients ) - { - mCoefficients = coefficients; - } - - public float[] getCoefficients() - { - return mCoefficients; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandFilter.java deleted file mode 100644 index 4722d9f2..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandFilter.java +++ /dev/null @@ -1,204 +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.real.RealSampleListener; -import org.apache.commons.lang3.Validate; - -import java.util.ArrayList; - -public class FloatHalfBandFilter implements RealSampleListener -{ - private RealSampleListener mListener; - private ArrayList mBuffer; - private int mBufferSize = 1; //Temporary initial value - private int mBufferPointer = 0; - private float mGain; - private float[] mCoefficients; - private int[][] mIndexMap; - private int mCenterCoefficient; - private int mCenterCoefficientMapIndex; - private boolean mDispatchFlag = false; - - - /** - * Half-Band filter with decimation by 2 against real valued floats. - * - * Takes advantage of the symmetrical nature of FIR filter coefficients by - * adding oldest and newest sample first, then multiplying once by the - * corresponding coefficient - * - * Also, takes advantage of the 0-valued FIR half-band coefficents inherent - * in the half-band filter, and does not calculate those coefficients. - * - * This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample. - * - * @param filter - filter coefficients - * @param gain - gain multiplier. Use 1.0 for unity/no gain - */ - public FloatHalfBandFilter( Filters filter, float gain ) - { - mCoefficients = filter.getCoefficients(); - mBuffer = new ArrayList(); - mBufferSize = mCoefficients.length; - - //Fill the buffer with zero valued samples - for( int x = 0; x < mCoefficients.length; x++ ) - { - mBuffer.add( 0.0f ); - } - - generateIndexMap( mCoefficients.length ); - mGain = gain; - } - - public void dispose() - { - mListener = null; - } - - /** - * Calculate the filtered value by applying the coefficients against - * the complex samples in mBuffer - */ - public void receive( float newSample ) - { - //Add the new sample to the buffer - mBuffer.set( mBufferPointer, newSample ); - - //Increment & Adjust the buffer pointer for circular wrap around - mBufferPointer++; - - if( mBufferPointer >= mBufferSize ) - { - mBufferPointer = 0; - } - - //Toggle the flag every time, so that we can calculate and dispatch a - //sample every other time, when the flag is true (ie decimate by 2) - mDispatchFlag = !mDispatchFlag; - - //Calculate a filtered sample when the flag is true - if( mDispatchFlag ) - { - //Convolution - multiply filter coefficients by the circular buffer - //samples to calculate a new filtered value - float accumulator = 0; - - //Start with the center tap value - accumulator += mCoefficients[ mCenterCoefficient ] * - mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ); - - //For the remaining coefficients, add the symmetric samples, oldest and newest - //first, then multiply by the single coefficient - for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 ) - { - accumulator += mCoefficients[ x ] * - ( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ) + - mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) ); - } - - //We're almost finished ... apply gain, cast the floats to shorts and - //send it on it's merry way - if( mListener != null ) - { - mListener.receive( (float)( accumulator * mGain ) ); - } - } - } - - /** - * Creates an n X (n + 1 / 2) index map enabling quick access to the - * circular buffer samples. - * - * As the buffer shifts right with each subsequent sample, we have to move - * the index pointers with it, for efficient access of the samples. - * - * The first array index value in the index map corresponds to the current - * buffer pointer location. - * - * The second array index value points to the samples that should be - * multiplied by the coefficients as follows: - * - * 0 = center tap sample, to be multiplied by center coefficient - * - * 0 = sample( 1 ) - * 1 = sample( size - 1 ) - * - * Indexes 0 and 1 will be multiplied by coefficient( 0 ). - * - * Subsequent indexes 3, 4, etc, point to the oldest and newest samples that - * correspond to the matching ( 3 ) coefficient index. - * - * @param odd-sized number of filter taps (ie coefficients) and buffer - */ - private void generateIndexMap( int size ) - { - //Ensure we have an odd size - Validate.isTrue(size % 2 == 1); - - int mapWidth = ( ( size + 1 ) / 2 ) + 1; - - //Set center tap index for coefficients array - mCenterCoefficient = ( size - 1 ) / 2; - mCenterCoefficientMapIndex = mCenterCoefficient + 1; - - mIndexMap = new int[ size ][ mapWidth ]; - - //Setup the first row, buffer pointer index 0, as a starting point - for( int x = 0; x < mapWidth - 2; x += 2 ) - { - mIndexMap[ 0 ][ x ] = x; - mIndexMap[ 0 ][ x + 1 ] = size - 1 - x; - } - - //Place center tap index in last element - mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient; - - //For each subsequent row, increment the previous row's value by 1, - //subtracting size as needed, to keep the values between 0 and size - 1 - for( int x = 1; x < size; x++ ) - { - for( int y = 0; y < mapWidth; y++ ) - { - mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1; - - if( mIndexMap[ x ][ y ] >= size ) - { - mIndexMap[ x ][ y ] -= size; - } - } - } - } - - /** - * Registers a listener for filtered samples - */ - public void setListener( RealSampleListener listener ) - { - mListener = listener; - } - - /** - * Removes (if exists) a registered filtered sample listener - */ - public void clearListener() - { - mListener = null; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandNoDecimateFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandNoDecimateFilter.java deleted file mode 100644 index 87a5f4cd..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandNoDecimateFilter.java +++ /dev/null @@ -1,194 +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.real.RealSampleListener; -import org.apache.commons.lang3.Validate; - -import java.util.ArrayList; - -public class FloatHalfBandNoDecimateFilter implements RealSampleListener -{ - private RealSampleListener mListener; - private ArrayList mBuffer; - private int mBufferSize = 1; //Temporary initial value - private int mBufferPointer = 0; - private float mGain; - private float[] mCoefficients; - private int[][] mIndexMap; - private int mCenterCoefficient; - private int mCenterCoefficientMapIndex; - - /** - * Half-Band filter with decimation by a real valued float. - * - * Takes advantage of the symmetrical nature of FIR filter coefficients by - * adding oldest and newest sample first, then multiplying once by the - * corresponding coefficient - * - * Also, takes advantage of the 0-valued FIR half-band coefficents inherent - * in the half-band filter, and does not calculate those coefficients. - * - * This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample. - * - * @param filter - filter coefficients - * @param gain - gain multiplier. Use 1.0 for unity/no gain - */ - public FloatHalfBandNoDecimateFilter( Filters filter, float gain ) - { - mCoefficients = filter.getCoefficients(); - mBuffer = new ArrayList(); - mBufferSize = mCoefficients.length; - - //Fill the buffer with zero valued samples - for( int x = 0; x < mCoefficients.length; x++ ) - { - mBuffer.add( 0.0f ); - } - - generateIndexMap( mCoefficients.length ); - mGain = gain; - } - - public void dispose() - { - mListener = null; - } - - /** - * Calculate the filtered value by applying the coefficients against - * the complex samples in mBuffer - */ - public void receive( float newSample ) - { - //Add the new sample to the buffer - mBuffer.set( mBufferPointer, newSample ); - - //Increment & Adjust the buffer pointer for circular wrap around - mBufferPointer++; - - if( mBufferPointer >= mBufferSize ) - { - mBufferPointer = 0; - } - - //Convolution - multiply filter coefficients by the circular buffer - //samples to calculate a new filtered value - float accumulator = 0; - - //Start with the center tap value - accumulator += mCoefficients[ mCenterCoefficient ] * - mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ); - - //For the remaining coefficients, add the symmetric samples, oldest and newest - //first, then multiply by the single coefficient - for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 ) - { - accumulator += mCoefficients[ x ] * - ( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ) + - mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) ); - } - - //We're almost finished ... apply gain, cast the floats to floats and - //send it on it's merry way - if( mListener != null ) - { - mListener.receive( (float)( accumulator * mGain ) ); - } - } - - /** - * Creates an n X (n + 1 / 2) index map enabling quick access to the - * circular buffer samples. - * - * As the buffer shifts right with each subsequent sample, we have to move - * the index pointers with it, for efficient access of the samples. - * - * The first array index value in the index map corresponds to the current - * buffer pointer location. - * - * The second array index value points to the samples that should be - * multiplied by the coefficients as follows: - * - * 0 = center tap sample, to be multiplied by center coefficient - * - * 0 = sample( 1 ) - * 1 = sample( size - 1 ) - * - * Indexes 0 and 1 will be multiplied by coefficient( 0 ). - * - * Subsequent indexes 3, 4, etc, point to the oldest and newest samples that - * correspond to the matching ( 3 ) coefficient index. - * - * @param odd-sized number of filter taps (ie coefficients) and buffer - */ - private void generateIndexMap( int size ) - { - //Ensure we have an odd size - Validate.isTrue(size % 2 == 1); - - int mapWidth = ( ( size + 1 ) / 2 ) + 1; - - //Set center tap index for coefficients array - mCenterCoefficient = ( size - 1 ) / 2; - mCenterCoefficientMapIndex = mCenterCoefficient + 1; - - mIndexMap = new int[ size ][ mapWidth ]; - - //Setup the first row, buffer pointer index 0, as a starting point - for( int x = 0; x < mapWidth - 2; x += 2 ) - { - mIndexMap[ 0 ][ x ] = x; - mIndexMap[ 0 ][ x + 1 ] = size - 1 - x; - } - - //Place center tap index in last element - mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient; - - //For each subsequent row, increment the previous row's value by 1, - //subtracting size as needed, to keep the values between 0 and size - 1 - for( int x = 1; x < size; x++ ) - { - for( int y = 0; y < mapWidth; y++ ) - { - mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1; - - if( mIndexMap[ x ][ y ] >= size ) - { - mIndexMap[ x ][ y ] -= size; - } - } - } - } - - /** - * Registers a listener for filtered samples - */ - public void setListener( RealSampleListener listener ) - { - mListener = listener; - } - - /** - * Removes (if exists) a registered filtered sample listener - */ - public void clearListener() - { - mListener = null; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/GoertzelFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/GoertzelFilter.java index 6cb112e8..b0ce0805 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/GoertzelFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/GoertzelFilter.java @@ -1,30 +1,30 @@ /******************************************************************************* * 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.dsp.filter.Window.WindowType; +import io.github.dsheirer.dsp.window.WindowFactory; +import io.github.dsheirer.dsp.window.WindowType; import org.apache.commons.math3.util.FastMath; - /** * Implements the Goertzel Filter as described in * http://en.wikipedia.org/wiki/Goertzel_algorithm - * + *

* Calculates the magnitude of a specific frequency component within a window of * samples, using a Goertzel Filter (ie an optimized DFT). */ @@ -33,34 +33,27 @@ public class GoertzelFilter private int mSampleRate; private long mTargetFrequency; private int mBlockSize; - private double[] mWindowCoefficients; + private float[] mWindowCoefficients; private double mCoefficient; /** * Constructs the Goertzel Filter to use for block processing of * time-domain signal samples. - * - * @param sampleRate - * - sample rate (Hz) - * @param targetFrequency - * - target frequency (Hz) - * @param blockSize - * - number of samples per block of processing - * @param window - * - window to apply against the samples prior to calculating - * the magnitude + * + * @param sampleRate - sample rate (Hz) + * @param targetFrequency - target frequency (Hz) + * @param blockSize - number of samples per block of processing + * @param window - window to apply against the samples prior to calculating + * the magnitude */ - public GoertzelFilter( int sampleRate, - long targetFrequency, - int blockSize, - WindowType window ) + public GoertzelFilter(int sampleRate, long targetFrequency, int blockSize, WindowType window) { - mSampleRate = sampleRate; - mTargetFrequency = targetFrequency; - mBlockSize = blockSize; - mWindowCoefficients = Window.getWindow( window, blockSize ); - init(); + mSampleRate = sampleRate; + mTargetFrequency = targetFrequency; + mBlockSize = blockSize; + mWindowCoefficients = WindowFactory.getWindow(window, blockSize); + init(); } /** @@ -68,77 +61,74 @@ public class GoertzelFilter */ private void init() { - double normalizedFrequency = (double) mTargetFrequency / mSampleRate; - mCoefficient = 2.0D * FastMath.cos( 2 * FastMath.PI * normalizedFrequency ); + double normalizedFrequency = (double)mTargetFrequency / mSampleRate; + mCoefficient = 2.0D * FastMath.cos(2 * FastMath.PI * normalizedFrequency); } - /** - * Returns the power (dB) of the target frequency within the block of + * Returns the power (dB) of the target frequency within the block of * samples as normalized against the bin(0) magnitude value - * - * @param samples - * - array of time-domain samples -- array size must be the same - * size as the windowSize parameter passed upon construction - * + * + * @param samples - array of time-domain samples -- array size must be the same + * size as the windowSize parameter passed upon construction * @return - power measurement in dB - * @throws IllegalArgumentException if the sample array size is not equal - * to the defined block size + * to the defined block size */ - public int getPower( float[] samples ) throws IllegalArgumentException + public int getPower(float[] samples) throws IllegalArgumentException { - // Verify size of samples array against block size - if ( samples.length != mBlockSize ) - { - throw new IllegalArgumentException( - "Sample array size does not equal Block Size" ); - } - - // Apply the window against the samples - samples = Window.apply( mWindowCoefficients, samples ); - - // Process the samples - double s = 0.0D; - double s_prev = 0.0D; - double s_prev2 = 0.0D; + // Verify size of samples array against block size + if(samples.length != mBlockSize) + { + throw new IllegalArgumentException("Sample array size does not equal Block Size"); + } - for (float sample : samples) { + // Apply the window against the samples + samples = WindowFactory.apply(mWindowCoefficients, samples); + + // Process the samples + double s = 0.0D; + double s_prev = 0.0D; + double s_prev2 = 0.0D; + + for(float sample : samples) + { s = sample + (mCoefficient * s_prev) - s_prev2; s_prev2 = s_prev; s_prev = s; } - - //power = s_prev2 * s_prev2 + s_prev * s_prev - coeff * s_prev * s_prev2 ; - - double magnitude = ( s_prev2 * s_prev2 ) + ( s_prev * s_prev) - (mCoefficient * s_prev * s_prev2); - int binZero = getBinZeroPower( samples ); - - int power = (int) (20 * FastMath.log10( magnitude / binZero ) ); - - return power; + + //power = s_prev2 * s_prev2 + s_prev * s_prev - coeff * s_prev * s_prev2 ; + + double magnitude = (s_prev2 * s_prev2) + (s_prev * s_prev) - (mCoefficient * s_prev * s_prev2); + int binZero = getBinZeroPower(samples); + + int power = (int)(20 * FastMath.log10(magnitude / binZero)); + + return power; } /** * Sums all of the sample values to derive the bin 0 power */ - private int getBinZeroPower( float[] samples ) + private int getBinZeroPower(float[] samples) { int retVal = 0; - for (float sample : samples) { + for(float sample : samples) + { retVal += sample; } - + return retVal; } - + /** * @return the Sample Rate */ public int getSampleRate() { - return mSampleRate; + return mSampleRate; } /** @@ -146,7 +136,7 @@ public class GoertzelFilter */ public long getTargetFrequency() { - return mTargetFrequency; + return mTargetFrequency; } /** @@ -154,7 +144,7 @@ public class GoertzelFilter */ public int getBlockSize() { - return mBlockSize; + return mBlockSize; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/Window.java b/src/main/java/io/github/dsheirer/dsp/filter/Window.java deleted file mode 100644 index c0a7e4d5..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/Window.java +++ /dev/null @@ -1,505 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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 org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.EnumSet; - -/** - * Window factory and corresponding utility methods. - */ -public class Window -{ - private final static Logger mLog = LoggerFactory.getLogger(Window.class); - - private static final double TWO_PI = FastMath.PI * 2.0d; - private static final double FOUR_PI = FastMath.PI * 4.0d; - private static final double SIX_PI = FastMath.PI * 6.0d; - private static final double EIGHT_PI = FastMath.PI * 8.0d; - - /** - * Creates a window of the specified type and length. - * - * @param type of window - * @param length of window - * @return window of the specified type and length - */ - public static double[] getWindow(WindowType type, int length) - { - switch(type) - { - case BLACKMAN: - return getBlackman(length); - case BLACKMAN_HARRIS_4: - return getBlackmanHarris4(length); - case BLACKMAN_HARRIS_7: - return getBlackmanHarris7(length); - case BLACKMAN_NUTALL: - return getBlackmanNutall(length); - case COSINE: - return getCosine(length); - case FLAT_TOP: - return getFlatTop(length); - case HAMMING: - return getHamming(length); - case HANN: - return getHann(length); - case KAISER: - throw new IllegalArgumentException("Kaiser Window cannot be created via this method. Use the getKaiser() method instead."); - case NUTALL: - return getNutall(length); - case NONE: - default: - return getRectangular(length); - } - } - - /** - * Creates an all-pass or rectangular window. - * - * @param length of the window - * @return rectangular window - */ - public static double[] getRectangular(int length) - { - double[] coefficients = new double[length]; - - Arrays.fill(coefficients, 1.0d); - - return coefficients; - } - - /** - * Creates a cosine window of the specified length. - * - * @param length of the window - * @return cosine window - */ - public static double[] getCosine(int length) - { - double[] coefficients = new double[length]; - - if(length % 2 == 0) //Even length - { - int half = (int)((length - 1) / 2); - - for(int x = -half; x < length / 2 + 1; x++) - { - coefficients[x + half] = FastMath.cos( - ((double)x * FastMath.PI) / ((double)length + 1.0d)); - } - } - else //Odd length - { - int half = (int)length / 2; - - for(int x = -half; x < half + 1; x++) - { - coefficients[x + half] = FastMath.cos( - ((double)x * FastMath.PI) / ((double)length + 1.0d)); - } - } - - return coefficients; - } - - /** - * Creates a 3-term Blackman window. - * - * @param length of the window - * @return Blackman window - */ - public static double[] getBlackman(int length) - { - double[] coefficients = new double[length]; - - double denominator = length - 1; - - double a0 = 0.426590713672; - double a1 = 0.496560619089; - double a2 = 0.0768486672399; - - for(int x = 0; x < length; x++) - { - coefficients[x] = a0 - - (a1 * FastMath.cos((TWO_PI * (double)x) / denominator)) + - (a2 * FastMath.cos((FOUR_PI * (double)x) / denominator)); - } - - return coefficients; - } - - /** - * Creates a 4-term Nutall window - * - * @param length of the window - * @return Nutall window - */ - public static double[] getNutall(int length) - { - double[] coefficients = new double[length]; - - double denominator = length - 1; - double a0 = 0.355768; - double a1 = 0.487396; - double a2 = 0.144232; - double a3 = 0.012604; - - for(int x = 0; x < length; x++) - { - coefficients[x] = a0 - - (a1 * FastMath.cos(TWO_PI * (double)x / denominator)) + - (a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) - - (a3 * FastMath.cos(SIX_PI * (double)x / denominator)); - } - - return coefficients; - } - - /** - * Creates a 4-term Blackman-Nutall window - * - * @param length of the window - * @return Blackman-Nutall window - */ - public static double[] getBlackmanNutall(int length) - { - double[] coefficients = new double[length]; - - double denominator = length - 1; - double a0 = 0.3635819; - double a1 = 0.4891775; - double a2 = 0.1365995; - double a3 = 0.0106411; - - for(int x = 0; x < length; x++) - { - coefficients[x] = a0 - - (a1 * FastMath.cos(TWO_PI * (double)x / denominator)) + - (a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) - - (a3 * FastMath.cos(SIX_PI * (double)x / denominator)); - } - - return coefficients; - } - - /** - * Creates a 4-term Blackman-Harris window - * - * @param length of the window - * @return Blackman-Harris window - */ - public static double[] getBlackmanHarris4(int length) - { - double[] coefficients = new double[length]; - - double denominator = length - 1; - double a0 = 0.35875; - double a1 = 0.48829; - double a2 = 0.14128; - double a3 = 0.01168; - - for(int x = 0; x < length; x++) - { - coefficients[x] = a0 - - (a1 * FastMath.cos(TWO_PI * (double)x / denominator)) + - (a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) - - (a3 * FastMath.cos(SIX_PI * (double)x / denominator)); - } - - return coefficients; - } - - /** - * Creates a 7-term Blackman-Harris window - * - * @param length of the window - * @return Blackman-Harris window - */ - public static double[] getBlackmanHarris7(int length) - { - double[] coefficients = new double[length]; - - double denominator = length - 1; - - double a0 = 0.27105140069342; - double a1 = 0.43329793923448; - double a2 = 0.21812299954311; - double a3 = 0.06592544638803; - double a4 = 0.01081174209837; - double a5 = 0.00077658482522; - double a6 = 0.00001388721735; - - - for(int x = 0; x < length; x++) - { - double w = TWO_PI * (double)x / denominator; - - coefficients[x] = a0 - - (a1 * FastMath.cos(w)) + - (a2 * FastMath.cos(2.0 * w)) - - (a3 * FastMath.cos(3.0 * w)) + - (a4 * FastMath.cos(4.0 * w)) - - (a5 * FastMath.cos(5.0 * w)) + - (a6 * FastMath.cos(6.0 * w)); - } - - return coefficients; - } - - /** - * Creates a Hamming window - * - * @param length of the window - * @return Hamming window - */ - public static double[] getHamming(int length) - { - double[] coefficients = new double[length]; - - for(int x = 0; x < length; x++) - { - coefficients[x] = 0.54 - (0.46 * FastMath.cos((TWO_PI * x) / (length - 1))); - } - - return coefficients; - } - - /** - * Creates a Hann window - * - * @param length of the window - * @return Hann window - */ - public static double[] getHann(int length) - { - double[] coefficients = new double[length]; - - for(int x = 0; x < length; x++) - { - coefficients[x] = 0.5 - (0.5 * FastMath.cos(TWO_PI * (double)x / (length - 1))); - } - - return coefficients; - } - - /** - * Creates a 4-term Blackman-Harris window - * - * @param length of the window - * @return Blackman-Harris window - */ - public static double[] getFlatTop(int length) - { - double[] coefficients = new double[length]; - - double denominator = length - 1; - - double a0 = 0.215578948; - double a1 = 0.41663158; - double a2 = 0.277263158; - double a3 = 0.083578947; - double a4 = 0.006947368; - - for(int x = 0; x < length; x++) - { - coefficients[x] = a0 - - (a1 * FastMath.cos(TWO_PI * (double)x / denominator)) + - (a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) - - (a3 * FastMath.cos(SIX_PI * (double)x / denominator)) + - (a4 * FastMath.cos(EIGHT_PI * (double)x / denominator)); - } - - return coefficients; - } - - /** - * Calculates the beta (roll-off factor) for a Kaiser window based on the specified attenuation. - * - * @param attenuation in decibels - * @return beta value in the range of 0.0 to 1.0 - */ - public static double getKaiserBeta(double attenuation) - { - if(attenuation > 50.0) - { - return 0.1102 * (attenuation - 8.7); - } - else if(attenuation >= 21.0) - { - return (0.5842 * FastMath.pow(attenuation - 21.0, 0.4)) + (0.07886 * (attenuation - 21.0)); - } - else - { - return 0.0; - } - } - - /** - * Creates a Kaiser window. - * - * @param length of the window - * @param attenuation desired - * @return window - */ - public static double[] getKaiser(int length, double attenuation) - { - double[] coefficients = new double[length]; - - double beta = getKaiserBeta(attenuation); - - double betaBesselZerothOrder = getBesselZerothOrder(beta); - - double temp; - - for(int x = 0; x < coefficients.length; x++) - { - temp = beta * FastMath.sqrt(1.0 - FastMath.pow(2.0 * x / (length - 1) - 1.0, 2)); - coefficients[x] = getBesselZerothOrder(temp) / betaBesselZerothOrder; - } - - return coefficients; - } - - - /** - * Zeroth order modified Bessel function. - * - * @param x Value. - * @return Return value. - */ - public final static double getBesselZerothOrder(final double x) - { - double f = 1; - final double x2 = x * x * 0.25; - double xc = x2; - double v = 1 + x2; - for (int i = 2; i < 100; i++) - { - f *= i; - xc *= x2; - final double a = xc / (f * f); - v += a; - if (a < 1e-20) break; - } - - return v; - } - - /** - * Apply the window against an array of float-type samples - */ - public static float[] apply(double[] coefficients, float[] samples) - { - for(int x = 0; x < coefficients.length; x++) - { - samples[x] = (float)(samples[x] * coefficients[x]); - } - - return samples; - } - - /** - * Apply the window type against the float array of samples - * - * @param type of window to use - * @param samples to be windowed - * @return windowed samples - */ - public static float[] apply(WindowType type, float[] samples) - { - double[] coefficients = getWindow(type, samples.length); - - return apply(coefficients, samples); - } - - /** - * Apply the window against an array of double-type samples - */ - public static double[] apply(double[] coefficients, double[] samples) - { - for(int x = 0; x < coefficients.length; x++) - { - samples[x] = samples[x] * coefficients[x]; - } - - return samples; - } - - /** - * Apply the window type against the double array of samples - * - * @param type of window to use - * @param samples to be windowed - * @return windowed samples - */ - public static double[] apply(WindowType type, double[] samples) - { - double[] coefficients = getWindow(type, samples.length); - - return apply(coefficients, samples); - } - - /** - * Window types - */ - public enum WindowType - { - BLACKMAN("Blackman"), - BLACKMAN_HARRIS_4("Blackman-Harris 4"), - BLACKMAN_HARRIS_7("Blackman-Harris 7"), - BLACKMAN_NUTALL("Blackman-Nutall"), - COSINE("Cosine"), - FLAT_TOP("Flat Top"), - HAMMING("Hamming"), - HANN("Hann"), - KAISER("Kaiser"), - NUTALL("Nutall"), - NONE("None"); - - private String mLabel; - - WindowType(String label) - { - mLabel = label; - } - - - public String toString() - { - return mLabel; - } - } - - public static final EnumSet NO_PARAMETER_WINDOWS = EnumSet.of(WindowType.BLACKMAN, WindowType.BLACKMAN_HARRIS_4, - WindowType.BLACKMAN_HARRIS_7, WindowType.BLACKMAN_NUTALL, WindowType.COSINE, WindowType.FLAT_TOP, - WindowType.HAMMING, WindowType.HANN, WindowType.NUTALL); - - public static void main(String[] args) - { - double[] kaiser = getKaiser(16, 80.0); - - mLog.debug("Window:" + Arrays.toString(kaiser)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java index 09f6d34a..4c9a7fe0 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,15 +14,13 @@ * * 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.channelizer; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; -import io.github.dsheirer.sample.buffer.ReusableChannelResultsBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; @@ -31,10 +29,9 @@ import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -public abstract class AbstractComplexPolyphaseChannelizer implements Listener, ISourceEventListener +public abstract class AbstractComplexPolyphaseChannelizer implements Listener, ISourceEventListener { private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class); - private ReusableChannelResultsBufferQueue mBufferQueue = new ReusableChannelResultsBufferQueue("AbstractComplexPolyphaseChannelizer"); private Broadcaster mSourceChangeBroadcaster = new Broadcaster(); private List mChannels = new CopyOnWriteArrayList<>(); private double mSampleRate; @@ -103,17 +100,14 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener channelResultsList) { for(PolyphaseChannelSource channel : mChannels) { - channelResultsBuffer.incrementUserCount(); - channel.receiveChannelResults(channelResultsBuffer); + channel.receiveChannelResults(channelResultsList); } - - channelResultsBuffer.decrementUserCount(); } /** @@ -152,16 +146,6 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener getSourceEventListener() { diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java index c8f797e3..80fd940f 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,24 +14,22 @@ * * 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.channelizer; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.design.FilterDesignException; -import io.github.dsheirer.sample.IOverflowListener; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; +import io.github.dsheirer.util.Dispatcher; import org.apache.commons.math3.util.FastMath; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.DecimalFormat; -import java.util.Arrays; -import java.util.Collection; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -67,8 +65,13 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0"); private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000; - //Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers - private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152)); + /** + * Determines how many processed channel results to dispatch for threaded IFFT processing per batch + */ + private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024; + + //Sized at 152 buffers a second where max = 5 seconds + private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(5 * 152); private FloatFFT_1D mFFT; private float[] mInlineSamples; private float[] mInlineFilter; @@ -78,6 +81,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann private int mSampleBufferPointer; private int mSamplesPerBlock; private int mTapsPerChannel; + private List mProcessedChannelResultsList = new ArrayList<>(); /** * Creates a NMDPFB channelizer instance. @@ -128,7 +132,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann */ public void start() { - mIFFTProcessor.start(); + mIFFTProcessorDispatcher.start(); } /** @@ -136,7 +140,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann */ public void stop() { - mIFFTProcessor.stop(); + mIFFTProcessorDispatcher.stop(); } /** @@ -187,12 +191,10 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann * Receives the complex sample buffer and processes the results through the channelizer. */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(InterleavedComplexSamples complexSamples) { - ReusableChannelResultsBuffer channelResultsBuffer = getChannelResultsBuffer(); - channelResultsBuffer.setTimestamp(reusableComplexBuffer.getTimestamp()); - - float[] samples = reusableComplexBuffer.getSamples(); +//TODO: how do we pass the timestamp onto the sample assemblers? + float[] samples = complexSamples.samples(); int samplesPointer = 0; int samplesToCopy; @@ -218,20 +220,19 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann if(mSampleBufferPointer >= mSamplesPerBlock) { //Filter buffered samples and produce a single sample across each of the polyphase channels - process(channelResultsBuffer); + mProcessedChannelResultsList.add(process()); + + if(mProcessedChannelResultsList.size() >= PROCESSED_CHANNEL_RESULTS_THRESHOLD) + { + mIFFTProcessorDispatcher.receive(new ArrayList<>(mProcessedChannelResultsList)); + mProcessedChannelResultsList.clear(); + } //Right-shift the samples in the buffer over to make room for a new block of samples - //Note: since JDK 8, hotspot JIT compiler uses native processor intrinsics for efficiency System.arraycopy(mInlineSamples, 0, mInlineSamples, mSamplesPerBlock, (mInlineSamples.length - mSamplesPerBlock)); mSampleBufferPointer = 0; } } - - //Enqueue the channel results buffer for IFFT processing and distribution on a different thread - mIFFTProcessor.receive(channelResultsBuffer); - - //Decrement the user count to let the originator know we're done with their buffer - reusableComplexBuffer.decrementUserCount(); } /** @@ -334,7 +335,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann * Processes the sample buffer for each new block of sample data that is loaded and distributes the results to any * registered channel listeners. */ - private void process(ReusableChannelResultsBuffer channelResultsBuffer) + private float[] process() { int bufferLength = getSubChannelCount() * mTapsPerChannel; float[] inlineInterimOutput = new float[bufferLength]; @@ -360,7 +361,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann } } - float[] processed = channelResultsBuffer.getEmptyBuffer(getSubChannelCount()); + float[] processed = new float[getSubChannelCount()]; if(mTopBlockIndicator) { @@ -377,9 +378,9 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann } } - channelResultsBuffer.addChannelResults(processed); - mTopBlockIndicator = !mTopBlockIndicator; + + return processed; } /** @@ -389,10 +390,10 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann */ private void init(float[] coefficients) { - mFFT = new FloatFFT_1D(getChannelCount()); int channelCount = getChannelCount(); + mFFT = new FloatFFT_1D(channelCount); int bufferLength = getSubChannelCount() * mTapsPerChannel; - mSamplesPerBlock = getChannelCount(); //Same as subChannelCount / 2 + mSamplesPerBlock = channelCount; //Same as subChannelCount / 2 mTopBlockMap = getTopBlockMap(channelCount); mMiddleBlockMap = getMiddleBlockMap(channelCount); mInlineFilter = getAlignedFilter(coefficients, channelCount, mTapsPerChannel); @@ -404,46 +405,28 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann * as required to align the phase of each polyphase channel, and then dispatch the results to any registered * sample consumer channels. */ - public class IFFTProcessor extends ContinuousBufferProcessor + public class IFFTProcessorDispatcher extends Dispatcher> { - public IFFTProcessor(int maximumSize, int resetThreshold) + public IFFTProcessorDispatcher(int maximumSize) { - super(maximumSize, resetThreshold); + super(maximumSize, "sdrtrunk polyphase ifft processor", Collections.emptyList()); - //We create a listener interface to receive the buffers from the scheduled thread pool + //We create a listener interface to receive the batched channel results arrays from the scheduled thread pool //dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each //channel results array contained in each results buffer and then dispatch the buffer //so that it can be distributed to each channel listener. - setListener(buffers -> { - for(ReusableChannelResultsBuffer buffer: buffers) + setListener(list -> { + List processedChannelResults = new ArrayList<>(); + + for(float[] channelResults: list) { - for(float[] channelResults: buffer.getChannelResults()) - { - //Rotate each of the channels to the correct phase using the IFFT - mFFT.complexInverse(channelResults, true); - } - - dispatch(buffer); + //Rotate each of the channels to the correct phase using the IFFT + mFFT.complexInverse(channelResults, true); + processedChannelResults.add(channelResults); } + + dispatch(processedChannelResults); }); - - setOverflowListener(overflow -> mLog.debug("IFFTProcessor overflow changed - overflow:" + overflow)); - } - - /** - * Clears any buffers from the dispatch/processing queue. Overrides the parent method so that we can set - * the user count to 0 to allow the buffer to be reclaimed. - */ - protected Collection clearQueue() - { - Collection buffersToDispose = super.clearQueue(); - - for(ReusableChannelResultsBuffer buffer: buffersToDispose) - { - buffer.clearUserCount(); - } - - return buffersToDispose; } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java deleted file mode 100644 index 2ffe2587..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java +++ /dev/null @@ -1,227 +0,0 @@ -/******************************************************************************* - * sdr-trunk - * 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.dsp.filter.channelizer; - -import io.github.dsheirer.sample.IOverflowListener; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.OverflowableTransferQueue; -import io.github.dsheirer.util.ThreadPool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public class ContinuousBufferProcessor implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(ContinuousBufferProcessor.class); - - protected OverflowableTransferQueue mQueue; - private Listener> mListener; - private ScheduledFuture mScheduledFuture; - private AtomicBoolean mRunning = new AtomicBoolean(); - private long mProcessingPeriod = 5; //milliseconds - - /** - * Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task - * for periodically distributing internally queued elements to the registered listener. This processor provides - * a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then - * distributing those elements to a registered listener where distribution occurs on a separate scheduled thread - * pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent - * processing workload. - * - * The internal queue is an overflowable queue implementation that allows a listener to be registered to receive - * notifications of overflow and reset state. Queue sizing parameters are specified in the constructor. - * - * @param maximumSize of the internal queue (overflow happens when this is exceeded) - * @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold - */ - public ContinuousBufferProcessor(int maximumSize, int resetThreshold) - { - this(new OverflowableTransferQueue<>(maximumSize, resetThreshold)); - } - - /** - * Listener to receive the queued buffers each time this processor runs. - */ - protected Listener> getListener() - { - return mListener; - } - - /** - * Sets the processing periodicity or run interval for how often the buffer queue is processed. - * - * @param milliseconds (default = 5 milliseconds) - */ - public void setProcessingPeriod(long milliseconds) - { - mProcessingPeriod = milliseconds; - } - - /** - * Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task - * for periodically distributing internally queued elements to the registered listener. This processor provides - * a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then - * distributing those elements to a registered listener where distribution occurs on a separate scheduled thread - * pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent - * processing workload. - * - * The internal queue is an overflowable queue implementation that allows a listener to be registered to receive - * notifications of overflow and reset state. Queue sizing parameters are specified in the constructor. - * - * @param queue implmentation of an overflowable transfer queue - */ - public ContinuousBufferProcessor(OverflowableTransferQueue queue) - { - mQueue = queue; - } - - /** - * Sets the listener to receive notifications of buffer overflow and/or reset. Note: this method can also be - * invoked with a null argument to clear the previously registered overflow listener. - * - * @param listener to receive overflow/reset notifications. - */ - public void setOverflowListener(IOverflowListener listener) - { - mQueue.setOverflowListener(listener); - } - - /** - * Sets or changes the listener to receive buffers from this processor. - * @param listener to receive buffers - */ - public void setListener(Listener> listener) - { - mListener = listener; - } - - /** - * Primary input method for adding buffers to this processor. Note: incoming buffers will be ignored if this - * processor is in a stopped state. You must invoke start() to allow incoming buffers and initiate buffer - * processing. - * - * @param e to enqueue for distribution to a registered listener - */ - @Override - public void receive(E e) - { - mQueue.offer(e); - } - - /** - * Starts this buffer processor and allows queuing of incoming buffers. - */ - public void start() - { - if(mRunning.compareAndSet(false, true)) - { - mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0, mProcessingPeriod, TimeUnit.MILLISECONDS); - } - } - - /** - * Stops this buffer processor, stops queuing of incoming buffers and clears the internal buffer queue. - */ - public void stop() - { - if(mRunning.compareAndSet(true, false)) - { - if(mScheduledFuture != null) - { - mScheduledFuture.cancel(true); - mScheduledFuture = null; - } - - clearQueue(); - } - } - - /** - * Stops this buffer processor,stops queuing of incoming buffers and flushes the internal buffer queue - * contents to the listener. - */ - public void flushAndStop() - { - if(mRunning.compareAndSet(true, false)) - { - if(mScheduledFuture != null) - { - mScheduledFuture.cancel(true); - mScheduledFuture = null; - } - - process(); - } - } - - /** - * Distributes queued buffers to the listener - */ - protected void process() - { - List buffers = new ArrayList<>(); - - try - { - mQueue.drainTo(buffers); - - if(mListener != null) - { - mListener.receive(buffers); - } - } - catch(Throwable throwable) - { - mLog.error("Error while dispatching buffers to listener", throwable); - } - } - - /** - * Clears any buffers from the queue - */ - protected Collection clearQueue() - { - Collection clearedElements = new ArrayList<>(); - mQueue.drainTo(clearedElements); - return clearedElements; - } - - /** - * Indicates if this processor is currently running - */ - public boolean isRunning() - { - return mRunning.get(); - } - - /** - * Processor to service the buffer queue and distribute the buffers to the registered listener - */ - class Processor implements Runnable - { - @Override - public void run() - { - process(); - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java deleted file mode 100644 index 9f3b9cb9..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java +++ /dev/null @@ -1,88 +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.dsp.filter.channelizer; - -import io.github.dsheirer.sample.buffer.AbstractReusableBuffer; -import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -public class ContinuousReusableBufferProcessor extends ContinuousBufferProcessor -{ - private final static Logger mLog = LoggerFactory.getLogger(ContinuousReusableBufferProcessor.class); - - /** - * Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing - * task for periodically distributing internally queued elements to the registered listener. This processor - * provides a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then - * distributing those elements to a registered listener where distribution occurs on a separate scheduled thread - * pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent - * processing workload. - * - * The internal queue is an overflowable queue implementation that allows a listener to be registered to receive - * notifications of overflow and reset state. Queue sizing parameters are specified in the constructor. - * - * @param maximumSize of the internal queue (overflow happens when this is exceeded) - * @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold - */ - public ContinuousReusableBufferProcessor(int maximumSize, int resetThreshold) - { - super(new OverflowableReusableBufferTransferQueue(maximumSize, resetThreshold)); - } - - /** - * Distributes queued buffers to the listener - */ - @Override - protected void process() - { - List buffers = new ArrayList<>(); - - mQueue.drainTo(buffers); - - try - { - if(getListener() != null) - { - getListener().receive(buffers); - } - - buffers.clear(); - } - catch(Throwable throwable) - { - mLog.error("Error while dispatching buffers to listener. Performing buffer user count cleanup", throwable); - - if(!buffers.isEmpty()) - { - for(T buffer: buffers) - { - try - { - buffer.decrementUserCount(); - } - catch(IllegalStateException ise) - { - mLog.error("Error while performing user count cleanup on reusable buffers."); - } - } - } - } - } -} 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 8d6d8941..36e8a749 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 @@ -1,26 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.channelizer; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.INativeBufferProvider; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor; import io.github.dsheirer.dsp.filter.channelizer.output.OneChannelOutputProcessor; @@ -28,21 +28,21 @@ 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.IReusableComplexBufferProvider; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.TunerController; -import io.github.dsheirer.source.tuner.channel.ChannelSpecification; import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; +import io.github.dsheirer.util.Dispatcher; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Queue; @@ -76,32 +76,31 @@ public class PolyphaseChannelManager implements ISourceEventProcessor private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9; private Broadcaster mSourceEventBroadcaster = new Broadcaster<>(); - private IReusableComplexBufferProvider mReusableBufferProvider; + private INativeBufferProvider mNativeBufferProvider; private List mChannelSources = new CopyOnWriteArrayList<>(); private ChannelCalculator mChannelCalculator; private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer; private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener(); - private BufferSourceEventMonitor mBufferSourceEventMonitor = new BufferSourceEventMonitor(); - private ContinuousBufferProcessor mBufferProcessor; + private NativeBufferReceiver mNativeBufferReceiver = new NativeBufferReceiver(); + private Dispatcher mBufferDispatcher; private Map mOutputProcessorFilters = new HashMap<>(); /** * Creates a polyphase channel manager instance. * - * @param reusableComplexBufferProvider (ie tuner) that supports register/deregister for reusable baseband sample buffer + * @param nativeBufferProvider (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(IReusableComplexBufferProvider reusableComplexBufferProvider, - long frequency, double sampleRate) + public PolyphaseChannelManager(INativeBufferProvider nativeBufferProvider, long frequency, double sampleRate) { - if(reusableComplexBufferProvider == null) + if(nativeBufferProvider == null) { throw new IllegalArgumentException("Complex buffer provider argument cannot be null"); } - mReusableBufferProvider = reusableComplexBufferProvider; + mNativeBufferProvider = nativeBufferProvider; int channelCount = (int)(sampleRate / MINIMUM_CHANNEL_BANDWIDTH); @@ -112,9 +111,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor } mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING); - - mBufferProcessor = new ContinuousBufferProcessor(200, 50); - mBufferProcessor.setListener(mBufferSourceEventMonitor); + mBufferDispatcher = new Dispatcher(500, "sdrtrunk polyphase buffer processor", + new NativeBufferPoisonPill()); + mBufferDispatcher.setListener(mNativeBufferReceiver); } /** @@ -161,7 +160,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor * @param tunerChannel specifying center frequency and bandwidth. * @return source or null. */ - public TunerChannelSource getChannel(TunerChannel tunerChannel, ChannelSpecification channelSpecification) + public TunerChannelSource getChannel(TunerChannel tunerChannel) { PolyphaseChannelSource channelSource = null; @@ -176,7 +175,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor try { channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener, - mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification); + mChannelCalculator.getChannelSampleRate(), centerFrequency); mChannelSources.add(channelSource); } @@ -229,7 +228,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor */ private void startChannelSource(PolyphaseChannelSource channelSource) { - synchronized(mBufferProcessor) + synchronized(mBufferDispatcher) { //Note: the polyphase channel source has already been added to the mChannelSources in getChannel() method @@ -241,9 +240,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor //If this is the first channel, register to start the sample buffers flowing if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1) { - mReusableBufferProvider.addBufferListener(mBufferProcessor); + mNativeBufferProvider.addBufferListener(mBufferDispatcher); mPolyphaseChannelizer.start(); - mBufferProcessor.start(); + mBufferDispatcher.start(); } } } @@ -256,7 +255,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor */ private void stopChannelSource(PolyphaseChannelSource channelSource) { - synchronized(mBufferProcessor) + synchronized(mBufferDispatcher) { mChannelSources.remove(channelSource); mPolyphaseChannelizer.removeChannel(channelSource); @@ -265,8 +264,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor //If this is the last/only channel, deregister to stop the sample buffers if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0) { - mReusableBufferProvider.removeBufferListener(mBufferProcessor); - mBufferProcessor.stop(); + mNativeBufferProvider.removeBufferListener(mBufferDispatcher); + mBufferDispatcher.stop(); mPolyphaseChannelizer.stop(); } } @@ -298,7 +297,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue()); //Defer channelizer configuration changes to be handled on the buffer processor thread - mBufferSourceEventMonitor.receive(sourceEvent); + mNativeBufferReceiver.receive(sourceEvent); break; case NOTIFICATION_SAMPLE_RATE_CHANGE: //Update channel calculator immediately so that channels can be allocated @@ -565,7 +564,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor * that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer * and/or the channel sources and output processors. */ - public class BufferSourceEventMonitor implements Listener> + public class NativeBufferReceiver implements Listener { private Queue mQueuedSourceEvents = new ConcurrentLinkedQueue<>(); @@ -579,7 +578,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor } @Override - public void receive(List reusableComplexBuffers) + public void receive(INativeBuffer nativeBuffer) { try { @@ -599,26 +598,19 @@ public class PolyphaseChannelManager implements ISourceEventProcessor queuedSourceEvent = mQueuedSourceEvents.poll(); } - for(ReusableComplexBuffer reusableComplexBuffer: reusableComplexBuffers) + if(mPolyphaseChannelizer != null) { - if(mPolyphaseChannelizer != null) + Iterator iterator = nativeBuffer.iteratorInterleaved(); + + while(iterator.hasNext()) { - //User count management is handled by the channelizer - mPolyphaseChannelizer.receive(reusableComplexBuffer); - } - else - { - reusableComplexBuffer.decrementUserCount(); + mPolyphaseChannelizer.receive(iterator.next()); } } } catch(Throwable throwable) { mLog.error("Error", throwable); - for(ReusableComplexBuffer buffer: reusableComplexBuffers) - { - buffer.decrementUserCount(); - } } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java index 1645a77a..8c3b77d1 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -20,36 +20,23 @@ package io.github.dsheirer.dsp.filter.channelizer; import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor; 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.remez.RemezFIRFilterDesigner; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.SourceEvent; -import io.github.dsheirer.source.tuner.channel.ChannelSpecification; import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; -import java.util.HashMap; -import java.util.Map; +import java.util.List; -public class PolyphaseChannelSource extends TunerChannelSource +public class PolyphaseChannelSource extends TunerChannelSource implements Listener { -// private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class); - - private static final int PROCESSED_BUFFER_SAMPLE_SIZE = 2048; - private ReusableComplexBufferAssembler mReusableComplexBufferAssembler; private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor; private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor; private long mReplacementFrequency; - private ComplexFIRFilter2 mLowPassFilter; - private static Map sLowPassFilters = new HashMap(); - private double mChannelSampleRate; private long mIndexCenterFrequency; private long mChannelFrequencyCorrection; + private Listener mComplexSamplesListener; /** * Polyphase channelizer tuner channel source implementation. Adapts the channel array output samples from the @@ -65,37 +52,57 @@ public class PolyphaseChannelSource extends TunerChannelSource */ public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor, Listener producerSourceEventListener, double channelSampleRate, - long centerFrequency, ChannelSpecification channelSpecification) - throws FilterDesignException + long centerFrequency) throws FilterDesignException { super(producerSourceEventListener, tunerChannel); mPolyphaseChannelOutputProcessor = outputProcessor; + mPolyphaseChannelOutputProcessor.setListener(this); mChannelSampleRate = channelSampleRate; - mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(PROCESSED_BUFFER_SAMPLE_SIZE, mChannelSampleRate); - - float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(), - channelSpecification.getStopFrequency()); - mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f); setFrequency(centerFrequency); } + @Override + public void start() + { + super.start(); + + if(mPolyphaseChannelOutputProcessor != null) + { + mPolyphaseChannelOutputProcessor.start(); + } + } + + @Override + public void stop() + { + super.stop(); + + if(mPolyphaseChannelOutputProcessor != null) + { + mPolyphaseChannelOutputProcessor.stop(); + } + } + /** * Registers the listener to receive complex sample buffers from this channel source */ @Override - public void setListener(final Listener listener) + public void setListener(final Listener listener) { - mReusableComplexBufferAssembler.setListener(listener); - } + mComplexSamplesListener = listener; + mPolyphaseChannelOutputProcessor.setListener(this); + } - /** - * Removes the listener from receiving complex sample buffers from this channel source - */ @Override - public void removeListener(Listener listener) + public void receive(ComplexSamples complexSamples) { - mReusableComplexBufferAssembler.setListener(null); + getHeartbeatManager().broadcast(); + + if(mComplexSamplesListener != null) + { + mComplexSamplesListener.receive(complexSamples); + } } /** @@ -119,6 +126,7 @@ public class PolyphaseChannelSource extends TunerChannelSource if(mPolyphaseChannelOutputProcessor == null) { mPolyphaseChannelOutputProcessor = outputProcessor; + mPolyphaseChannelOutputProcessor.setListener(this); } //Otherwise, we have to swap out the processor on the sample processing thread else @@ -138,17 +146,14 @@ public class PolyphaseChannelSource extends TunerChannelSource if(mReplacementPolyphaseChannelOutputProcessor != null) { IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor; + existingProcessor.stop(); + existingProcessor.setListener(null); //Swap out the processor so that incoming samples can accumulate in the new channel output processor mPolyphaseChannelOutputProcessor = mReplacementPolyphaseChannelOutputProcessor; mReplacementPolyphaseChannelOutputProcessor = null; - - //Remove the buffer overflow listener and clear any previous buffer overflow state - existingProcessor.setSourceOverflowListener(null); - broadcastOverflowState(false); - - //Register to receive buffer overflow notifications so we can push them up through the source chain - mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this); + mPolyphaseChannelOutputProcessor.setListener(this); + mPolyphaseChannelOutputProcessor.start(); //Finally, setup the frequency offset for the output processor. mIndexCenterFrequency = mReplacementFrequency; @@ -161,17 +166,16 @@ public class PolyphaseChannelSource extends TunerChannelSource * queued for processing to extract the target channel samples, process them for frequency correction and/or * channel aggregation, and dispatch the results to the downstream sample listener/consumer. * - * @param channelResultsBuffer containing an array of polyphase channelizer outputs. + * @param channelResultsList containing a list of polyphase channelizer output arrays. */ - public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer) + public void receiveChannelResults(List channelResultsList) { if(mReplacementPolyphaseChannelOutputProcessor != null) { swapOutputProcessor(); } - mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp()); - mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer); + mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList); } /** @@ -198,13 +202,9 @@ public class PolyphaseChannelSource extends TunerChannelSource @Override public void dispose() { - if(mReusableComplexBufferAssembler != null) - { - mReusableComplexBufferAssembler.dispose(); - } - if(mPolyphaseChannelOutputProcessor != null) { + mPolyphaseChannelOutputProcessor.setListener(null); mPolyphaseChannelOutputProcessor.dispose(); } } @@ -223,6 +223,7 @@ public class PolyphaseChannelSource extends TunerChannelSource */ protected void setChannelFrequencyCorrection(long value) { +//TODO: push this frequency correction down to the output processor ... mChannelFrequencyCorrection = value; updateFrequencyOffset(); broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection)); @@ -242,12 +243,6 @@ public class PolyphaseChannelSource extends TunerChannelSource setChannelFrequencyCorrection(0); } - @Override - protected void processSamples() - { - mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler); - } - /** * Calculates the frequency offset required to mix the incoming signal to center the desired frequency * within the channel @@ -273,43 +268,4 @@ public class PolyphaseChannelSource extends TunerChannelSource return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " + getTunerChannel().getFrequency(); } - - /** - * Creates a low-pass filter to use as the final cleanup filter for the decimated output stream - * - * @param sampleRate for the final output channel rate - * @param passFrequency for half of the desired channel rate - * @param stopFrequency for the attenuated band - * @return a newly designed filter or a previously designed (cached) filter - * @throws FilterDesignException - */ - private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException - { - //Use existing filter if we've already designed one - float[] filter = sLowPassFilters.get((int) sampleRate); - if(filter != null) - { - return filter; - } - - FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - .sampleRate(sampleRate) - .gridDensity(16) - .passBandCutoff(passFrequency) - .passBandAmplitude(1.0) - .passBandRipple(0.01) - .stopBandStart(stopFrequency) - .stopBandAmplitude(0.0) - .stopBandRipple(0.01) - .build(); - - RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); - - //This will throw an exception if the filter cannot be designed - float[] taps = designer.getImpulseResponse(); - - sLowPassFilters.put((int)sampleRate, taps); - - return taps; - } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java index 2ba55d27..995bf6ce 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java @@ -1,22 +1,24 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.filter.channelizer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; +import io.github.dsheirer.sample.complex.ComplexSamples; import org.apache.commons.math3.util.FastMath; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; @@ -45,8 +47,6 @@ import org.slf4j.LoggerFactory; public class TwoChannelSynthesizerM2 { private final static Logger mLog = LoggerFactory.getLogger(TwoChannelSynthesizerM2.class); - - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Two Channel Synthesizer M2"); private float[] mSerpentineDataBuffer; private float[] mIQInterleavedFilter; private float[] mFilterVectorProduct; @@ -87,28 +87,30 @@ public class TwoChannelSynthesizerM2 * @param channelBuffer2 input channel * @return synthesized channel results of the same length as both channel 1/2 input arrays. */ - public ReusableComplexBuffer process(ReusableComplexBuffer channelBuffer1, ReusableComplexBuffer channelBuffer2) + public ComplexSamples process(ComplexSamples channelBuffer1, ComplexSamples channelBuffer2) { - if(channelBuffer1.getSamples().length != channelBuffer1.getSamples().length) + if(channelBuffer1.i().length != channelBuffer1.i().length) { throw new IllegalArgumentException("Channel 1 and 2 array length must be equal"); } - float[] channel1 = channelBuffer1.getSamples(); - float[] channel2 = channelBuffer2.getSamples(); + float[] iCh1 = channelBuffer1.i(); + float[] qCh1 = channelBuffer1.q(); + float[] iCh2 = channelBuffer2.i(); + float[] qCh2 = channelBuffer2.q(); - ReusableComplexBuffer synthesizedComplexBuffer = mReusableComplexBufferQueue.getBuffer(channel1.length); + float[] i = new float[iCh1.length]; + float[] q = new float[qCh1.length]; - float[] output = synthesizedComplexBuffer.getSamples(); float[] IFFTBuffer = new float[4]; - for(int x = 0; x < channel1.length; x += 2) + for(int x = 0; x < iCh1.length; x++) { //Load samples from each channel into buffer for IFFT - IFFTBuffer[0] = channel1[x]; //i - IFFTBuffer[1] = channel1[x + 1]; //q - IFFTBuffer[2] = channel2[x]; //i - IFFTBuffer[3] = channel2[x + 1]; //q + IFFTBuffer[0] = iCh1[x]; //i + IFFTBuffer[1] = qCh1[x]; //q + IFFTBuffer[2] = iCh2[x]; //i + IFFTBuffer[3] = qCh2[x]; //q //Perform Inverse FFT (IFFT) mFFT.complexInverse(IFFTBuffer, true); @@ -148,16 +150,13 @@ public class TwoChannelSynthesizerM2 mQAccumulator += mFilterVectorProduct[y + 1]; } - output[x] = mIAccumulator; - output[x + 1] = mQAccumulator; + i[x] = mIAccumulator; + q[x] = mQAccumulator; mTopBlockFlag = !mTopBlockFlag; } - channelBuffer1.decrementUserCount(); - channelBuffer2.decrementUserCount(); - - return synthesizedComplexBuffer; + return new ComplexSamples(i, q); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java index 95862798..a7242e93 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,35 +14,26 @@ * * 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.channelizer.output; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.Oscillator; -import io.github.dsheirer.sample.IOverflowListener; -import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; -import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; -import io.github.dsheirer.source.Source; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.util.Dispatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputProcessor { private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class); - private OverflowableReusableBufferTransferQueue mChannelResultsQueue; - private List mChannelResultsToProcess = new ArrayList<>(); - private int mMaxResultsToProcess; - + private Dispatcher> mChannelResultsDispatcher; + protected Listener mComplexSamplesListener; private int mInputChannelCount; - private IOscillator mFrequencyCorrectionMixer; - private boolean mFrequencyCorrectionEnabled; - private double mGain = 1.0; /** * Base class for polyphase channelizer output channel processing. Provides built-in frequency translation @@ -52,21 +43,32 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP * @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output * channel sample rate for frequency translation/correction. */ - public ChannelOutputProcessor(int inputChannelCount, double sampleRate, double gain) + public ChannelOutputProcessor(int inputChannelCount, double sampleRate) { mInputChannelCount = inputChannelCount; - mGain = gain; - -//TODO: swap this out and use the LowPhaseNoiseOscillator - mFrequencyCorrectionMixer = new Oscillator(0, sampleRate); - mMaxResultsToProcess = (int)(sampleRate / 10) * 2; //process at 100 millis interval, twice the expected inflow rate - - mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5)); + mChannelResultsDispatcher = new Dispatcher<>((int)sampleRate, "sdrtrunk polyphase channel", Collections.emptyList()); + mChannelResultsDispatcher.setListener(floats -> process(floats)); } - protected double getGain() + @Override + public void start() { - return mGain; + mChannelResultsDispatcher.start(); + } + + @Override + public void stop() + { + mChannelResultsDispatcher.stop(); + } + + /** + * Registers the listener to receive the assembled complex sample buffers from this processor. + */ + @Override + public void setListener(Listener listener) + { + mComplexSamplesListener = listener; } @Override @@ -77,97 +79,23 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP public void dispose() { - if(mChannelResultsQueue != null) - { - mChannelResultsQueue.dispose(); - } - } - - /** - * Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband. - * @param frequencyOffset in hertz - */ - @Override - public void setFrequencyOffset(long frequencyOffset) - { - mFrequencyCorrectionMixer.setFrequency(frequencyOffset); - mFrequencyCorrectionEnabled = (frequencyOffset != 0); - } - - /** - * Oscillator/mixer to use for frequency correction against incoming sample stream - */ - protected IOscillator getFrequencyCorrectionMixer() - { - return mFrequencyCorrectionMixer; } @Override - public void receiveChannelResults(ReusableChannelResultsBuffer channelResults) + public void receiveChannelResults(List channelResultsList) { - mChannelResultsQueue.offer(channelResults); - } - - /** - * Processes all enqueued polyphase channelizer results until the internal queue is empty - * @param reusableComplexBufferAssembler to receive the processed channel results - */ - @Override - public void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler) - { - try - { - int toProcess = mChannelResultsQueue.drainTo(mChannelResultsToProcess, mMaxResultsToProcess); - - if(toProcess > 0) - { - process(mChannelResultsToProcess, reusableComplexBufferAssembler); - } - } - catch(Throwable throwable) - { - mLog.error("Error while processing polyphase channel samples", throwable); - } - - mChannelResultsToProcess.clear(); + mChannelResultsDispatcher.receive(channelResultsList); } /** * Sub-class implementation to process one polyphase channelizer result array. * @param channelResults to process - * @param reusableComplexBufferAssembler to receive the processed channelizer results */ - public abstract void process(List channelResults, - ReusableComplexBufferAssembler reusableComplexBufferAssembler); - - - /** - * Sets the overflow listener to monitor the internal channelizer channel results queue overflow state - */ - public void setSourceOverflowListener(Source source) - { - mChannelResultsQueue.setSourceOverflowListener(source); - } - - /** - * Removes the overflow listener from monitoring the internal channelizer channel results queue overflow state - */ - public void removeOverflowListener(IOverflowListener listener) - { - mChannelResultsQueue.setOverflowListener(null); - } + public abstract void process(List channelResults); @Override public int getInputChannelCount() { return mInputChannelCount; } - - /** - * Indicates if this channel has a frequency correction offset - */ - public boolean hasFrequencyCorrection() - { - return mFrequencyCorrectionEnabled; - } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java index 3b88b07e..286f6c17 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,30 +14,41 @@ * * 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.channelizer.output; -import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; -import io.github.dsheirer.source.Source; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.complex.ComplexSamples; import java.util.List; public interface IPolyphaseChannelOutputProcessor { /** - * Receive and enqueue output results from the polyphase analysis channelizer - * @param channelResultsBuffer to enqueue + * Start processing channel results */ - void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer); + void start(); /** - * Process the channel output channel results queue and deliver the output to the listener - * - * @param reusableComplexBufferAssembler to receive the processed channel results + * Stop processing channel results. */ - void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler); + void stop(); + /** + * Receive and enqueue output results from the polyphase analysis channelizer + * @param channelResults to enqueue + */ + void receiveChannelResults(List channelResults); + + /** + * Listener to receive assembled complex samples buffers + */ + void setListener(Listener listener); + +// /** +// * Process the channel output channel results queue and deliver the output to the listener +// */ +// void processChannelResults(); /** * Sets the desired frequency offset from center. The samples will be mixed with an oscillator set to this offset @@ -49,13 +60,12 @@ public interface IPolyphaseChannelOutputProcessor /** * Indicates the number of input channels processed by this output processor - * @return */ int getInputChannelCount(); /** * Updates the input polyphase channel index(es) used by this output processor - * @param indexes + * @param indexes for the channel */ void setPolyphaseChannelIndices(List indexes); @@ -66,16 +76,9 @@ public interface IPolyphaseChannelOutputProcessor /** * Updates the synthesis filter taps for this output processor - * @param filter + * @param filter for the output processor */ void setSynthesisFilter(float[] filter); - /** - * Sets the listener to receive buffer overflow notifications - * @param source to receive overflow notifications - */ - void setSourceOverflowListener(Source source); - - void dispose(); } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/MixerAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/MixerAssembler.java new file mode 100644 index 00000000..f610ab84 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/MixerAssembler.java @@ -0,0 +1,71 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.channelizer.output; + +import io.github.dsheirer.dsp.gain.complex.ComplexGain; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.mixer.ComplexMixer; +import io.github.dsheirer.dsp.mixer.ComplexMixerFactory; +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Sample assembler for a channel output processor that incorporates a mixer to perform final frequency + * translation on an extracted channel. + */ +public abstract class MixerAssembler +{ + protected static final int BUFFER_SIZE = 2048; + + private ComplexMixer mMixer = ComplexMixerFactory.getMixer(0, 0); + private ComplexGain mGain; + + /** + * Constructs an instance. + * @param gain to apply to fully assembled buffers. + */ + protected MixerAssembler(float gain) + { + mGain = ComplexGainFactory.getComplexGain(gain); + } + + /** + * Complex samples mixer for this assembler + */ + protected ComplexMixer getMixer() + { + return mMixer; + } + + protected ComplexGain getGain() + { + return mGain; + } + + /** + * Indicates if this assembler has a buffer ready to dispatch to consumers. + */ + public abstract boolean hasBuffer(); + + /** + * Access the fully assembled buffer when hasBuffer() indicates that a buffer is ready. + * @return true if a full buffer is assembled and ready. + */ + public abstract ComplexSamples getBuffer(); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelMixerAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelMixerAssembler.java new file mode 100644 index 00000000..df9e1e36 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelMixerAssembler.java @@ -0,0 +1,77 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.channelizer.output; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Sample assembler to extract a single channel from the channel results array of a polyphase channel results array. + */ +public class OneChannelMixerAssembler extends MixerAssembler +{ + private SampleAssembler mSampleAssembler = new SampleAssembler(BUFFER_SIZE); + + /** + * Constructs an instance + * @param gain to apply to fully assembled buffers. + */ + public OneChannelMixerAssembler(float gain) + { + super(gain); + } + + /** + * Receive a complex I & Q sample pair. + * @param i inphase + * @param q quadrature + */ + public void receive(float i, float q) + { + mSampleAssembler.receive(i, q); + } + + /** + * Indicates if there is a fully assembled buffer ready. + */ + @Override + public boolean hasBuffer() + { + return mSampleAssembler.hasBuffer(); + } + + /** + * Access the assembled buffer. Frequency offset and gain are applied to the returned buffer. + * @return assembled buffer. + */ + @Override + public ComplexSamples getBuffer() + { + ComplexSamples buffer = mSampleAssembler.getBufferAndReset(); + + if(getMixer().hasFrequency()) + { + buffer = getMixer().mix(buffer); + } + + buffer = getGain().apply(buffer); + + return buffer; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java index 4e7b57e0..4fe01f01 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,13 +14,11 @@ * * 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.channelizer.output; -import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; +import io.github.dsheirer.sample.complex.ComplexSamples; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +27,7 @@ import java.util.List; public class OneChannelOutputProcessor extends ChannelOutputProcessor { private final static Logger mLog = LoggerFactory.getLogger(OneChannelOutputProcessor.class); - + private final OneChannelMixerAssembler mMixerAssembler; private int mChannelOffset; /** @@ -38,12 +36,14 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor * * @param sampleRate of the output sample stream. * @param channelIndexes containing a single channel index. - * @param gain value to apply. Typically this is the same as the channelizer's channel count. + * @param gain value to apply. This is typically the same as the channelizer's channel count. */ - public OneChannelOutputProcessor(double sampleRate, List channelIndexes, double gain) + public OneChannelOutputProcessor(double sampleRate, List channelIndexes, float gain) { - super(1, sampleRate, gain); + super(1, sampleRate); setPolyphaseChannelIndices(channelIndexes); + mMixerAssembler = new OneChannelMixerAssembler(gain); + mMixerAssembler.getMixer().setSampleRate(sampleRate); } @Override @@ -62,7 +62,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor if(indexes.size() != 1) { throw new IllegalArgumentException("Single channel output processor requires a single index to " + - "process - provided indexes " + indexes.toString()); + "process - provided indexes " + indexes); } //Set the channelized output results offset to twice the channel index to account for each channel having @@ -70,38 +70,45 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor mChannelOffset = indexes.get(0) * 2; } + @Override + public void setFrequencyOffset(long frequency) + { + mMixerAssembler.getMixer().setFrequency(frequency); + } + /** - * Extract the channel from the channel results array, apply frequency translation, and deliver the - * extracted frequency-corrected channel I/Q sample set to the complex sample listener. + * Extract the channel from the channel results array and pass to the assembler. The assembler will + * apply frequency translation and gain and indicate when a buffer is fully assembled. * - * @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn) - * @param reusableComplexBufferAssembler to receive the extracted, frequency-translated channel results + * @param lists to process containing a list of a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn) */ @Override - public void process(List channelResults, - ReusableComplexBufferAssembler reusableComplexBufferAssembler) + public void process(List channelResultsList) { - for(ReusableChannelResultsBuffer channelResultsBuffer: channelResults) + for(float[] channelResults: channelResultsList) { - try - { - ReusableComplexBuffer channelBuffer = channelResultsBuffer.getChannel(mChannelOffset); + mMixerAssembler.receive(channelResults[mChannelOffset], channelResults[mChannelOffset + 1]); - if(hasFrequencyCorrection()) + if(mMixerAssembler.hasBuffer()) + { + ComplexSamples buffer = mMixerAssembler.getBuffer(); + + if(mComplexSamplesListener != null) { - getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples()); + try + { + mComplexSamplesListener.receive(buffer); + } + catch(NullPointerException npe) + { + //Ignore ... can happen when the listener is nullified on another thread + } + catch(Exception e) + { + mLog.error("Error extracting channel samples from one polyphase channel results buffer", e); + } } - - channelBuffer.applyGain(getGain()); - - reusableComplexBufferAssembler.receive(channelBuffer); } - catch(IllegalArgumentException iae) - { - mLog.error("Error extracting channel samples from polyphase channel results buffer"); - } - - channelResultsBuffer.decrementUserCount(); } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/SampleAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/SampleAssembler.java new file mode 100644 index 00000000..af696548 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/SampleAssembler.java @@ -0,0 +1,85 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.channelizer.output; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +public class SampleAssembler +{ + private final int mBufferSize; + private float[] mI; + private float[] mQ; + private int mPointer = 0; + + /** + * Creates a sample assembler to produce buffers of the specified size. + * @param bufferSize for a full buffer + */ + public SampleAssembler(int bufferSize) + { + mBufferSize = bufferSize; + reset(); + } + + /** + * Resets the buffer pointer and creates new (empty) I and Q arrays. + */ + private void reset() + { + mPointer = 0; + mI = new float[mBufferSize]; + mQ = new float[mBufferSize]; + } + + /** + * Adds the I & Q samples to this assembler. + * @param i inphase sample + * @param q quadrature sample. + */ + public void receive(float i, float q) + { + if(mPointer >= mBufferSize) + { + throw new IllegalStateException("Buffer is full. Use flushBuffer() to reset the assembler."); + } + + mI[mPointer] = i; + mQ[mPointer++] = q; + } + + /** + * Indicates if this assembler has a full buffer. + * Note: use getBufferAndReset() to access the full buffer. + */ + public boolean hasBuffer() + { + return mPointer >= mBufferSize; + } + + /** + * Extracts the full buffer from this assembler and resets the assembler to accept more samples. + */ + public ComplexSamples getBufferAndReset() + { + ComplexSamples buffer = new ComplexSamples(mI, mQ); + reset(); + return buffer; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java new file mode 100644 index 00000000..a0f944ed --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java @@ -0,0 +1,104 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.channelizer.output; + +import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2; +import io.github.dsheirer.dsp.oscillator.FS4DownConverter; +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Sample assembler to extract a single channel from the channel results array of a polyphase channel results array. + */ +public class TwoChannelMixerAssembler extends MixerAssembler +{ + private SampleAssembler mChannel1SampleAssembler = new SampleAssembler(BUFFER_SIZE); + private SampleAssembler mChannel2SampleAssembler = new SampleAssembler(BUFFER_SIZE); + private FS4DownConverter mFS4DownConverter = new FS4DownConverter(); + private TwoChannelSynthesizerM2 mTwoChannelSynthesizer; + + /** + * Constructs an instance + * @param gain to apply to fully assembled buffers. + */ + public TwoChannelMixerAssembler(float gain) + { + super(gain); + } + + /** + * Sets the filter coefficients to use for two-channel synthesis + * @param filter + */ + public void setSynthesisFilter(float[] filter) + { + mTwoChannelSynthesizer = new TwoChannelSynthesizerM2(filter); + } + + /** + * Receive a complex I & Q sample pair. + * @param iChannel1 inphase + * @param qChannel1 quadrature + * @param iChannel2 inphase + * @param qChannel2 quadrature + */ + public void receive(float iChannel1, float qChannel1, float iChannel2, float qChannel2) + { + mChannel1SampleAssembler.receive(iChannel1, qChannel1); + mChannel2SampleAssembler.receive(iChannel2, qChannel2); + } + + /** + * Indicates if there is a fully assembled buffer ready. + */ + @Override + public boolean hasBuffer() + { + //Both channel 1 and channel 2 assemblers work in parallel, so we only need to check one of them. + return mChannel1SampleAssembler.hasBuffer(); + } + + /** + * Access the assembled buffer. Frequency offset and gain are applied to the returned buffer. + * @return assembled buffer. + */ + @Override + public ComplexSamples getBuffer() + { + ComplexSamples channel1 = mChannel1SampleAssembler.getBufferAndReset(); + ComplexSamples channel2 = mChannel2SampleAssembler.getBufferAndReset(); + + //Synthesize two channels into one, reducing sample rate by 2 so it's same as single channel rate + ComplexSamples buffer = mTwoChannelSynthesizer.process(channel1, channel2); + + //Mix to baseband + buffer = mFS4DownConverter.mixComplex(buffer); + + //Apply frequency correction if necessary + if(getMixer().hasFrequency()) + { + buffer = getMixer().mix(buffer); + } + + //Apply gain + buffer = getGain().apply(buffer); + + return buffer; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java index 780d136e..e6b7d0dc 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java @@ -1,34 +1,33 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.filter.channelizer.output; -import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2; -import io.github.dsheirer.dsp.mixer.FS4DownConverter; -import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; +import io.github.dsheirer.sample.complex.ComplexSamples; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; public class TwoChannelOutputProcessor extends ChannelOutputProcessor { -// private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class); - private TwoChannelSynthesizerM2 mSynthesizer; - private FS4DownConverter mFS4DownConverter = new FS4DownConverter(); - + private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class); + private TwoChannelMixerAssembler mMixerAssembler; private int mChannelOffset1; private int mChannelOffset2; @@ -40,12 +39,14 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor * @param channelIndexes containing two channel indices. * @param gain to apply to output. Typically this is equal to the channelizer's channel count. */ - public TwoChannelOutputProcessor(double sampleRate, List channelIndexes, float[] filter, double gain) + public TwoChannelOutputProcessor(double sampleRate, List channelIndexes, float[] filter, float gain) { //Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency //after synthesizing both input channels - super(2, sampleRate, gain); + super(2, sampleRate); setPolyphaseChannelIndices(channelIndexes); + mMixerAssembler = new TwoChannelMixerAssembler(gain); + mMixerAssembler.getMixer().setSampleRate(sampleRate); setSynthesisFilter(filter); } @@ -57,14 +58,13 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor @Override public void setFrequencyOffset(long frequencyOffset) { - super.setFrequencyOffset(frequencyOffset); + mMixerAssembler.getMixer().setFrequency(frequencyOffset); } - @Override public void setSynthesisFilter(float[] filter) { - mSynthesizer = new TwoChannelSynthesizerM2(filter); + mMixerAssembler.setSynthesisFilter(filter); } /** @@ -78,7 +78,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor if(indexes.size() != 2) { throw new IllegalArgumentException("Double channel output processor requires two indexes to " + - "process - provided indexes " + indexes.toString()); + "process - provided indexes " + indexes); } //Set the channelized output results offsets to twice the channel index to account for each channel having @@ -91,32 +91,36 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor * Extract the channel from the channel results array, apply frequency translation, and deliver the * extracted frequency-corrected channel I/Q sample set to the complex sample listener. * - * @param channelResultsBuffers to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn) - * @param reusableComplexBufferAssembler to receive the extracted, frequency-translated channel results + * @param lists to process containing a list of a list of an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn) */ @Override - public void process(List channelResultsBuffers, - ReusableComplexBufferAssembler reusableComplexBufferAssembler) + public void process(List channelResultsList) { - for(ReusableChannelResultsBuffer buffer : channelResultsBuffers) + for(float[] channelResults : channelResultsList) { - ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1); - ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2); + mMixerAssembler.receive(channelResults[mChannelOffset1], channelResults[mChannelOffset1 + 1], + channelResults[mChannelOffset2], channelResults[mChannelOffset2 + 1]); - //Join the two channels using the synthesizer - ReusableComplexBuffer synthesized = mSynthesizer.process(channel1, channel2); + if(mMixerAssembler.hasBuffer()) + { + ComplexSamples buffer = mMixerAssembler.getBuffer(); - //The synthesized channels are centered at +FS/4 ... downconvert to center the spectrum - mFS4DownConverter.mixComplex(synthesized.getSamples()); - - //Apply offset and frequency correction to center the signal of interest within the synthesized channel - getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples()); - - synthesized.applyGain(getGain()); - - reusableComplexBufferAssembler.receive(synthesized); - - buffer.decrementUserCount(); + if(mComplexSamplesListener != null) + { + try + { + mComplexSamplesListener.receive(buffer); + } + catch(NullPointerException npe) + { + //Ignore ... can happen when the listener is nullified on another thread + } + catch(Exception e) + { + mLog.error("Error extracting channel samples from two polyphase channel results buffer", e); + } + } + } } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java b/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java deleted file mode 100644 index 7093b939..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java +++ /dev/null @@ -1,456 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * 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.cic; - -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.remez.RemezFIRFilterDesigner; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler; -import io.github.dsheirer.sample.complex.ComplexSampleListener; -import io.github.dsheirer.sample.decimator.ComplexDecimator; -import org.apache.commons.lang3.Validate; -import org.apache.commons.math3.primes.Primes; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Non-Recursive Prime-Factor CIC Filter. - * - * Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This - * filter is comprised of multiple decimating stages each with a prime factor decimation rate. Multiple stages are - * cascaded to achieve the overall decimation rate. - * - * This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by - * adding additional prime factors to the PRIMES array. - */ -public class ComplexPrimeCICDecimate implements Listener -{ -// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate.class); - - private static Map sLowPassFilters = new HashMap(); - private static Map> sPrimeFactors = new HashMap(); - - private List mDecimatingStages = new ArrayList(); - private DecimatingStage mFirstDecimatingStage; - private Output mOutput; - - /** - * Constructs a new decimation filter. - * - * An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel - * pass band might be: - * sampleRate = 2,400,000.0 - * decimation = 96 - * passFrequency = 6250 (1/4 of the channel rate) - * stopFrequency = 7000 - * - * Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter, - * where smaller transition bandwidth creates a longer filter that requires more processing effort. - * - * @param sampleRate of the input sample stream - * @param decimation - overall decimation rate - * @param passFrequency for the final cleanup filter - * @param stopFrequency for the final cleanup filter - * @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate - * and specified pass/stop frequencies. - */ - public ComplexPrimeCICDecimate(double sampleRate, int decimation, double passFrequency, double stopFrequency) - throws FilterDesignException - { - List stageSizes = getPrimeFactors(decimation); - - for(int x = 0; x < stageSizes.size(); x++) - { - DecimatingStage stage = new DecimatingStage(stageSizes.get(x), 1); - - mDecimatingStages.add(stage); - - if(x == 0) - { - /* Reference to first stage -- will receive all samples */ - mFirstDecimatingStage = stage; - } - else - { - /* Wire the current stage to the previous stage */ - mDecimatingStages.get(x - 1).setListener(stage); - } - } - - double channelRate = sampleRate / (double)decimation; - - mOutput = new Output(channelRate, passFrequency, stopFrequency); - - mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput); - } - - public void dispose() - { - for(DecimatingStage stage : mDecimatingStages) - { - stage.dispose(); - } - - mDecimatingStages.clear(); - mDecimatingStages = null; - mFirstDecimatingStage = null; - - mOutput.dispose(); - mOutput = null; - } - - /** - * Adds a listener to receive the output of this CIC decimation filter - */ - public void setListener(Listener listener) - { - mOutput.setListener(listener); - } - - /** - * Removes listener from output of this CIC decimation filter - */ - public void removeListener() - { - mOutput.removeListener(); - } - - /** - * Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the - * highest rate listed in the PRIMES array, then add additional prime factors to the PRIMES array. - * - * @param decimation - integral decimation rate - * @return - ordered list (smallest to largest) of prime factors - */ - public static List getPrimeFactors(int decimation) - { - List primeFactors = sPrimeFactors.get(decimation); - if(primeFactors != null) - { - return primeFactors; - } - - primeFactors = Primes.primeFactors(decimation); - - sPrimeFactors.put(decimation, primeFactors); - - return primeFactors; - } - - /** - * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc. - */ - @Override - public void receive(ReusableComplexBuffer buffer) - { - if(mFirstDecimatingStage != null) - { - float[] samples = buffer.getSamples(); - - for(int x = 0; x < samples.length; x += 2) - { - mFirstDecimatingStage.receive(samples[x], samples[x + 1]); - } - } - - buffer.decrementUserCount(); - } - - /** - * Decimating stage combines multiple CIC stages with a decimator. The - * number of stages is indicated by the order value and the size indicates - * the decimation rate of this stage. - */ - public class DecimatingStage implements ComplexSampleListener - { - private List mStages = new ArrayList(); - private Stage mFirstStage; - private ComplexDecimator mDecimator; - - public DecimatingStage(int size, int order) - { - for(int x = 0; x < order; x++) - { - Stage stage; - - if(size == 2) - { - stage = new TwoStage(); - } - else - { - stage = new Stage(size); - } - - mStages.add(stage); - - if(x == 0) - { - mFirstStage = stage; - } - else - { - mStages.get(x - 1).setListener(stage); - } - } - - mDecimator = new ComplexDecimator(size); - mStages.get(mStages.size() - 1).setListener(mDecimator); - } - - public void dispose() - { - for(Stage stage : mStages) - { - stage.dispose(); - } - - mStages.clear(); - mDecimator.dispose(); - mDecimator = null; - mFirstStage = null; - mStages = null; - } - - @Override - public void receive(float i, float q) - { - mFirstStage.receive(i, q); - } - - public void setListener(ComplexSampleListener listener) - { - mDecimator.setListener(listener); - } - } - - /** - * Single non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement - * the stage so that stage size has essentially no impact on the computational requirements of the stage - */ - public class Stage implements ComplexSampleListener - { - protected ComplexSampleListener mListener; - - private float[] mISamples; - private float[] mQSamples; - - protected float mISum; - protected float mQSum; - - private int mSamplePointer = 0; - private int mSize; - - protected float mGain; - - protected Stage() - { - } - - public Stage(int size) - { - mSize = size - 1; - - mISamples = new float[mSize]; - mQSamples = new float[mSize]; - - float baseGain = 1.0f / (float)size; - mGain = baseGain * (1.0f / (1.0f - baseGain)); - } - - public void dispose() - { - mListener = null; - } - - public void receive(float i, float q) - { - /* Subtract the oldest sample and add in the newest sample */ - mISum = mISum - mISamples[mSamplePointer] + i; - mQSum = mQSum - mQSamples[mSamplePointer] + q; - - /* Overwrite the oldest sample with the newest */ - mISamples[mSamplePointer] = i; - mQSamples[mSamplePointer] = q; - - mSamplePointer++; - - if(mSamplePointer >= mSize) - { - mSamplePointer = 0; - } - - if(mListener != null) - { - mListener.receive((mISum * mGain), (mQSum * mGain)); - } - } - - public void setListener(ComplexSampleListener listener) - { - mListener = listener; - } - } - - /** - * Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage. - */ - public class TwoStage extends Stage - { - public TwoStage() - { - mGain = 0.5f; - } - - public void receive(float i, float q) - { - if(mListener != null) - { - mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain)); - } - - mISum = i; - mQSum = q; - } - } - - - /** - * Output adapter - applies gain correction and cleanup filter and broadcast - * to registered listener. - */ - public class Output implements ComplexSampleListener - { - /* Decimated output buffers will contain 1024 complex samples */ - private ReusableComplexBufferAssembler mBufferAssembler; - private ComplexFIRFilter2 mLowPassFilter; - private Listener mReusableComplexBufferListener; - - public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException - { - mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate); - - //This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies - float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency); - mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f); - - mBufferAssembler.setListener(new Listener() - { - @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) - { - if(mReusableComplexBufferListener != null) - { - ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer); - mReusableComplexBufferListener.receive(filteredBuffer); -// mReusableComplexBufferListener.receive(reusableComplexBuffer); - } - else - { - reusableComplexBuffer.decrementUserCount(); - } - } - }); - } - - public void dispose() - { - mBufferAssembler.dispose(); - mLowPassFilter.dispose(); - } - - /** - * Interface for receiving CIC decimated output samples - */ - @Override - public void receive(float inphase, float quadrature) - { - mBufferAssembler.receive(inphase, quadrature); - } - - public void setListener(Listener listener) - { - mReusableComplexBufferListener = listener; - - if(mReusableComplexBufferListener == null) - { - removeListener(); - } - } - - public void removeListener() - { - mReusableComplexBufferListener = new Listener() - { - @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) - { - //empty receiver - reusableComplexBuffer.decrementUserCount(); - } - }; - } - - /** - * Creates a low-pass filter to use as the final cleanup filter for the decimated output stream - * - * @param sampleRate for the final output channel rate - * @param passFrequency for half of the desired channel rate - * @param stopFrequency for the attenuated band - * @return a newly designed filter or a previously designed (cached) filter - * @throws FilterDesignException - */ - private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException - { - //Use existing filter if we've already designed one - float[] existingFilter = sLowPassFilters.get((int) sampleRate); - if(existingFilter != null) - { - return existingFilter; - } - - FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - .sampleRate(sampleRate) - .gridDensity(16) - .passBandCutoff(passFrequency) - .passBandAmplitude(1.0) - .passBandRipple(0.01) - .stopBandStart(stopFrequency) - .stopBandAmplitude(0.0) - .stopBandRipple(0.01) - .build(); - - RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); - - //This will throw an exception if the filter cannot be designed - float[] taps = designer.getImpulseResponse(); - - sLowPassFilters.put((int)sampleRate, taps); - - return taps; - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/cic/PrimeCicDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/cic/PrimeCicDecimationFilter.java new file mode 100644 index 00000000..e7e0910c --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/cic/PrimeCicDecimationFilter.java @@ -0,0 +1,471 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.cic; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; +import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; +import org.apache.commons.math3.primes.Primes; + +import java.util.Arrays; +import java.util.List; + +/** + * Non-Recursive Prime-Factor CIC Filter. + * + * Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This + * filter contains multiple decimating stages each with a prime factor decimation rate. Multiple stages are + * cascaded to achieve the overall decimation rate. + * + * This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by + * adding additional prime factors to the PRIMES array. + */ +public class PrimeCicDecimationFilter +{ + private Stage mFirstStage; + + /** + * Constructs a new decimation filter. + * + * An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel + * pass band might be: + * sampleRate = 2,400,000.0 + * decimation = 96 + * passFrequency = 6250 (1/4 of the channel rate) + * stopFrequency = 7000 + * + * Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter, + * where smaller transition bandwidth creates a longer filter that requires more processing effort. + * + * @param sampleRate of the input sample stream + * @param decimation - overall decimation rate + * @param passFrequency for the final cleanup filter + * @param stopFrequency for the final cleanup filter + * @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate + * and specified pass/stop frequencies. + */ + public PrimeCicDecimationFilter(double sampleRate, int decimation, double passFrequency, double stopFrequency) + throws FilterDesignException + { + List stageSizes = Primes.primeFactors(decimation); + + Stage stage = null; + + for(int x = 0; x < stageSizes.size(); x++) + { + Stage nextStage = new DecimatingStage(stageSizes.get(x), 1); + + if(stage != null) + { + stage.setChild(nextStage); + } + + stage = nextStage; + + if(x == 0) + { + /* Reference to first stage -- will receive all samples */ + mFirstStage = stage; + } + } + + //stage should reference the last stage that was created at this point. Create an + //output and chain it to the final stage. + if(stage != null) + { + double channelRate = sampleRate / (double)decimation; + Stage output = new Output(channelRate, passFrequency, stopFrequency); + stage.setChild(output); + } + } + + /** + * Decimates the sample array. + * @param samples to decimate + * @return decimated samples + */ + public float[] decimate(float[] samples) + { + return mFirstStage.process(samples); + } + + /** + * Base stage for all decimating and non-decimating stages + */ + public abstract class Stage + { + private Stage mChild; + + /** + * Sets the child stage to follow this stage + * @param stage child + */ + public void setChild(Stage stage) + { + mChild = stage; + } + + /** + * Indicates if this stage has an optional child stage that should process the samples + * after this stage processes those samples. + */ + protected boolean hasChild() + { + return mChild != null; + } + + /** + * Optional child stage that should process samples after this stage. + */ + protected Stage getChild() + { + return mChild; + } + + /** + * Process the samples. Note: original argument array will be reused if/when possible. + * @param samples to process + * @return processed samples. + */ + public abstract float[] process(float[] samples); + } + + /** + * Decimating stage chains multiple non-decimating stages with a decimator. The + * number of stages in the chain is dictated by the order value and the size indicates + * the decimation rate of this stage. + */ + public class DecimatingStage extends Stage + { + private Stage mFirstStage; + private Stage mDecimator; + + /** + * Constructs a decimating stage + * @param size + * @param order + */ + public DecimatingStage(int size, int order) + { + Stage stage = null; + + for(int x = 0; x < order; x++) + { + Stage nextStage; + + if(size == 2) + { + nextStage = new NoDecimateTwoStage(); + } + else + { + nextStage = new NoDecimateStage(size); + } + + //Chain each stage together + if(stage != null) + { + stage.setChild(nextStage); + } + + stage = nextStage; + + if(x == 0) + { + mFirstStage = stage; + } + } + + mDecimator = new Decimator(size); + + //Add a decimator to the end of the stage chain. + if(stage != null) + { + stage.setChild(mDecimator); + } + } + + @Override + public float[] process(float[] samples) + { + if(hasChild()) + { + return getChild().process(mFirstStage.process(samples)); + } + + return mFirstStage.process(samples); + } + + /** + * Override the default behavior since this stage is a chain of stages and we want any external + * child to be added to the final stage in the chain, which is the decimator. + * @param stage child to add + */ + @Override + public void setChild(Stage stage) + { + mDecimator.setChild(stage); + } + } + + /** + * Decimates samples of float arrays. + */ + public class Decimator extends Stage + { + private float[] mResidual; + private int mDecimationFactor; + + /** + * Constructs an instance. + * @param decimationFactor ratio of 1:decimate. + */ + public Decimator(int decimationFactor) + { + mDecimationFactor = decimationFactor; + } + + /** + * Decimates the sample array by combining the array with any residual from the previous decimation + * operation, decimating, and then storing any residual samples leftover from the decimation. + * @param samples to decimate + * @return decimated samples array. + */ + public float[] process(float[] samples) + { + if(mResidual != null) + { + float[] expanded = new float[mResidual.length + samples.length]; + System.arraycopy(mResidual, 0, expanded, 0, mResidual.length); + System.arraycopy(samples, 0, expanded, mResidual.length, samples.length); + samples = expanded; + mResidual = null; + } + + float[] decimated = new float[samples.length / mDecimationFactor]; + + for(int x = 0; x < decimated.length; x++) + { + decimated[x] = samples[x * mDecimationFactor]; + } + + int residual = samples.length % mDecimationFactor; + + if(residual > 0) + { + mResidual = Arrays.copyOfRange(samples, samples.length - residual, samples.length); + } + + if(hasChild()) + { + return getChild().process(decimated); + } + + return decimated; + } + } + + /** + * Single, non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement + * the stage so that stage size has essentially no impact on the computational requirements of the stage + */ + public class NoDecimateStage extends Stage + { + private float[] mSampleBuffer; + private int mSamplePointer = 0; + private int mSize; + protected float mSum; + protected float mGain; + + /** + * Constructs an instance. + * @param size of the stage + */ + public NoDecimateStage(int size) + { + mSize = size - 1; + mSampleBuffer = new float[mSize]; + float baseGain = 1.0f / (float)size; + mGain = baseGain * (1.0f / (1.0f - baseGain)); + } + + /** + * Alternate constructor that specifies gain only. + * @param gain to apply + */ + protected NoDecimateStage(float gain) + { + mGain = gain; + } + + @Override + public float[] process(float[] samples) + { + float sum = mSum; + float gain = mGain; + float size = mSize; + int pointer = mSamplePointer; + + for(int x = 0; x < samples.length; x++) + { + /* Subtract the oldest sample and add in the newest sample */ + sum -= (mSampleBuffer[pointer] + samples[x]); + + /* Overwrite the oldest sample with the newest */ + mSampleBuffer[pointer] = samples[x]; + pointer++; + pointer %= size; + samples[x] = (sum * gain); + } + + mSum = sum; + mSamplePointer = pointer; + + if(hasChild()) + { + return getChild().process(samples); + } + + return samples; + } + } + + /** + * Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage. + */ + public class NoDecimateTwoStage extends NoDecimateStage + { + public NoDecimateTwoStage() + { + //Use gain of 1/2 or 0.5 + super(0.5f); + } + + @Override + public float[] process(float[] samples) + { + float previousSample = mSum; + float temp; + + for(int x = 0; x < samples.length; x++) + { + //Calculate average of the current and previous samples + temp = (previousSample + samples[x]) * 0.5f; + previousSample = samples[x]; + samples[x] = temp; + } + + mSum = previousSample; + + if(hasChild()) + { + return getChild().process(samples); + } + + return samples; + } + } + + + /** + * Output adapter - applies gain correction and cleanup filter and broadcast + * to registered listener. + */ + public class Output extends Stage + { + private IRealFilter mFilter; + + public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException + { + //This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies + float[] coefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency); + mFilter = FilterFactory.getRealFilter(coefficients); + } + + @Override + public float[] process(float[] samples) + { + if(hasChild()) + { + return getChild().process(mFilter.filter(samples)); + } + + return mFilter.filter(samples); + } + + /** + * Creates a low-pass filter to use as the final cleanup filter for the decimated output stream + * + * @param sampleRate for the final output channel rate + * @param passFrequency for half of the desired channel rate + * @param stopFrequency for the attenuated band + * @return a newly designed filter or a previously designed (cached) filter + * @throws FilterDesignException + */ + private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException + { + FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() + .sampleRate(sampleRate) + .gridDensity(16) + .passBandCutoff(passFrequency) + .passBandAmplitude(1.0) + .passBandRipple(0.01) + .stopBandStart(stopFrequency) + .stopBandAmplitude(0.0) + .stopBandRipple(0.01) + .build(); + + RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); + + //This will throw an exception if the filter cannot be designed + return designer.getImpulseResponse(); + } + } + + public static void main(String[] args) + { + double sampleRate = 25.0; + int decimation = 5; + double finalRate = sampleRate / decimation; + IRealOscillator oscillator = OscillatorFactory.getRealOscillator(1.0, sampleRate); + + try + { + PrimeCicDecimationFilter filter = new PrimeCicDecimationFilter(sampleRate, decimation, 1.5, 2.0); + + for(int x = 0; x < 20; x++) + { + float[] samples = oscillator.generate(5000); + float[] decimated = filter.decimate(samples); + System.out.println(Arrays.toString(decimated)); + } + + } + catch(FilterDesignException fde) + { + fde.printStackTrace(); + } + + + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/cic/RealPrimeCICDecimate.java b/src/main/java/io/github/dsheirer/dsp/filter/cic/RealPrimeCICDecimate.java deleted file mode 100644 index 1acd545d..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/cic/RealPrimeCICDecimate.java +++ /dev/null @@ -1,382 +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.dsp.filter.cic; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Filters; -import io.github.dsheirer.dsp.filter.Window.WindowType; -import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2; -import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferAssembler; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.sample.decimator.RealDecimator; -import io.github.dsheirer.sample.real.RealSampleListener; -import org.apache.commons.lang3.Validate; -import org.apache.commons.math3.primes.Primes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -public class RealPrimeCICDecimate -{ - private final static Logger mLog = LoggerFactory.getLogger(RealPrimeCICDecimate.class); - - private List mDecimatingStages = new ArrayList(); - private DecimatingStage mFirstDecimatingStage; - private Output mOutput; - - private int mOutputBufferSize = 2048; - - /** - * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and - * decimated float sample output. - * - * Implements the CIC filter described in Understanding Digital Signal - * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple - * decimating stages each with a prime factor decimation rate. Multiple - * stages are cascaded to achieve the overall decimation rate. - * - * This filter supports a maximum decimation rate of 700. This filter can - * be adapted to higher decimation rates by adding additional prime factors - * to the PRIMES array. - * - * @param decimation - overall decimation rate - * @param order - filter order - */ - public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType) - { - this(decimation, order, passFrequency, attenuation, windowType, 2048); - } - - public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize) - { - Validate.isTrue(decimation <= 700); - - mOutputBufferSize = outputBufferSize; - - List stageSizes = Primes.primeFactors(decimation); - - for(int x = 0; x < stageSizes.size(); x++) - { - DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order); - - mDecimatingStages.add(stage); - - if(x == 0) - { - /* Reference to first stage -- will receive all samples */ - mFirstDecimatingStage = stage; - } - else - { - /* Wire the current stage to the previous stage */ - mDecimatingStages.get(x - 1).setListener(stage); - } - } - - mOutput = new Output(48000, passFrequency, attenuation, windowType, - outputBufferSize); - - mDecimatingStages.get(mDecimatingStages.size() - 1) - .setListener(mOutput); - } - - public void dispose() - { - for(DecimatingStage stage : mDecimatingStages) - { - stage.dispose(); - } - - mDecimatingStages.clear(); - mDecimatingStages = null; - mFirstDecimatingStage = null; - - mOutput.dispose(); - mOutput = null; - } - - /** - * Adds a listener to receive the output of this CIC decimation filter - */ - public void setListener(Listener listener) - { - mOutput.setListener(listener); - } - - /** - * Removes listener from output of this CIC decimation filter - */ - public void removeListener() - { - mOutput.removeListener(); - } - - /** - * Primary input method for receiving real buffers. - */ - public void receive(ReusableFloatBuffer buffer) - { - if(mFirstDecimatingStage != null) - { - for(float sample : buffer.getSamples()) - { - mFirstDecimatingStage.receive(sample); - } - } - - buffer.decrementUserCount(); - } - - /** - * Decimating stage combines multiple CIC stages with a decimator. The - * number of stages is indicated by the order value and the size indicates - * the decimation rate of this stage. - */ - public class DecimatingStage implements RealSampleListener - { - private ArrayList mStages = new ArrayList(); - private Stage mFirstStage; - private RealDecimator mDecimator; - - public DecimatingStage(int size, int order) - { - for(int x = 0; x < order; x++) - { - Stage stage; - - if(size == 2) - { - stage = new TwoStage(); - } - else - { - stage = new Stage(size); - } - - mStages.add(stage); - - if(x == 0) - { - mFirstStage = stage; - } - else - { - mStages.get(x - 1).setListener(stage); - } - } - - mDecimator = new RealDecimator(size); - - mStages.get(mStages.size() - 1).setListener(mDecimator); - } - - public void dispose() - { - for(Stage stage : mStages) - { - stage.dispose(); - } - - mStages.clear(); - mDecimator.dispose(); - mDecimator = null; - mFirstStage = null; - mStages = null; - } - - @Override - public void receive(float sample) - { - mFirstStage.receive(sample); - } - - public void setListener(RealSampleListener listener) - { - mDecimator.setListener(listener); - } - } - - /** - * Single non-decimating CIC stage component. Uses a circular buffer and a - * running average internally to implement the stage so that stage size has - * essentially no impact on the computational requirements of the stage - */ - public class Stage implements RealSampleListener - { - protected RealSampleListener mListener; - - private float[] mSamples; - - protected float mSum; - - private int mSamplePointer = 0; - private int mSize; - - protected float mGain; - - public Stage() - { - } - - public Stage(int size) - { - mSize = size - 1; - - mSamples = new float[mSize]; - - mGain = 1.0f / (float)size; - } - - public void dispose() - { - mListener = null; - } - - public void receive(float sample) - { - /* Subtract the oldest sample and add back in the newest */ - mSum = mSum - mSamples[mSamplePointer] + sample; - - /* Overwrite the oldest sample with the newest */ - mSamples[mSamplePointer] = sample; - - mSamplePointer++; - - if(mSamplePointer >= mSize) - { - mSamplePointer = 0; - } - - if(mListener != null) - { - mListener.receive(mSum * mGain); - } - } - - public void setListener(RealSampleListener listener) - { - mListener = listener; - } - } - - /** - * Size 2 stage that removes the unnecessary circular buffer management for - * a two-stage. - */ - public class TwoStage extends Stage - { - public TwoStage() - { - super(); - - mGain = 0.5f; - } - - public void receive(float sample) - { - float sum = mSum + sample; - - mSum = sample; - - if(mListener != null) - { - mListener.receive(sum * mGain); - } - } - } - - /** - * Output adapter - applies gain correction, applies cleanup filter, casts - * double-valued samples into float-valued complex sample and sends the - * result to the registered listener. - */ - public class Output implements RealSampleListener - { - /* Decimated output buffers will contain 1024 samples */ - private ReusableBufferAssembler mAssembler; - private Listener mReusableBufferListener; - - private RealFIRFilter2 mCleanupFilter; - private HalfBandFilter mHalfBandFilter = new HalfBandFilter( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false); - - public Output(int outputSampleRate, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize) - { - mCleanupFilter = new RealFIRFilter2(FilterFactory.getCICCleanupFilter(outputSampleRate, passFrequency, - attenuation, windowType), 0.4f); - - mAssembler = new ReusableBufferAssembler(outputBufferSize); - mAssembler.setListener(new Listener() - { - @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) - { - - if(mReusableBufferListener != null) - { - ReusableFloatBuffer cleanupFiltered = mCleanupFilter.filter(reusableFloatBuffer); - ReusableFloatBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered); - mReusableBufferListener.receive(halfBandFiltered); - } - else - { - reusableFloatBuffer.decrementUserCount(); - } - } - }); - } - - public void dispose() - { - mAssembler.dispose(); - mCleanupFilter.dispose(); - mHalfBandFilter.dispose(); - } - - /** - * Receiver method for the output adapter to receive a filtered, - * decimated sample, apply gain correction, apply cleanup filtering - * and output the sample values as a complex sample. - */ - @Override - public void receive(float sample) - { - mAssembler.receive(sample * 32.0f); - } - - /** - * Adds a listener to receive output samples - */ - public void setListener(Listener listener) - { - mReusableBufferListener = listener; - } - - /** - * Removes the listener from receiving output samples - */ - public void removeListener() - { - mReusableBufferListener = null; - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/correction/IQCorrectionFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/correction/IQCorrectionFilter.java deleted file mode 100644 index 03224bc2..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/correction/IQCorrectionFilter.java +++ /dev/null @@ -1,58 +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.dsp.filter.correction; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; - -public class IQCorrectionFilter -{ - private float mRatio = 0.00001f; - private float mAverageInphase = 0.0f; - private float mAverageQuadrature = 0.0f; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("IQCorrectionFilter"); - - public IQCorrectionFilter(float ratio) - { - mRatio = ratio; - } - - public IQCorrectionFilter() - { - } - - public ReusableComplexBuffer filter(ReusableComplexBuffer buffer) - { - float[] samples = buffer.getSamples(); - - ReusableComplexBuffer filtered = mReusableComplexBufferQueue.getBuffer(samples.length); - float[] filteredSamples = filtered.getSamples(); - - for(int x = 0; x < samples.length; x += 2) - { - mAverageInphase = mAverageInphase + (mRatio * (samples[x] - mAverageInphase)); - filteredSamples[x] = samples[x] - mAverageInphase; - - mAverageQuadrature = mAverageQuadrature + (mRatio * (samples[x + 1] - mAverageQuadrature)); - filteredSamples[x + 1] = samples[x + 1] - mAverageQuadrature; - } - - return filtered; - } -} 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 deleted file mode 100644 index 9d12e45d..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java +++ /dev/null @@ -1,97 +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.dsp.filter.dc; - -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -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 void filterReal(float[] samples) - { - float adjustment = getOffset(samples); - - for(int x = 0; x < samples.length; x++) - { - samples[x] = samples[x] - adjustment; - } - } - - private float getOffset(float[] samples) - { - mAccumulator = 0.0; - - for (float sample : samples) { - mAccumulator += sample; - } - - return (float)(mAccumulator / (double)samples.length); - } - - /** - * Calculates the DC offset present in the samples and subtracts the offset from all samples. - */ - public static void filterComplex(float[] samples) - { - double iAccumulator = 0.0; - double qAccumulator = 0.0; - - for(int x = 0; x < samples.length; x += 2) - { - iAccumulator += samples[x]; - qAccumulator += samples[x + 1]; - } - - double half = (double)samples.length / 2.0; - - float iAdjustment = (float)(iAccumulator / half); - float qAdjustment = (float)(qAccumulator / half); - - for(int x = 0; x < samples.length; x += 2) - { - samples[x] = samples[x] - iAdjustment; - samples[x + 1] = samples[x + 1] - qAdjustment; - } - } - - public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer) - { - float[] unfilteredSamples = unfilteredBuffer.getSamples(); - - ReusableFloatBuffer 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(); - - 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 deleted file mode 100644 index 8a54141f..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java +++ /dev/null @@ -1,96 +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.dsp.filter.dc; - -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -/** - * 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 -{ - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("DC Removal Filter"); - protected float mAverage; - protected float mRatio; - - /** - * Creates a DC removal filter. - * - * @param ratio - */ - public DCRemovalFilter(float ratio) - { - mRatio = ratio; - } - - /** - * Resets the running average to zero. - */ - public void reset() - { - mAverage = 0.0f; - } - - /** - * 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; - } - - 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. - * - * 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 ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer) - { - ReusableFloatBuffer 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(); - - return filteredBuffer; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/DcRemovalFilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/DcRemovalFilterFactory.java new file mode 100644 index 00000000..a5db586e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/DcRemovalFilterFactory.java @@ -0,0 +1,49 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; + +/** + * Factory for constructing the optimal DC removal filter. + */ +public class DcRemovalFilterFactory +{ + /** + * Creates the optimal DC removal filter for this computer + * @param gain to apply to DC removal + * @return optimal implementation. + */ + public static IDcRemovalFilter getFilter(float gain) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DC_REMOVAL_REAL); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorDcRemovalFilter(gain); + case SCALAR: + default: + return new ScalarDcRemovalFilter(gain); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/IDcRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/IDcRemovalFilter.java new file mode 100644 index 00000000..b5ec0adf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/IDcRemovalFilter.java @@ -0,0 +1,28 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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; + +/** + * Interface for a DC removal filter. + */ +public interface IDcRemovalFilter +{ + float[] filter(float[] samples); +} 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 b980b02f..70aa35a8 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,34 +1,30 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.filter.dc; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.sample.real.RealSampleListener; - -public class IIRSinglePoleDCRemovalFilter implements RealSampleListener +public class IIRSinglePoleDCRemovalFilter { - 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: @@ -64,42 +60,16 @@ public class IIRSinglePoleDCRemovalFilter implements RealSampleListener * 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 + * @param samples to filter * @return filtered samples in a new (ie reused) output buffer */ - public ReusableFloatBuffer filter(ReusableFloatBuffer inputBuffer) + public float[] filter(float[] samples) { - ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount()); - float[] inputSamples = inputBuffer.getSamples(); - float[] outputSamples = outputBuffer.getSamples(); - - for(int x = 0; x < inputSamples.length; x++) + for(int x = 0; x < samples.length; x++) { - outputSamples[x] = filter(inputSamples[x]); + samples[x] = filter(samples[x]); } - inputBuffer.decrementUserCount(); - - 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; + return samples; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/ScalarDcRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/ScalarDcRemovalFilter.java new file mode 100644 index 00000000..6fab6f56 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/ScalarDcRemovalFilter.java @@ -0,0 +1,86 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 java.util.Random; + +public class ScalarDcRemovalFilter implements IDcRemovalFilter +{ + private float mAverage; + private float mGain; + + public ScalarDcRemovalFilter(float gain) + { + mGain = gain; + } + + @Override + public float[] filter(float[] samples) + { + float accumulator = 0.0f; + float average = mAverage; + + for(float sample: samples) + { + accumulator += sample; + } + + float averageDCNow = (accumulator / samples.length) - average; + + average += (mGain * averageDCNow); + + for(int x = 0; x < samples.length; x++) + { + samples[x] -= average; + } + + mAverage = average; + + return samples; + } + + public static void main(String[] args) + { + System.out.println("Starting ..."); + Random random = new Random(); + ScalarDcRemovalFilter scalarFilter = new ScalarDcRemovalFilter(0.15f); + VectorDcRemovalFilter vectorFilter = new VectorDcRemovalFilter(0.15f); + + float dcOffset = 0.03f; + float[] samples = new float[8192]; + + for(int y = 0; y < samples.length; y++) + { + samples[y] = (random.nextFloat() * 2.0f) - 1.0f + dcOffset; + } + + long start = System.currentTimeMillis(); + + for(int x = 0; x < 1_000_000; x++) + { +// scalarFilter.filter(samples); + vectorFilter.filter(samples); + } + + long duration = System.currentTimeMillis() - start; + + System.out.println("Finished - Duration: " + duration); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/VectorDcRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/VectorDcRemovalFilter.java new file mode 100644 index 00000000..33cc14b2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/VectorDcRemovalFilter.java @@ -0,0 +1,87 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +import java.util.Random; + +public class VectorDcRemovalFilter implements IDcRemovalFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private float mAverageDc; + private float mGain; + + public VectorDcRemovalFilter(float gain) + { + mGain = gain; + } + + @Override + public float[] filter(float[] samples) + { + FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int pointer = 0; pointer < samples.length; pointer += VECTOR_SPECIES.length()) + { + accumulator = accumulator.add(FloatVector.fromArray(VECTOR_SPECIES, samples, pointer)); + } + + float sum = accumulator.reduceLanes(VectorOperators.ADD); + float average = mAverageDc; + float averageDCNow = (sum / samples.length) - average; + average += (mGain * averageDCNow); + + for(int pointer = 0; pointer < samples.length; pointer += VECTOR_SPECIES.length()) + { + //Load into vector, subtract dc, and write back to sample array + FloatVector.fromArray(VECTOR_SPECIES, samples, pointer).add(average).intoArray(samples, pointer); + } + + mAverageDc = average; + + return samples; + } + + public static void main(String[] args) + { + System.out.println("Starting ..."); + Random random = new Random(); + VectorDcRemovalFilter filter = new VectorDcRemovalFilter(0.15f); + + float dcOffset = 0.03f; + + for(int x = 0; x < 10; x++) + { + float[] samples = new float[8192]; + + for(int y = 0; y < samples.length; y++) + { + samples[y] = (random.nextFloat() * 2.0f) - 1.0f + dcOffset; + } + + samples = filter.filter(samples); + } + + System.out.println("Finished!"); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX1024Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX1024Filter.java deleted file mode 100644 index 01c067e4..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX1024Filter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Decimate by 1024 filter for complex valued sample buffers. - */ -public class ComplexDecimateX1024Filter extends ComplexDecimateX512Filter -{ - private static final int VALIDATION_LENGTH = 1024 * 2; - private static final int DECIMATE_BY_1024_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_1024_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX1024Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_1024_FILTER_LENGTH, - DECIMATE_BY_1024_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX128Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX128Filter.java deleted file mode 100644 index c6aac08e..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX128Filter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Decimate by 128 filter for complex valued sample buffers. - */ -public class ComplexDecimateX128Filter extends ComplexDecimateX64Filter -{ - private static final int VALIDATION_LENGTH = 128 * 2; - private static final int DECIMATE_BY_128_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_128_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX128Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_128_FILTER_LENGTH, - DECIMATE_BY_128_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX16Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX16Filter.java deleted file mode 100644 index a6204a0f..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX16Filter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Decimate by 16 filter for complex valued sample buffers. - */ -public class ComplexDecimateX16Filter extends ComplexDecimateX8Filter -{ - private static final int VALIDATION_LENGTH = 16 * 2; - private static final int DECIMATE_BY_16_FILTER_LENGTH = 15; - private static final Window.WindowType DECIMATE_BY_16_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX16Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_16_FILTER_LENGTH, - DECIMATE_BY_16_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX256Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX256Filter.java deleted file mode 100644 index edf62cda..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX256Filter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Decimate by 256 filter for complex valued sample buffers. - */ -public class ComplexDecimateX256Filter extends ComplexDecimateX128Filter -{ - private static final int VALIDATION_LENGTH = 256 * 2; - private static final int DECIMATE_BY_256_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_256_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX256Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_256_FILTER_LENGTH, - DECIMATE_BY_256_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX2Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX2Filter.java deleted file mode 100644 index 6adf9721..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX2Filter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Constructs the decimation filter. - */ -public class ComplexDecimateX2Filter extends ComplexHalfBandDecimationFilter -{ - private static final int DECIMATE_BY_2_FILTER_LENGTH = 63; - private static final Window.WindowType DECIMATE_BY_2_WINDOW_TYPE = Window.WindowType.HAMMING; - - /** - * Creates a half band filter with inherent decimation by two. - */ - public ComplexDecimateX2Filter() - { - super(FilterFactory.getHalfBand(DECIMATE_BY_2_FILTER_LENGTH, DECIMATE_BY_2_WINDOW_TYPE)); - } - - /** - * Validates that the samples length is an integer multiple of the specified validation multiple argument. - * @param samples to validate - * @param validationMultiple to validate against - */ - protected static void validate(float[] samples, int validationMultiple) - { - if(samples.length % validationMultiple != 0) - { - throw new IllegalArgumentException("Sample buffer length [" + samples.length + - "] must be an integer multiple of " + validationMultiple); - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX32Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX32Filter.java deleted file mode 100644 index b3edae7a..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX32Filter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Decimate by 32 filter for complex valued sample buffers. - */ -public class ComplexDecimateX32Filter extends ComplexDecimateX16Filter -{ - private static final int VALIDATION_LENGTH = 32 * 2; - private static final int DECIMATE_BY_32_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_32_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX32Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_32_FILTER_LENGTH, - DECIMATE_BY_32_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX4Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX4Filter.java deleted file mode 100644 index 738bd74a..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX4Filter.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; - -/** - * Decimate by 4 filter for complex valued sample buffers. - */ -public class ComplexDecimateX4Filter extends ComplexDecimateX2Filter -{ - private static final int VALIDATION_LENGTH = 4 * 2; - private static final int DECIMATE_BY_4_FILTER_LENGTH = 23; - private static final Window.WindowType DECIMATE_BY_4_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX4Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_4_FILTER_LENGTH, - DECIMATE_BY_4_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX512Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX512Filter.java deleted file mode 100644 index ef9d5472..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX512Filter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Decimate by 512 filter for complex valued sample buffers. - */ -public class ComplexDecimateX512Filter extends ComplexDecimateX256Filter -{ - private static final int VALIDATION_LENGTH = 512 * 2; - private static final int DECIMATE_BY_512_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_512_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX512Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_512_FILTER_LENGTH, - DECIMATE_BY_512_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX64Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX64Filter.java deleted file mode 100644 index 26152ec2..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX64Filter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Decimate by 64 filter for complex valued sample buffers. - */ -public class ComplexDecimateX64Filter extends ComplexDecimateX32Filter -{ - private static final int VALIDATION_LENGTH = 64 * 2; - private static final int DECIMATE_BY_64_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_64_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX64Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_64_FILTER_LENGTH, - DECIMATE_BY_64_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX8Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX8Filter.java deleted file mode 100644 index 42477c9c..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX8Filter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.decimate; - -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter; - -/** - * Decimate by 8 filter for complex valued sample buffers. - */ -public class ComplexDecimateX8Filter extends ComplexDecimateX4Filter -{ - private static final int VALIDATION_LENGTH = 8 * 2; - private static final int DECIMATE_BY_8_FILTER_LENGTH = 15; - private static final Window.WindowType DECIMATE_BY_8_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private ComplexHalfBandDecimationFilter mFilter; - - /** - * Constructs the decimation filter. - */ - public ComplexDecimateX8Filter() - { - mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_8_FILTER_LENGTH, - DECIMATE_BY_8_WINDOW_TYPE)); - } - - @Override - public float[] decimateComplex(float[] samples) - { - validate(samples, VALIDATION_LENGTH); - - //Decimate by this filter, then by the parent decimation filter - return super.decimateComplex(mFilter.decimateComplex(samples)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java index d8d5a750..e82337f5 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java @@ -19,11 +19,15 @@ package io.github.dsheirer.dsp.filter.decimate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Factory for creating real and complex decimation filters. */ public class DecimationFilterFactory { + private static final Logger mLog = LoggerFactory.getLogger(DecimationFilterFactory.class); private static final int[] SUPPORTED_RATES = new int[]{0,2,4,8,16,32,64,128,256,512,1024}; /** @@ -66,41 +70,25 @@ public class DecimationFilterFactory } /** - * Creates a complex-valued decimation filter for float array sample buffers providing greater than 100 dB of - * attenuation for out of band signal aliases. Supports power of 2 decimation rates up to 1024. - * - * @param decimationRate requested @see SUPPORTED_RATES - * @return constructed decimation filter + * Finds the greatest factor of 2 decimation rate that is less than the requested rate. + * @param requestedRate for decimation + * @return factor of 2 decimation rate that is closest to the requested rate */ - public static IComplexDecimationFilter getComplexDecimationFilter(int decimationRate) + public static int getDecimationRate(int requestedRate) { - switch(decimationRate) + if(requestedRate < 2) { - case 0: - return new ComplexDecimateX0Filter(); - case 2: - return new ComplexDecimateX2Filter(); - case 4: - return new ComplexDecimateX4Filter(); - case 8: - return new ComplexDecimateX8Filter(); - case 16: - return new ComplexDecimateX16Filter(); - case 32: - return new ComplexDecimateX32Filter(); - case 64: - return new ComplexDecimateX64Filter(); - case 128: - return new ComplexDecimateX128Filter(); - case 256: - return new ComplexDecimateX256Filter(); - case 512: - return new ComplexDecimateX512Filter(); - case 1024: - return new ComplexDecimateX1024Filter(); - default: - throw new IllegalArgumentException("Unsupported decimation rate: " + decimationRate + - ". Supported decimation rates are:" + SUPPORTED_RATES); + throw new IllegalArgumentException("Requested decimation rate must be greater than 2"); } + + for(int x = SUPPORTED_RATES.length - 1; x >= 0; x--) + { + if(SUPPORTED_RATES[x] < requestedRate) + { + return SUPPORTED_RATES[x]; + } + } + + return 2; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX1024Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX1024Filter.java index 5a4fba7c..030d8c5c 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX1024Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX1024Filter.java @@ -20,8 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 1024 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX1024Filter extends RealDecimateX512Filter { private static final int VALIDATION_LENGTH = 1024; private static final int DECIMATE_BY_1024_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_1024_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_1024_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX1024Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_1024_FILTER_LENGTH, - DECIMATE_BY_1024_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_1024_FILTER_LENGTH, + DECIMATE_BY_1024_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX128Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX128Filter.java index faf2da51..b6579a5c 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX128Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX128Filter.java @@ -20,9 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; -import io.github.dsheirer.gui.playlist.alias.ViewAliasIdentifierRequest; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 128 filter for real valued sample buffers. @@ -31,16 +29,16 @@ public class RealDecimateX128Filter extends RealDecimateX64Filter { private static final int VALIDATION_LENGTH = 128; private static final int DECIMATE_BY_128_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_128_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_128_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX128Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_128_FILTER_LENGTH, - DECIMATE_BY_128_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_128_FILTER_LENGTH, + DECIMATE_BY_128_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX16Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX16Filter.java index ca732a0b..5f9296c6 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX16Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX16Filter.java @@ -20,8 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 16 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX16Filter extends RealDecimateX8Filter { private static final int VALIDATION_LENGTH = 16; private static final int DECIMATE_BY_16_FILTER_LENGTH = 15; - private static final Window.WindowType DECIMATE_BY_16_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_16_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX16Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_16_FILTER_LENGTH, - DECIMATE_BY_16_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_16_FILTER_LENGTH, + DECIMATE_BY_16_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX256Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX256Filter.java index e72e3d36..98e15199 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX256Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX256Filter.java @@ -20,8 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 256 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX256Filter extends RealDecimateX128Filter { private static final int VALIDATION_LENGTH = 256; private static final int DECIMATE_BY_256_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_256_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_256_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX256Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_256_FILTER_LENGTH, - DECIMATE_BY_256_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_256_FILTER_LENGTH, + DECIMATE_BY_256_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX2Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX2Filter.java index c62daef8..9cc41832 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX2Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX2Filter.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -20,23 +20,29 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Constructs the decimation filter. */ -public class RealDecimateX2Filter extends RealHalfBandDecimationFilter +public class RealDecimateX2Filter implements IRealDecimationFilter { private static final int DECIMATE_BY_2_FILTER_LENGTH = 63; - private static final Window.WindowType DECIMATE_BY_2_WINDOW_TYPE = Window.WindowType.HAMMING; + private static final WindowType DECIMATE_BY_2_WINDOW_TYPE = WindowType.HAMMING; + private IRealDecimationFilter mFilter; /** * Creates a half band filter with inherent decimation by two. */ public RealDecimateX2Filter() { - super(FilterFactory.getHalfBand(DECIMATE_BY_2_FILTER_LENGTH, DECIMATE_BY_2_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_2_FILTER_LENGTH, + DECIMATE_BY_2_WINDOW_TYPE); + } + + @Override public float[] decimateReal(float[] samples) + { + return mFilter.decimateReal(samples); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX32Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX32Filter.java index 86227039..1a6bc192 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX32Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX32Filter.java @@ -20,8 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 32 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX32Filter extends RealDecimateX16Filter { private static final int VALIDATION_LENGTH = 32; private static final int DECIMATE_BY_32_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_32_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_32_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX32Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_32_FILTER_LENGTH, - DECIMATE_BY_32_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_32_FILTER_LENGTH, + DECIMATE_BY_32_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX4Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX4Filter.java index 5461cf91..469beae1 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX4Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX4Filter.java @@ -20,8 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 4 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX4Filter extends RealDecimateX2Filter { private static final int VALIDATION_LENGTH = 4; private static final int DECIMATE_BY_4_FILTER_LENGTH = 23; - private static final Window.WindowType DECIMATE_BY_4_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_4_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX4Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_4_FILTER_LENGTH, - DECIMATE_BY_4_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_4_FILTER_LENGTH, + DECIMATE_BY_4_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX512Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX512Filter.java index 7d2882ef..70e78dfb 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX512Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX512Filter.java @@ -20,8 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 512 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX512Filter extends RealDecimateX256Filter { private static final int VALIDATION_LENGTH = 512; private static final int DECIMATE_BY_512_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_512_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_512_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX512Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_512_FILTER_LENGTH, - DECIMATE_BY_512_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_512_FILTER_LENGTH, + DECIMATE_BY_512_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX64Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX64Filter.java index a2ba8c87..d18bff5d 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX64Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX64Filter.java @@ -20,8 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 64 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX64Filter extends RealDecimateX32Filter { private static final int VALIDATION_LENGTH = 64; private static final int DECIMATE_BY_64_FILTER_LENGTH = 11; - private static final Window.WindowType DECIMATE_BY_64_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_64_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX64Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_64_FILTER_LENGTH, - DECIMATE_BY_64_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_64_FILTER_LENGTH, + DECIMATE_BY_64_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX8Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX8Filter.java index 8feb9318..89267400 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX8Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX8Filter.java @@ -20,8 +20,7 @@ package io.github.dsheirer.dsp.filter.decimate; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; /** * Decimate by 8 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX8Filter extends RealDecimateX4Filter { private static final int VALIDATION_LENGTH = 8; private static final int DECIMATE_BY_8_FILTER_LENGTH = 15; - private static final Window.WindowType DECIMATE_BY_8_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_8_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX8Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_8_FILTER_LENGTH, - DECIMATE_BY_8_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_8_FILTER_LENGTH, + DECIMATE_BY_8_WINDOW_TYPE); } @Override 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 c4062976..a587b659 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,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -20,8 +20,8 @@ package io.github.dsheirer.dsp.filter.design; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; +import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; +import io.github.dsheirer.dsp.window.WindowType; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; @@ -59,11 +59,13 @@ public class FilterViewer extends Application */ private float[] getFilter() { - int filterLength = 63; - Window.WindowType windowType = Window.WindowType.HAMMING; + int filterLength = 47; + WindowType windowType = WindowType.HAMMING; float[] taps = FilterFactory.getHalfBand(filterLength, windowType); + taps = HilbertTransform.HALF_BAND_FILTER_47_TAP; + if(taps == null) { throw new IllegalStateException("Couldn't design filter"); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/HalfBandDecimationFilterAnalysis.java b/src/main/java/io/github/dsheirer/dsp/filter/design/HalfBandDecimationFilterAnalysis.java index 14c4dee3..763c695f 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/design/HalfBandDecimationFilterAnalysis.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/design/HalfBandDecimationFilterAnalysis.java @@ -20,7 +20,7 @@ package io.github.dsheirer.dsp.filter.design; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; +import io.github.dsheirer.dsp.window.WindowType; import org.apache.commons.math3.util.FastMath; import org.jtransforms.fft.FloatFFT_1D; @@ -35,7 +35,7 @@ public class HalfBandDecimationFilterAnalysis private static FloatFFT_1D FFT = new FloatFFT_1D(FFT_SIZE); private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0"); - private static final Window.WindowType WINDOW_TYPE = Window.WindowType.BLACKMAN; + private static final WindowType WINDOW_TYPE = WindowType.BLACKMAN; private static final int[] TAP_LENGTHS = new int[]{7,11,15,19,23,27,31,35,39,43,47,51,55,59,63,67,71,75,79,83,87,91,95,99,103,107}; private static final int BY_2 = (int)(FFT_SIZE / 2 * (1.0 / 2.0)); private static final int BY_2_25 = (int)(FFT_SIZE / 2 * (1.0 / 2.25)); @@ -58,7 +58,7 @@ public class HalfBandDecimationFilterAnalysis public static void analyze() { - for(Window.WindowType windowType: Window.NO_PARAMETER_WINDOWS) + for(WindowType windowType: WindowType.NO_PARAMETER_WINDOWS) { StringBuilder sb = new StringBuilder(); sb.append("Window: " + windowType).append("\n"); @@ -133,7 +133,7 @@ public class HalfBandDecimationFilterAnalysis * @param windowType to apply to the taps during filter creation * @return filter */ - private static float[] getTaps(int filterLength, Window.WindowType windowType) + private static float[] getTaps(int filterLength, WindowType windowType) { return FilterFactory.getHalfBand(filterLength, windowType); } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter2.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter2.java deleted file mode 100644 index 7c618c00..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter2.java +++ /dev/null @@ -1,138 +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.dsp.filter.fir.complex; - -import io.github.dsheirer.dsp.filter.fir.FIRFilter; -import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.complex.Complex; - -public class ComplexFIRFilter2 extends FIRFilter -{ - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Complex FIR Filter"); - private RealFIRFilter2 mIFilter; - private RealFIRFilter2 mQFilter; - - /** - * Complex FIR Filter for processing complex sample pairs that internally uses two RealFIRFilter - * instances. - * - * @param coefficients - filter taps - * @param gain - gain to apply to filtered outputs - use 1.0f for no gain - */ - public ComplexFIRFilter2(float[] coefficients, float gain) - { - mIFilter = new RealFIRFilter2(coefficients, gain); - mQFilter = new RealFIRFilter2(coefficients, gain); - } - - /** - * Complex FIR Filter for processing complex sample pairs that internally uses two RealFIRFilter - * instances. This constructor uses a default gain of 1.0f. - * - * @param coefficients - filter taps - */ - public ComplexFIRFilter2(float[] coefficients) - { - this(coefficients, 1.0f); - } - - /** - * Filters the inphase sample value. - * @param sample to filter - * @return filtered sample - */ - public float filterInphase(float sample) - { - return mIFilter.filter(sample); - } - - /** - * Current filtered inphase sample value after invoking the filterInphase() method. - */ - public float currentInphaseValue() - { - return mIFilter.currentValue(); - } - - /** - * Filters the quadrature sample value. - * @param sample to filter - * @return filtered sample - */ - public float filterQuadrature(float sample) - { - return mQFilter.filter(sample); - } - - /** - * Current filtered quadrature sample value after invoking the filterInphase() method. - */ - public float currentQuadratureValue() - { - return mQFilter.currentValue(); - } - - /** - * Filters the complex sample. - * @param sample to filter - * @return filtered sample - */ - public Complex filter(Complex sample) - { - float i = filterInphase(sample.inphase()); - float q = filterQuadrature(sample.quadrature()); - - return new Complex(i, q); - } - - /** - * Filters the complex samples from the reusable buffer and returns a new complex buffer with the filtered output - * - * Note: the original reusable buffer user count is decremented and a new reusable buffer is returned - * with the user count already incremented to one. - * - * @param originalBuffer with complex samples to filter - * @return new buffer containing filtered complex samples - */ - public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer) - { - ReusableComplexBuffer filteredBuffer = mReusableComplexBufferQueue.getBuffer(originalBuffer.getSamples().length); - filteredBuffer.setTimestamp(originalBuffer.getTimestamp()); - - float[] samples = originalBuffer.getSamples(); - float[] filteredSamples = filteredBuffer.getSamples(); - - for(int x = 0; x < originalBuffer.getSamples().length; x += 2) - { - filteredSamples[x] = filterInphase(samples[x]); - filteredSamples[x + 1] = filterQuadrature(samples[x + 1]); - } - - originalBuffer.decrementUserCount(); - - return filteredBuffer; - } - - @Override - public void dispose() - { - mIFilter.dispose(); - mQFilter.dispose(); - mReusableComplexBufferQueue.dispose(); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/IComplexFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/IComplexFilter.java new file mode 100644 index 00000000..aac7f9c2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/IComplexFilter.java @@ -0,0 +1,28 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.complex; + +/** + * Interface for a filter for float array sample buffers + */ +public interface IComplexFilter +{ + float[] filter(float[] samples); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ScalarComplexFIRFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ScalarComplexFIRFilter.java new file mode 100644 index 00000000..ae154d87 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ScalarComplexFIRFilter.java @@ -0,0 +1,152 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.complex; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.window.WindowType; +import org.apache.commons.lang3.ArrayUtils; + +import java.text.DecimalFormat; +import java.util.Random; + +public class ScalarComplexFIRFilter implements IComplexFilter +{ + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public ScalarComplexFIRFilter(float[] coefficients) + { + //Reverse the order of the coefficients. + ArrayUtils.reverse(coefficients); + + //Double the size of the coefficients array to align with the complex samples array + mCoefficients = new float[coefficients.length * 2]; + for(int x = 0; x < coefficients.length; x++) + { + mCoefficients[2 * x] = coefficients[x]; + } + + mBufferOverlap = mCoefficients.length - 2; + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + int offset = 0; + + float iAccumulator, qAccumulator; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + iAccumulator = 0.0f; + qAccumulator = 0.0f; + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += 2) + { + offset = bufferPointer + coefficientPointer; + iAccumulator += mBuffer[offset] * mCoefficients[coefficientPointer]; + qAccumulator += mBuffer[offset + 1] * mCoefficients[coefficientPointer]; + } + + filtered[bufferPointer] = iAccumulator; + filtered[bufferPointer + 1] = qAccumulator; + } + + return filtered; + } + + public static void main(String[] args) + { + Random random = new Random(); + + int sampleSize = 2048; + + float[] samples = new float[sampleSize]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + float[] coefficients = FilterFactory.getLowPass(1000, 250, 99, WindowType.BLACKMAN); + +// ComplexFIRFilter2 legacyFilter = new ComplexFIRFilter2(coefficients); + ScalarComplexFIRFilter filter = new ScalarComplexFIRFilter(coefficients); + VectorComplexFIRFilter128Bit vectorFilter = new VectorComplexFIRFilter128Bit(coefficients); + + double accumulator = 0.0d; + + int iterations = 1_000_000; + + long start = System.currentTimeMillis(); + + for(int x = 0; x < iterations; x++) + { +// float[] filtered = legacyFilter.filter(samples); +// float[] filtered = filter.filter(samples); + float[] filtered = vectorFilter.filter(samples); +// float[] vfiltered = vectorFilter.filter(samples); + accumulator += filtered[3]; + } + +// System.out.println("REG:" + Arrays.toString(filtered)); +// System.out.println("VEC:" + Arrays.toString(vfiltered)); + + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } + +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter128Bit.java new file mode 100644 index 00000000..1ff4e32f --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter128Bit.java @@ -0,0 +1,124 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.complex; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorComplexFIRFilter128Bit implements IComplexFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final VectorMask I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES); + private static final VectorMask Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES); + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorComplexFIRFilter128Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length * 2)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients with each tap duplicated for complex filtering + ArrayUtils.reverse(coefficients); + + for(int x = 0; x < coefficients.length; x++) + { + mCoefficients[2 * x] = coefficients[x]; + mCoefficients[2 * x + 1] = coefficients[x]; + } + + //Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2); + + //Set to non-null and we'll resize on the first buffer. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK); + filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter64Bit.java new file mode 100644 index 00000000..6515bb06 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter64Bit.java @@ -0,0 +1,124 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.complex; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorComplexFIRFilter64Bit implements IComplexFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final VectorMask I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES); + private static final VectorMask Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES); + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorComplexFIRFilter64Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length * 2)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients with each tap duplicated for complex filtering + ArrayUtils.reverse(coefficients); + + for(int x = 0; x < coefficients.length; x++) + { + mCoefficients[2 * x] = coefficients[x]; + mCoefficients[2 * x + 1] = coefficients[x]; + } + + //Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2); + + //Set to non-null and we'll resize on the first buffer. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK); + filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilterDefaultBit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilterDefaultBit.java new file mode 100644 index 00000000..4a45dbcf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilterDefaultBit.java @@ -0,0 +1,124 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.complex; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorComplexFIRFilterDefaultBit implements IComplexFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final VectorMask I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES); + private static final VectorMask Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES); + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorComplexFIRFilterDefaultBit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length * 2)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients with each tap duplicated for complex filtering + ArrayUtils.reverse(coefficients); + + for(int x = 0; x < coefficients.length; x++) + { + mCoefficients[2 * x] = coefficients[x]; + mCoefficients[2 * x + 1] = coefficients[x]; + } + + //Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2); + + //Set to non-null and we'll resize on the first buffer. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK); + filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/IRealFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/IRealFilter.java new file mode 100644 index 00000000..41db8ddc --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/IRealFilter.java @@ -0,0 +1,28 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.real; + +/** + * Interface for a filter for float array sample buffers + */ +public interface IRealFilter +{ + float[] filter(float[] samples); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java new file mode 100644 index 00000000..82e6c5cf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java @@ -0,0 +1,135 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.real; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.window.WindowType; +import org.apache.commons.lang3.ArrayUtils; + +import java.text.DecimalFormat; +import java.util.Random; + +/** + * Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays. + * + * Note: filtering operations in this class are structured to leverage SIMD processor intrinsics when + * available to the Java runtime. + */ +public class RealFIRFilter implements IRealFilter +{ + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public RealFIRFilter(float[] coefficients) + { + //Reverse the order of the coefficients. + mCoefficients = coefficients; + ArrayUtils.reverse(mCoefficients); + mBufferOverlap = mCoefficients.length - 1; + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer++) + { + filtered[bufferPointer] += mBuffer[bufferPointer + coefficientPointer] * mCoefficients[coefficientPointer]; + } + } + + return filtered; + } + + public static void main(String[] args) + { + Random random = new Random(); + + int sampleSize = 2048; + + float[] samples = new float[sampleSize]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + float[] coefficients = FilterFactory.getLowPass(1000, 250, 99, WindowType.BLACKMAN); + + RealFIRFilter filter = new RealFIRFilter(coefficients); + VectorRealFIRFilter256Bit vectorFilter = new VectorRealFIRFilter256Bit(coefficients); + + double accumulator = 0.0d; + + int iterations = 1_000_000; + + long start = System.currentTimeMillis(); + + for(int x = 0; x < iterations; x++) + { + float[] filtered = filter.filter(samples); +// float[] filtered = vectorFilter.filter(samples); +// float[] vfiltered = vectorFilter.filter(samples); + accumulator += filtered[3]; + } + +// System.out.println("REG:" + Arrays.toString(filtered)); +// System.out.println("VEC:" + Arrays.toString(vfiltered)); + + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } +} \ No newline at end of file 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 deleted file mode 100644 index ee866c96..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java +++ /dev/null @@ -1,132 +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.dsp.filter.fir.real; - -import io.github.dsheirer.dsp.filter.fir.FIRFilter; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -/** - * Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays. - * - * Note: filtering operations in this class are structured to leverage SIMD processor intrinsics when - * available to the Java runtime. - */ -public class RealFIRFilter2 extends FIRFilter -{ - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealFIRFilter2"); - - private float[] mData; - private float[] mCoefficients; - private float mGain; - private float mAccumulator; - - /** - * Float sample FIR filter base class. - * - * @param coefficients - filter coefficients in normal order. - * @param gain value to apply to the filtered output. Use 1.0f for no gain - */ - public RealFIRFilter2(float[] coefficients, float gain) - { - mGain = gain; - mCoefficients = coefficients; - mData = new float[coefficients.length]; - } - - /** - * Float sample FIR filter base class that uses a default gain value of 1.0f. - * - * @param coefficients - filter coefficients in normal order. - */ - public RealFIRFilter2(float[] coefficients) - { - this(coefficients, 1.0f); - } - - /** - * Disposes this filter to prepare for garbage collection. - */ - @Override - public void dispose() - { - mCoefficients = null; - mData = null; - } - - /** - * Filters the sample argument. Loads the sample into the internal data buffer and performs - * convolution between the sample buffer and the filter coefficients. - * - * @param sample to load - * @return filtered value - */ - public float filter(float sample) - { - //Use array copy to leverage SIMD intrinsics - System.arraycopy(mData, 0, mData, 1, mData.length - 1); - mData[0] = sample; - - mAccumulator = 0.0f; - - //Use vector dot product to leverage SIMD intrinsics - for(int x = 0; x < mCoefficients.length; x++) - { - mAccumulator = Math.fma(mData[x], mCoefficients[x], mAccumulator); - } - - /* Apply gain and return the filtered value */ - mAccumulator *= mGain; - - return mAccumulator; - } - - /** - * Current filtered output value for the filter after the filter() method has been invoked. - */ - public float currentValue() - { - return mAccumulator; - } - - - /** - * Filters the samples contained in the unfilteredBuffer and returns a new reusable buffer with the - * filtered samples. - * - * Note: user count on the returned (new) buffer is set to one and the user count is decremented on - * the unfiltered buffer argument. - * - * @param unfilteredBuffer containing a sample array to be filtered - * @return a new reusable buffer with the filtered samples. - */ - public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer) - { - float[] unfilteredSamples = unfilteredBuffer.getSamples(); - - ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length); - float[] filteredSamples = filteredBuffer.getSamples(); - - for(int x = 0; x < unfilteredSamples.length; x++) - { - filteredSamples[x] = filter(unfilteredSamples[x]); - } - - unfilteredBuffer.decrementUserCount(); - - return filteredBuffer; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter128Bit.java new file mode 100644 index 00000000..52a86bce --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter128Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilter128Bit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilter128Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter256Bit.java new file mode 100644 index 00000000..24c8190e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter256Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilter256Bit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilter256Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter512Bit.java new file mode 100644 index 00000000..7688380f --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter512Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilter512Bit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilter512Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter64Bit.java new file mode 100644 index 00000000..de53d409 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter64Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilter64Bit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilter64Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilterDefaultBit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilterDefaultBit.java new file mode 100644 index 00000000..be40db4d --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilterDefaultBit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilterDefaultBit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilterDefaultBit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/RealHalfBandDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/RealHalfBandDecimationFilter.java new file mode 100644 index 00000000..82210c3b --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/RealHalfBandDecimationFilter.java @@ -0,0 +1,160 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; + +import java.text.DecimalFormat; +import java.util.Random; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when + * supported by the host CPU. + * + * Note: this class is structured to process an entire float array, versus processing one sample at a time from the + * array. + */ +public class RealHalfBandDecimationFilter implements IRealDecimationFilter +{ + private static final float CENTER_COEFFICIENT = 0.5f; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public RealHalfBandDecimationFilter(float[] coefficients) + { + if((coefficients.length + 1) % 4 != 0) + { + throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]"); + } + + mCoefficients = coefficients; + mBufferOverlap = mCoefficients.length - 1; + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + float accumulator = 0.0f; + int half = mBufferOverlap / 2; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = 0.0f; + + for(int coefficientPointer = 0; coefficientPointer < half; coefficientPointer += 2) + { + //Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by + //one of the coefficients to achieve the same effect. + accumulator += mCoefficients[coefficientPointer] * + (mBuffer[bufferPointer + coefficientPointer] + + mBuffer[bufferPointer + (mBufferOverlap - coefficientPointer)]); + } + + accumulator += mBuffer[bufferPointer + half] * CENTER_COEFFICIENT; + + filtered[bufferPointer / 2] = accumulator; + } + + return filtered; + } + + public static void main(String[] args) + { + Random random = new Random(); + + int sampleSize = 2048; + + float[] samples = new float[sampleSize]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + float[] coefficients = FilterFactory.getHalfBand(63, WindowType.BLACKMAN); + +// RealHalfBandDecimationFilter filter = new RealHalfBandDecimationFilter(coefficients); +// VectorRealHalfBandDecimationFilter63Tap64Bit vectorFilter64 = new VectorRealHalfBandDecimationFilter63Tap64Bit(coefficients); +// VectorRealHalfBandDecimationFilter63Tap128Bit vectorFilter128 = new VectorRealHalfBandDecimationFilter63Tap128Bit(coefficients); +// VectorRealHalfBandDecimationFilter63Tap256Bit vectorFilter256 = new VectorRealHalfBandDecimationFilter63Tap256Bit(coefficients); +// VectorRealHalfBandDecimationFilter23Tap512Bit vectorFilter512 = new VectorRealHalfBandDecimationFilter23Tap512Bit(coefficients); + VectorRealHalfBandDecimationFilter256Bit vectorFilter = new VectorRealHalfBandDecimationFilter256Bit(coefficients); + + double accumulator = 0.0d; + + int iterations = 10_000_000; + + long start = System.currentTimeMillis(); + + for(int x = 0; x < iterations; x++) + { +// float[] filtered = filter.decimateReal(samples); +// float[] filtered = vectorFilter256.decimateReal(samples); + float[] filtered = vectorFilter.decimateReal(samples); +// float[] vfiltered = vectorFilterGeneric.decimateReal(samples); + accumulator += filtered[3]; + } + +// System.out.println("REG:" + Arrays.toString(filtered)); +// System.out.println("VEC:" + Arrays.toString(vfiltered)); + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap128Bit.java new file mode 100644 index 00000000..0068225d --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap128Bit.java @@ -0,0 +1,115 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 11-tap half band filter running on a CPU that supports 128-bit/4-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter11Tap128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final int COEFFICIENTS_LENGTH = 11; + + private float[] mCoefficients = new float[12]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter11Tap128Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + + FloatVector product1, product2, product3; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + filtered[bufferPointer / 2] = product1.add(product2).add(product3).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap256Bit.java new file mode 100644 index 00000000..aad736bf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap256Bit.java @@ -0,0 +1,112 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 11-tap half band filter running on a CPU that supports 256-bit/8-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter11Tap256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private static final int COEFFICIENTS_LENGTH = 11; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter11Tap256Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + + FloatVector product1, product2; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 3)); + filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/RealHalfBandDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap512Bit.java similarity index 51% rename from src/main/java/io/github/dsheirer/dsp/filter/halfband/real/RealHalfBandDecimationFilter.java rename to src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap512Bit.java index 53d99569..6bf03c6e 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/RealHalfBandDecimationFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap512Bit.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -16,29 +16,30 @@ * along with this program. If not, see * **************************************************************************** */ -package io.github.dsheirer.dsp.filter.halfband.real; +package io.github.dsheirer.dsp.filter.halfband; import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; /** * Implements a half-band filter that produces one filtered output for every two input samples. * - * This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when - * supported by the host CPU. + * This filter uses the Java Vector API for SIMD available in JDK 17+. * - * Note: this class is structured to process an entire float array, versus processing one sample at a time from the - * array. + * This filter is optimized for an 11-tap half band filter running on a CPU that supports 512-bit/16-lane floating + * point SIMD instructions. */ -public class RealHalfBandDecimationFilter implements IRealDecimationFilter +public class VectorRealHalfBandDecimationFilter11Tap512Bit implements IRealDecimationFilter { - private static final float CENTER_COEFFICIENT = 0.5f; - private float[] mCoefficients; + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private static final int COEFFICIENTS_LENGTH = 11; + + private float[] mCoefficients = new float[16]; private float[] mBuffer; - private float mAccumulator; - private int mCoefficientPointer; - private int mCoefficientsLengthMinus1; - private int mBufferPointer; - private int mHalf; + private int mBufferOverlap; /** * Creates a half band filter with inherent decimation by two. @@ -46,16 +47,22 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are * zero valued except for the middle odd index coefficient which should be valued 0.5 */ - public RealHalfBandDecimationFilter(float[] coefficients) + public VectorRealHalfBandDecimationFilter11Tap512Bit(float[] coefficients) { - if((coefficients.length + 1) % 4 != 0) + if(coefficients.length != COEFFICIENTS_LENGTH) { - throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]"); + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); } - mCoefficients = coefficients; - mCoefficientsLengthMinus1 = mCoefficients.length - 1; - mHalf = mCoefficientsLengthMinus1 / 2; + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); } public float[] decimateReal(float[] samples) @@ -65,7 +72,7 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); } - int bufferLength = samples.length + mCoefficientsLengthMinus1; + int bufferLength = samples.length + mBufferOverlap; if(mBuffer == null) { @@ -75,36 +82,28 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter { float[] temp = new float[bufferLength]; //Move residual samples from end of old buffer to the beginning of the new temp buffer - System.arraycopy(mBuffer, mBuffer.length - mCoefficientsLengthMinus1, temp, 0, mCoefficientsLengthMinus1); + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); mBuffer = temp; } else { //Move residual samples from end of buffer to the beginning of the buffer - System.arraycopy(mBuffer, samples.length, mBuffer, 0, mCoefficientsLengthMinus1); + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); } //Copy new sample array into end of buffer - System.arraycopy(samples, 0, mBuffer, mCoefficientsLengthMinus1, samples.length); + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); float[] filtered = new float[samples.length / 2]; - for(mBufferPointer = 0; mBufferPointer < samples.length; mBufferPointer += 2) + FloatVector filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + + FloatVector product; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) { - mAccumulator = 0.0f; - - for(mCoefficientPointer = 0; mCoefficientPointer < mHalf; mCoefficientPointer += 2) - { - //Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by - //one of the coefficients to achieve the same effect. - mAccumulator += mCoefficients[mCoefficientPointer] * - (mBuffer[mBufferPointer + mCoefficientPointer] + - mBuffer[mBufferPointer + (mCoefficientsLengthMinus1 - mCoefficientPointer)]); - } - - mAccumulator += mBuffer[mBufferPointer + mHalf] * CENTER_COEFFICIENT; - - filtered[mBufferPointer / 2] = mAccumulator; + product = filter.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + filtered[bufferPointer / 2] = product.reduceLanes(VectorOperators.ADD); } return filtered; diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap64Bit.java new file mode 100644 index 00000000..ecbcc1df --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap64Bit.java @@ -0,0 +1,122 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 11-tap half band filter running on a CPU that supports 64-bit/2-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter11Tap64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final int COEFFICIENTS_LENGTH = 11; + + private float[] mCoefficients = new float[12]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter11Tap64Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6); + FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10); + + FloatVector product1, product2, product3, product4, product5, product6; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6)); + product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10)); + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5) + .add(product6).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter128Bit.java new file mode 100644 index 00000000..87c251ae --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter128Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any tap length and uses SIMD 128-bit/4-lane instructions. + */ +public class VectorRealHalfBandDecimationFilter128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter128Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap128Bit.java new file mode 100644 index 00000000..18932032 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap128Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 15-tap half band filter running on a CPU that supports 128-bit/4-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter15Tap128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final int COEFFICIENTS_LENGTH = 15; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter15Tap128Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + + FloatVector product1, product2, product3, product4; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap256Bit.java new file mode 100644 index 00000000..41a32c92 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap256Bit.java @@ -0,0 +1,112 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 15-tap half band filter running on a CPU that supports 256-bit/8-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter15Tap256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private static final int COEFFICIENTS_LENGTH = 15; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter15Tap256Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + + FloatVector product1, product2; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap512Bit.java new file mode 100644 index 00000000..b34294b2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap512Bit.java @@ -0,0 +1,111 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 15-tap half band filter running on a CPU that supports 512-bit/16-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter15Tap512Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private static final int COEFFICIENTS_LENGTH = 15; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter15Tap512Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + + FloatVector product; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product = filter.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + filtered[bufferPointer / 2] = product.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap64Bit.java new file mode 100644 index 00000000..e5a358f8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap64Bit.java @@ -0,0 +1,127 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 15-tap half band filter running on a CPU that supports 64-bit/2-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter15Tap64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final int COEFFICIENTS_LENGTH = 15; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter15Tap64Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6); + FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10); + FloatVector filter7 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filter8 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 14); + + FloatVector product1, product2, product3, product4, product5, product6, product7, product8; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6)); + product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10)); + product7 = filter7.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12)); + product8 = filter8.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 14)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5) + .add(product6).add(product7).add(product8).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap128Bit.java new file mode 100644 index 00000000..bab951a0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap128Bit.java @@ -0,0 +1,123 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 23-tap half band filter running on a CPU that supports 128-bit/4-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter23Tap128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final int COEFFICIENTS_LENGTH = 23; + + private float[] mCoefficients = new float[24]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter23Tap128Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20); + + FloatVector product1, product2, product3, product4, product5, product6; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12)); + product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16)); + product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 20)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5) + .add(product6).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap256Bit.java new file mode 100644 index 00000000..91fa52cf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap256Bit.java @@ -0,0 +1,115 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 23-tap half band filter running on a CPU that supports 256-bit/8-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter23Tap256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private static final int COEFFICIENTS_LENGTH = 23; + + private float[] mCoefficients = new float[24]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter23Tap256Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + + FloatVector product1, product2, product3; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap512Bit.java new file mode 100644 index 00000000..f0704b0a --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap512Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 23-tap half band filter running on a CPU that supports 512-bit/16-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter23Tap512Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private static final int COEFFICIENTS_LENGTH = 23; + + private float[] mCoefficients = new float[32]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter23Tap512Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + int laneMultiples = VECTOR_SPECIES.length() * (coefficients.length / VECTOR_SPECIES.length() + 1); + mBufferOverlap = Math.max(laneMultiples - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + + FloatVector product1, product2; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16)); + filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap64Bit.java new file mode 100644 index 00000000..97286c50 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap64Bit.java @@ -0,0 +1,137 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 23-tap half band filter running on a CPU that supports 64-bit/2-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter23Tap64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final int COEFFICIENTS_LENGTH = 23; + + private float[] mCoefficients = new float[24]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter23Tap64Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6); + FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10); + FloatVector filter7 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filter8 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 14); + FloatVector filter9 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + FloatVector filter10 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 18); + FloatVector filter11 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20); + FloatVector filter12 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 22); + + FloatVector product1, product2, product3, product4, product5, product6, product7, product8; + FloatVector product9, product10, product11, product12; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6)); + product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10)); + product7 = filter7.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12)); + product8 = filter8.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 14)); + product9 = filter9.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16)); + product10 = filter10.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 18)); + product11 = filter11.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 20)); + product12 = filter12.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 22)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5) + .add(product6).add(product7).add(product8).add(product9).add(product10).add(product11) + .add(product12).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter256Bit.java new file mode 100644 index 00000000..b725780e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter256Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any tap length and uses SIMD 256-bit/8-lane instructions. + */ +public class VectorRealHalfBandDecimationFilter256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter256Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter512Bit.java new file mode 100644 index 00000000..e05bec7b --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter512Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any tap length and uses SIMD 512-bit/16-lane instructions. + */ +public class VectorRealHalfBandDecimationFilter512Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter512Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap128Bit.java new file mode 100644 index 00000000..ef863db3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap128Bit.java @@ -0,0 +1,117 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 63-tap half band filter running on a CPU that supports 128-bit/4-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter63Tap128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final int COEFFICIENTS_LENGTH = 63; + + private float[] mCoefficients = new float[64]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter63Tap128Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap256Bit.java new file mode 100644 index 00000000..d9139180 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap256Bit.java @@ -0,0 +1,117 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 63-tap half band filter running on a CPU that supports 256-bit/8-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter63Tap256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private static final int COEFFICIENTS_LENGTH = 63; + + private float[] mCoefficients = new float[64]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter63Tap256Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap512Bit.java new file mode 100644 index 00000000..9ca9acee --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap512Bit.java @@ -0,0 +1,117 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 63-tap half band filter running on a CPU that supports 512-bit/16-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter63Tap512Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private static final int COEFFICIENTS_LENGTH = 63; + + private float[] mCoefficients = new float[64]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter63Tap512Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap64Bit.java new file mode 100644 index 00000000..0a49b7ca --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap64Bit.java @@ -0,0 +1,117 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 63-tap half band filter running on a CPU that supports 64-bit/2-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter63Tap64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final int COEFFICIENTS_LENGTH = 63; + + private float[] mCoefficients = new float[64]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter63Tap64Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter64Bit.java new file mode 100644 index 00000000..ffb82a5f --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter64Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any tap length and uses SIMD 64-bit/2-lane instructions. + */ +public class VectorRealHalfBandDecimationFilter64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter64Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilterDefaultBit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilterDefaultBit.java new file mode 100644 index 00000000..be317504 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilterDefaultBit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any combination of tap length and SIMD lane lengths. + */ +public class VectorRealHalfBandDecimationFilterDefaultBit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilterDefaultBit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java deleted file mode 100644 index b13ae60c..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 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.halfband.complex; - -import io.github.dsheirer.dsp.filter.decimate.IComplexDecimationFilter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; - -/** - * Complex half-band filter that processes samples on a per-array basis, versus a per-sample basis. - */ -public class ComplexHalfBandDecimationFilter implements IComplexDecimationFilter -{ - private static final float CENTER_COEFFICIENT = 0.5f; - private float[] mCoefficients; - private float[] mBuffer; - private float mIAccumulator; - private float mQAccumulator; - private int mCoefficientPointer; - private int mCoefficientsLengthMinus2; - private int mBufferPointer; - private int mHalf; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("half-band decimation filter"); - - /** - * Constructs a complex sample half-band decimate x2 filter using the specified filter coefficients. - * @param coefficients for the half-band filter where the middle coefficient is 0.5, and the even-numbered - * coefficients are non-zero and symmetrical, and the odd-numbered coefficients are all zero valued. - */ - public ComplexHalfBandDecimationFilter(float[] coefficients) - { - if((coefficients.length + 1) % 4 != 0) - { - throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and " + - "symmetrical where L = [x * 4 - 1]"); - } - - mCoefficients = new float[coefficients.length * 2]; - for(int x = 0; x < coefficients.length; x++) - { - mCoefficients[2 * x] = coefficients[x]; - mCoefficients[2 * x + 1] = coefficients[x]; - } - - mCoefficientsLengthMinus2 = mCoefficients.length - 2; - mHalf = mCoefficients.length / 2 - 1; - } - - public float[] decimateComplex(float[] samples) - { - if(samples.length % 4 != 0) - { - throw new IllegalArgumentException("Samples array length must be an integer multiple of 4"); - } - - int bufferLength = samples.length + mCoefficientsLengthMinus2; - - if(mBuffer == null) - { - mBuffer = new float[bufferLength]; - } - else if(mBuffer.length != bufferLength) - { - float[] temp = new float[bufferLength]; - //Move residual samples from end of old buffer to the beginning of the new temp buffer - System.arraycopy(mBuffer, mBuffer.length - mCoefficientsLengthMinus2, temp, 0, mCoefficientsLengthMinus2); - mBuffer = temp; - } - else - { - //Move residual samples from end of buffer to the beginning of the buffer - System.arraycopy(mBuffer, samples.length, mBuffer, 0, mCoefficientsLengthMinus2); - } - - //Copy new sample array into end of buffer - System.arraycopy(samples, 0, mBuffer, mCoefficientsLengthMinus2, samples.length); - - float[] filtered = new float[samples.length / 2]; - - for(mBufferPointer = 0; mBufferPointer < samples.length; mBufferPointer += 4) - { - mIAccumulator = 0.0f; - mQAccumulator = 0.0f; - - for(mCoefficientPointer = 0; mCoefficientPointer < mHalf; mCoefficientPointer += 4) - { - //Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by - //one of the coefficients to achieve the same effect. - mIAccumulator += mCoefficients[mCoefficientPointer] * - (mBuffer[mBufferPointer + mCoefficientPointer] + - mBuffer[mBufferPointer + (mCoefficientsLengthMinus2 - mCoefficientPointer)]); - - mQAccumulator += mCoefficients[mCoefficientPointer] * - (mBuffer[mBufferPointer + mCoefficientPointer + 1] + - mBuffer[mBufferPointer + (mCoefficientsLengthMinus2 - mCoefficientPointer) + 1]); - } - - mIAccumulator += mBuffer[mBufferPointer + mHalf] * CENTER_COEFFICIENT; - mQAccumulator += mBuffer[mBufferPointer + mHalf + 1] * CENTER_COEFFICIENT; - - filtered[mBufferPointer / 2] = mIAccumulator; - filtered[mBufferPointer / 2 + 1] = mQAccumulator; - } - - return filtered; - } - - /** - * Decimates the complex samples and returns a buffer of decimated samples. - * @param buffer to decimate - * @return decimated buffer. - */ - @Override - public ReusableComplexBuffer decimate(ReusableComplexBuffer buffer) - { - float[] decimated = decimateComplex(buffer.getSamples()); - buffer.decrementUserCount(); - return mReusableComplexBufferQueue.getBuffer(decimated, buffer.getTimestamp()); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter.java deleted file mode 100644 index b86f190f..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.halfband.complex; - -import io.github.dsheirer.dsp.filter.Filters; -import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter2; -import io.github.dsheirer.dsp.mixer.Oscillator; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; - -public class ComplexHalfBandFilter -{ - private ReusableComplexBufferQueue mBufferQueue = new ReusableComplexBufferQueue("ComplexHalfBandFilter"); - - private HalfBandFilter2 mIFilter; - private HalfBandFilter2 mQFilter; - private float mResidualISample; - private float mResidualQSample; - private boolean mHasResidual; - private int mOutputBufferLength; - private float[] mSamples; - private int mSamplesPointer; - private float[] mFilteredSamples; - private int mFilteredSamplesPointer; - - public ComplexHalfBandFilter(float[] coefficients) - { - mIFilter = new HalfBandFilter2(coefficients); - mQFilter = new HalfBandFilter2(coefficients); - } - - public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer) - { - mSamples = originalBuffer.getSamples(); - mSamplesPointer = 0; - - mOutputBufferLength = (mHasResidual ? (mSamples.length + 2) : mSamples.length) / 4 * 2; - - ReusableComplexBuffer filteredBuffer = mBufferQueue.getBuffer(mOutputBufferLength); - mFilteredSamples = filteredBuffer.getSamples(); - mFilteredSamplesPointer = 0; - - if(mHasResidual) - { - mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mResidualISample, mSamples[mSamplesPointer++]); - mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mResidualQSample, mSamples[mSamplesPointer++]); - mHasResidual = false; - } - - while(mSamplesPointer + 3 < mSamples.length) - { - mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer], mSamples[mSamplesPointer + 2]); - mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mSamples[mSamplesPointer + 1], mSamples[mSamplesPointer + 3]); - mSamplesPointer += 4; - } - - if(mSamplesPointer < mSamples.length) - { - mHasResidual = true; - mResidualISample = mSamples[mSamplesPointer++]; - mResidualQSample = mSamples[mSamplesPointer]; - } - - originalBuffer.decrementUserCount(); - return filteredBuffer; - } - - public static void main(String[] args) - { - Oscillator oscillator = new Oscillator(1, 16); - ComplexHalfBandFilter filter = new ComplexHalfBandFilter(Filters.HALF_BAND_FILTER_27T.getCoefficients()); - - ReusableComplexBufferQueue bufferQueue = new ReusableComplexBufferQueue("Test"); - - ReusableComplexBuffer complexBuffer = bufferQueue.getBuffer(8); - oscillator.generateComplex(complexBuffer); - ReusableComplexBuffer filteredBuffer = filter.filter(complexBuffer); - filteredBuffer.decrementUserCount(); - - ReusableComplexBuffer complexBuffer2 = bufferQueue.getBuffer(10); - oscillator.generateComplex(complexBuffer2); - ReusableComplexBuffer filteredBuffer2 = filter.filter(complexBuffer2); - filteredBuffer2.decrementUserCount(); - - ReusableComplexBuffer complexBuffer3 = bufferQueue.getBuffer(8); - oscillator.generateComplex(complexBuffer3); - ReusableComplexBuffer filteredBuffer3 = filter.filter(complexBuffer3); - filteredBuffer3.decrementUserCount(); - - - - - - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter.java deleted file mode 100644 index 6b1bf893..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 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.halfband.real; - -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import org.apache.commons.lang3.Validate; - -public class HalfBandFilter -{ - private float[] mBuffer; - private int mBufferSize = 1; //Temporary initial value - private int mBufferPointer = 0; - - private float mGain; - private float[] mCoefficients; - private int[][] mIndexMap; - - private int mCenterCoefficient; - private int mCenterCoefficientMapIndex; - - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("HalfBandFilter"); - - /** - * Half-Band filter for processing real float samples. - * - * Takes advantage of the 0-valued FIR half-band coefficents inherent - * in the half-band filter and does not calculate those coefficients. - * - * This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample. - * - * @param filter - filter coefficients - * @param gain - gain multiplier. Use 1.0 for unity/no gain - */ - public HalfBandFilter(float[] coefficients, float gain) - { - //TODO: update this to use SIMD structures with 2 float arrays and a toggle operation - - - mCoefficients = coefficients; - mGain = gain; - - mBufferSize = mCoefficients.length; - mBuffer = new float[mBufferSize]; - - generateIndexMap(mCoefficients.length); - } - - public HalfBandFilter(float[] coefficients, float gain, boolean decimate) - { - this(coefficients, gain); - - throw new IllegalStateException("This filter must be updated to support non-decimate option"); - } - - public void dispose() - { - } - - /** - * Inserts the sample into the buffer without calculating a filtered value - */ - public void insert(float sample) - { - mBuffer[mBufferPointer] = sample; - - mBufferPointer++; - - mBufferPointer = mBufferPointer % mBufferSize; - } - - public ReusableFloatBuffer filter(ReusableFloatBuffer input) - { - float[] inputSamples = input.getSamples(); - ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length); - float[] outputSamples = output.getSamples(); - - for(int x = 0; x < inputSamples.length; x++) - { - outputSamples[x] = filter(inputSamples[x]); - } - - input.decrementUserCount(); - - return output; - } - - /** - * Calculate the filtered value by applying the coefficients against the samples in mBuffer - */ - public float filter(float sample) - { - insert(sample); - - float accumulator = 0; - - //Start with the center tap value - accumulator += mCoefficients[mCenterCoefficient] * mBuffer[mIndexMap[mBufferPointer][mCenterCoefficientMapIndex]]; - - //For the remaining coefficients, add the symmetric samples, oldest and newest - //first, then multiply by the single coefficient - for(int x = 0; x < mCenterCoefficientMapIndex; x += 2) - { - accumulator += mCoefficients[x] * (mBuffer[mIndexMap[mBufferPointer][x]] + mBuffer[mIndexMap[mBufferPointer][x + 1]]); - } - - return accumulator * mGain; - } - - /** - * Creates an n X (n + 1 / 2) index map enabling quick access to the - * circular buffer samples. - * - * As the buffer shifts right with each subsequent sample, we have to move - * the index pointers with it, for efficient access of the samples. - * - * The first array index value in the index map corresponds to the current - * buffer pointer location. - * - * The second array index value points to the samples that should be - * multiplied by the coefficients as follows: - * - * 0 = center tap sample, to be multiplied by center coefficient - * - * 0 = sample( 1 ) - * 1 = sample( size - 1 ) - * - * Indexes 0 and 1 will be multiplied by coefficient( 0 ). - * - * Subsequent indexes 3, 4, etc, point to the oldest and newest samples that - * correspond to the matching ( 3 ) coefficient index. - * - * @param odd-sized number of filter taps (ie coefficients) and buffer - */ - private void generateIndexMap(int size) - { - //Ensure we have an odd size - Validate.isTrue(size % 2 == 1); - - int mapWidth = ((size + 1) / 2) + 1; - - //Set center tap index for coefficients array - mCenterCoefficient = (size - 1) / 2; - mCenterCoefficientMapIndex = mCenterCoefficient + 1; - - mIndexMap = new int[size][mapWidth]; - - //Setup the first row, buffer pointer index 0, as a starting point - for(int x = 0; x < mapWidth - 2; x += 2) - { - mIndexMap[0][x] = x; - mIndexMap[0][x + 1] = size - 1 - x; - } - - //Place center tap index in last element - mIndexMap[0][mCenterCoefficientMapIndex] = mCenterCoefficient; - - //For each subsequent row, increment the previous row's value by 1, - //subtracting size as needed, to keep the values between 0 and size - 1 - for(int x = 1; x < size; x++) - { - for(int y = 0; y < mapWidth; y++) - { - mIndexMap[x][y] = mIndexMap[x - 1][y] + 1; - - if(mIndexMap[x][y] >= size) - { - mIndexMap[x][y] -= size; - } - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java deleted file mode 100644 index ddfd5724..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.halfband.real; - -import io.github.dsheirer.buffer.FloatCircularBuffer; -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; - -import java.util.Random; - -/** - * Implements a half-band filter that produces one filtered output for every two input samples. - * - * This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when - * supported by the host CPU. - */ -public class HalfBandFilter2 -{ - private static final float CENTER_COEFFICIENT = 0.5f; - private float[] mCoefficients; - private float[] mEvenSamples; - private float mAccumulator; - private int mPointer; - private FloatCircularBuffer mCircularBuffer; - - /** - * Creates a half band filter with inherent decimation by two. - * - * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are - * zero valued except for the middle odd index coefficient which should be valued 0.5 - */ - public HalfBandFilter2(float[] coefficients) - { - if((coefficients.length + 1) % 4 != 0) - { - throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]"); - } - - int half = coefficients.length / 2 + 1; - - mCircularBuffer = new FloatCircularBuffer(half / 2); - mCoefficients = new float[half]; - mEvenSamples = new float[half]; - - //Use only the even coefficients since the odd coefficients are all zero-valued - for(int x = 0; x < coefficients.length; x += 2) - { - mCoefficients[x / 2] = coefficients[x]; - } - } - - public float filter(float sample1, float sample2) - { - //Structured for SIMD array copy optimization when supported by host CPU - System.arraycopy(mEvenSamples, 0, mEvenSamples, 1, mEvenSamples.length - 1); - - mEvenSamples[0] = sample1; - - mAccumulator = 0.0f; - - //Structured for SIMD dot.product optimization when supported by host CPU - for(mPointer = 0; mPointer < mEvenSamples.length; mPointer++) - { - mAccumulator += (mEvenSamples[mPointer] * mCoefficients[mPointer]); - } - - mAccumulator += (mCircularBuffer.getAndPut(sample2) * CENTER_COEFFICIENT); - - return mAccumulator; - } - - public float[] filter(float[] samples) - { - float[] filtered = new float[samples.length / 2]; - for(int x = 0; x < samples.length; x += 2) - { - filtered[x / 2] = filter(samples[x], samples[x + 1]); - } - - return filtered; - } - - public static void main(String[] args) - { - System.out.println("Starting ..."); - float[] taps = FilterFactory.getHalfBand(99, Window.WindowType.HAMMING); - - HalfBandFilter2 halfband = new HalfBandFilter2(taps); - - Random random = new Random(); - float[] samples = new float[1024]; - float[] filtered = new float[512]; - - for(int x = 0; x < samples.length; x++) - { - samples[x] = (2.0f * random.nextFloat()) - 1.0f; - } - - System.out.println("Testing ..."); - long current = System.currentTimeMillis(); - - int iterations = 10_000_000; - - for(int x = 0; x < iterations; x++) - { - for(int y = 0; y < samples.length; y += 2) - { - filtered[y / 2] = halfband.filter(samples[y], samples[y + 1]); - } - } - - long elapsed = System.currentTimeMillis() - current; - - System.out.println("Elapsed: " + (elapsed / 1000d) + " seconds"); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransform.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransform.java index 32de3ee0..aacd5870 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransform.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransform.java @@ -1,246 +1,122 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.filter.hilbert; -import io.github.dsheirer.dsp.filter.Filters; +import io.github.dsheirer.sample.complex.ComplexSamples; import org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.util.Arrays; - -public class HilbertTransform +/** + * Base Hilbert transform class. + */ +public abstract class HilbertTransform { - private static final Logger mLog = LoggerFactory.getLogger(HilbertTransform.class); - - private float[] mHilbertFilter; - private float[] mBuffer; - private int mBufferSize; - private int mBufferPointer; - private int[][] mIndexMap; - private int mMapHeight; - private int mCenterTapIndex; - private float mCenterCoefficent; - - private boolean mInvertFlag = false; - - /** - * Hilbert transform filter used for converting real-valued samples into - * complex valued samples using frequency translation (FS/4) and a half-band - * filter. + /* Half-band filter coefficients retrieved October 2015 from: + * https://github.com/airspy/host/libairspy/src/filters.h * - * This filter uses a circular sample buffer and a pre-calculated - * index map to correctly map the filter coefficients to the circular delay - * buffer contents as each new sample is added to the buffer and processed. + * Copyright (C) 2014, Youssef Touil * - * Half-band filter coefficients used in this filter must be of length N - * where (N + 1) is a multiple of 4. This filter uses a pre-defined half - * band filter with 47 coefficients. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * This transform process is described in Understanding Digital Signal - * Processing, Lyons, 3e, 2011, sections 13.1.2 and 13.1.3 (p 674-678) and - * implemented as described in Section 13.37.1 and 13.37.2 (p 802-804) - */ + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + public static final float[] HALF_BAND_FILTER_47_TAP = new float[] + { + -0.000998606272947510f, 0.0f, 0.001695637278417295f, 0.0f, + -0.003054430179754289f, 0.0f, 0.005055504379767936f, 0.0f, + -0.007901319195893647f, 0.0f, 0.011873357051047719f, 0.0f, + -0.017411159379930066f, 0.0f, 0.025304817427568772f, 0.0f, + -0.037225225204559217f, 0.0f, 0.057533286997004301f, 0.0f, + -0.102327462004259350f, 0.0f, 0.317034472508947400f, 0.5f, + 0.317034472508947400f, 0.0f, -0.102327462004259350f, 0.0f, + 0.057533286997004301f, 0.0f, -0.037225225204559217f, 0.0f, + 0.025304817427568772f, 0.0f, -0.017411159379930066f, 0.0f, + 0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f, + 0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f, + 0.001695637278417295f, 0.0f, -0.000998606272947510f + }; + + protected float[] mCoefficients; + protected int mIOverlap = 11; + protected int mQOverlap = 23; + protected float[] mIBuffer = new float[mIOverlap]; + protected float[] mQBuffer = new float[mQOverlap]; + public HilbertTransform() { - convertHalfBandToHilbert(Filters.HALF_BAND_FILTER_47T.getCoefficients()); - - mBufferSize = mHilbertFilter.length + 1; - mBuffer = new float[mBufferSize]; - - mCenterCoefficent = mHilbertFilter[mHilbertFilter.length / 2]; - - generateIndexMap(mHilbertFilter.length); + mCoefficients = convertHalfBandToHilbert(HALF_BAND_FILTER_47_TAP); } /** - * Filters the continuous real-sample stream by treating the stream as a - * set of interleaved values and applying two hilbert filters against each - * interleaved set (I & Q). Frequency translation has already been - * pre-applied to the hilbert filter coefficients. - * - * Filtering of the inphase component is a simple multiply by the center - * coefficient (0.5) since all of the other coefficients are zero-valued. - * - * Filtering of the quadrature component uses a folded FIR structure and - * expects the hilbert coefficients to be symmetric with all negative valued - * coefficients below the center coefficient and all positive-valued coefficients - * above the center coefficient. So, convolution only applies the filter - * coefficient against the difference of the two samples. - * - * ( coefficient * value1 ) + (-coefficient * value2 ) = coefficient * ( value1 - value2 ) - * - * Performs frequency translation by FS/2 on the final filtered values by - * applying a sequence of 1,-1 (sign change) to each I/Q output sample. + * Converts the real sample array to complex samples as half the sample rate + * @param samples to convert + * @return converted samples */ - public float[] filter(float[] samples) - { - float accumulator; - - for(int y = 0; y < samples.length; y += 2) - { - insert(samples[y]); - insert(samples[y + 1]); - - accumulator = 0.0f; - - int index = mBufferPointer / 2; - - for(int x = 0; x < mHilbertFilter.length / 2; x += 2) - { - accumulator += mHilbertFilter[x] * - (mBuffer[mIndexMap[index][x + 1]] - - mBuffer[mIndexMap[index][x]]); - } - - //Perform FS/2 frequency translation on the final filtered values - if(mInvertFlag) - { - //inphase - samples[y] = -(mBuffer[mIndexMap[index][mCenterTapIndex]]); - - //quadrature - samples[y + 1] = -accumulator; - } - else - { - //inphase - samples[y] = mBuffer[mIndexMap[index][mCenterTapIndex]]; - - //quadrature - samples[y + 1] = accumulator; - } - - mInvertFlag = !mInvertFlag; - } - - return samples; - } + public abstract ComplexSamples filter(float[] samples); /** - * Inserts the sample into the circular buffer, overwriting the oldest value + * Converts the half-band filter coefficients for use as hilbert transform filter coefficients. Sets all + * even-numbered coefficients left of center tap to negative and all even-numbered coefficients right of + * center tap to positive and sets the center tap from 0.5 to 0.0, resulting in an overall 2x reduction + * in gain. We throw away the odd-number coefficients because they are 0.0 or 0.5 (center). + * + * Since we need the filter to be reversed for implementation and since the filter is symmetrical, we + * flip it during assignment and make the left of center positive and the right of center negative. + * + * Note: we apply a 2.0 gain to the coefficients to compensate for the loss, or zero-ing of the original + * 0.5 center coefficient. + * + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, sections 13.1.2 and + * 13.1.3 (p 674-678) and implemented as described in Section 13.37.1 and 13.37.2 (p 802-804) */ - private void insert(float sample) + public static float[] convertHalfBandToHilbert(float[] coefficients) { - mBuffer[mBufferPointer++] = sample; - - mBufferPointer = mBufferPointer % mBufferSize; - } - - /** - * Creates an index map to support efficient lookup of sample indexes from - * the circular buffer. - * - * The first index of the map corresponds to the current buffer pointer - * setting. - * - * The second index is structured in sample pairs to align with - * the hilbert filter coefficients. For example, the first iteration of - * the filter would use filter coefficient 0 and the index map values 0 and - * 1 would be pointing to samples 46 and 0. - * - * The final element of each index set is the index of the center sample. - */ - private void generateIndexMap(int size) - { - mMapHeight = size / 2 + 1; - int mapWidth = mMapHeight + 1; - - mIndexMap = new int[mMapHeight][mapWidth]; - - //Setup the first row with buffer pointer index 0 as a starting point - for(int x = 0; x < mapWidth - 1; x += 2) - { - mIndexMap[0][x] = size - 1 - x; - mIndexMap[0][x + 1] = x; - } - - //Place the center index at the end of the array - mCenterTapIndex = mapWidth - 1; - - mIndexMap[0][mCenterTapIndex] = size / 2; - - //For each subsequent row, increment the previous row's value by 2, - //wrapping as needed, to keep the values between 0 and size - 1 - for(int x = 1; x < mMapHeight; x++) - { - for(int y = 0; y < mapWidth; y++) - { - mIndexMap[x][y] = mIndexMap[x - 1][y] + 2; - - if(mIndexMap[x][y] >= size) - { - mIndexMap[x][y] -= size + 1; - - //Special handling for center tap wrap around - if(y == mCenterTapIndex && mIndexMap[x][y] < 0) - { - mIndexMap[x][y] = size; - } - } - } - } - } - - /** - * Logs the index map contents - */ - public void logIndexMap() - { - for(int[] indexSet : mIndexMap) - { - mLog.debug("Row:" + Arrays.toString(indexSet)); - } - } - - /** - * Converts the half-band filter coefficients for use as hilbert transform - * filter coefficients. Sets all even-numbered coefficients left of center - * to negative and all even-numbered coefficients right of center to positive - * which applies a FS/4 frequency translation to the coefficients. - * - * Note: even though the coefficients above the center coefficient are being - * defined, they are unused during the convolution process since we're using - * a folded FIR structure to exploit the symmetric nature of the half-band - * filter coefficients. The polarity difference between the upper and lower - * halves is accounted for in the filter() method. - * - * Note: we apply a 2.0 gain to the coefficients to compensate for the loss - * of splitting the signal via two filters. - */ - private void convertHalfBandToHilbert(float[] coefficients) - { - mHilbertFilter = new float[coefficients.length]; + float[] hilbert = new float[coefficients.length / 2 + 1]; int middle = coefficients.length / 2; - for(int x = 0; x < coefficients.length; x++) + for(int x = 0; x < coefficients.length; x += 2) { if(x < middle) { - mHilbertFilter[x] = 2.0f * -FastMath.abs(coefficients[x]); + hilbert[x / 2] = -2.0f * FastMath.abs(coefficients[x]); } else if(x > middle) { - mHilbertFilter[x] = 2.0f * FastMath.abs(coefficients[x]); - } - else - { - mHilbertFilter[x] = 2.0f * coefficients[x]; + hilbert[x / 2] = 2.0f * FastMath.abs(coefficients[x]); } } + + return hilbert; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransformFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransformFactory.java new file mode 100644 index 00000000..153464e8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransformFactory.java @@ -0,0 +1,54 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.hilbert; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; + +/** + * Factory class for creating an optimal implementation of a Hilbert Transform as determined by the + * Calibration manager. + */ +public class HilbertTransformFactory +{ + public static HilbertTransform getHilbertTransform() + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.HILBERT_TRANSFORM); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorHilbertTransformDefaultBits(); + case VECTOR_SIMD_64: + return new VectorHilbertTransform64Bits(); + case VECTOR_SIMD_128: + return new VectorHilbertTransform128Bits(); + case VECTOR_SIMD_256: + return new VectorHilbertTransform256Bits(); + case VECTOR_SIMD_512: + return new VectorHilbertTransform512Bits(); + case SCALAR: + case UNCALIBRATED: + default: + return new ScalarHilbertTransform(); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/LegacyHilbertTransform.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/LegacyHilbertTransform.java new file mode 100644 index 00000000..dcaa0660 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/LegacyHilbertTransform.java @@ -0,0 +1,248 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.hilbert; + +import org.apache.commons.math3.util.FastMath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +public class LegacyHilbertTransform +{ + private static final Logger mLog = LoggerFactory.getLogger(LegacyHilbertTransform.class); + + private float[] mHilbertFilter; + private float[] mBuffer; + private int mBufferSize; + private int mBufferPointer; + private int[][] mIndexMap; + private int mMapHeight; + private int mCenterTapIndex; + private float mCenterCoefficent; + + private boolean mInvertFlag = false; + + /** + * Hilbert transform filter used for converting real-valued samples into + * complex valued samples using frequency translation (FS/4) and a half-band + * filter. + * + * This filter uses a circular sample buffer and a pre-calculated + * index map to correctly map the filter coefficients to the circular delay + * buffer contents as each new sample is added to the buffer and processed. + * + * Half-band filter coefficients used in this filter must be of length N + * where (N + 1) is a multiple of 4. This filter uses a pre-defined half + * band filter with 47 coefficients. + * + * This transform process is described in Understanding Digital Signal + * Processing, Lyons, 3e, 2011, sections 13.1.2 and 13.1.3 (p 674-678) and + * implemented as described in Section 13.37.1 and 13.37.2 (p 802-804) + */ + public LegacyHilbertTransform() + { + convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP); + + mBufferSize = mHilbertFilter.length + 1; + mBuffer = new float[mBufferSize]; + + mCenterCoefficent = mHilbertFilter[mHilbertFilter.length / 2]; + + generateIndexMap(mHilbertFilter.length); + } + + /** + * Filters the continuous real-sample stream by treating the stream as a + * set of interleaved values and applying two hilbert filters against each + * interleaved set (I & Q). Frequency translation has already been + * pre-applied to the hilbert filter coefficients. + * + * Filtering of the inphase component is a simple multiply by the center + * coefficient (0.5) since all of the other coefficients are zero-valued. + * + * Filtering of the quadrature component uses a folded FIR structure and + * expects the hilbert coefficients to be symmetric with all negative valued + * coefficients below the center coefficient and all positive-valued coefficients + * above the center coefficient. So, convolution only applies the filter + * coefficient against the difference of the two samples. + * + * ( coefficient * value1 ) + (-coefficient * value2 ) = coefficient * ( value1 - value2 ) + * + * Performs frequency translation by FS/2 on the final filtered values by + * applying a sequence of 1,-1 (sign change) to each I/Q output sample. + */ + public float[] filter(float[] samples) + { + float accumulator; + + for(int y = 0; y < samples.length; y += 2) + { + insert(samples[y]); + insert(samples[y + 1]); + + accumulator = 0.0f; + + int index = mBufferPointer / 2; + + for(int x = 0; x < mHilbertFilter.length / 2; x += 2) + { + accumulator += mHilbertFilter[x] * + (mBuffer[mIndexMap[index][x + 1]] - + mBuffer[mIndexMap[index][x]]); + } + + //Perform FS/2 frequency translation on the final filtered values + if(mInvertFlag) + { + //inphase + samples[y] = -(mBuffer[mIndexMap[index][mCenterTapIndex]]); + + //quadrature + samples[y + 1] = -accumulator; + } + else + { + //inphase + samples[y] = mBuffer[mIndexMap[index][mCenterTapIndex]]; + + //quadrature + samples[y + 1] = accumulator; + } + + mInvertFlag = !mInvertFlag; + } + + return samples; + } + + /** + * Inserts the sample into the circular buffer, overwriting the oldest value + */ + private void insert(float sample) + { + mBuffer[mBufferPointer++] = sample; + + mBufferPointer = mBufferPointer % mBufferSize; + } + + /** + * Creates an index map to support efficient lookup of sample indexes from + * the circular buffer. + * + * The first index of the map corresponds to the current buffer pointer + * setting. + * + * The second index is structured in sample pairs to align with + * the hilbert filter coefficients. For example, the first iteration of + * the filter would use filter coefficient 0 and the index map values 0 and + * 1 would be pointing to samples 46 and 0. + * + * The final element of each index set is the index of the center sample. + */ + private void generateIndexMap(int size) + { + mMapHeight = size / 2 + 1; + int mapWidth = mMapHeight + 1; + + mIndexMap = new int[mMapHeight][mapWidth]; + + //Setup the first row with buffer pointer index 0 as a starting point + for(int x = 0; x < mapWidth - 1; x += 2) + { + mIndexMap[0][x] = size - 1 - x; + mIndexMap[0][x + 1] = x; + } + + //Place the center index at the end of the array + mCenterTapIndex = mapWidth - 1; + + mIndexMap[0][mCenterTapIndex] = size / 2; + + //For each subsequent row, increment the previous row's value by 2, + //wrapping as needed, to keep the values between 0 and size - 1 + for(int x = 1; x < mMapHeight; x++) + { + for(int y = 0; y < mapWidth; y++) + { + mIndexMap[x][y] = mIndexMap[x - 1][y] + 2; + + if(mIndexMap[x][y] >= size) + { + mIndexMap[x][y] -= size + 1; + + //Special handling for center tap wrap around + if(y == mCenterTapIndex && mIndexMap[x][y] < 0) + { + mIndexMap[x][y] = size; + } + } + } + } + } + + /** + * Logs the index map contents + */ + public void logIndexMap() + { + for(int[] indexSet : mIndexMap) + { + mLog.debug("Row:" + Arrays.toString(indexSet)); + } + } + + /** + * Converts the half-band filter coefficients for use as hilbert transform + * filter coefficients. Sets all even-numbered coefficients left of center + * to negative and all even-numbered coefficients right of center to positive + * which applies a FS/4 frequency translation to the coefficients. + * + * Note: even though the coefficients above the center coefficient are being + * defined, they are unused during the convolution process since we're using + * a folded FIR structure to exploit the symmetric nature of the half-band + * filter coefficients. The polarity difference between the upper and lower + * halves is accounted for in the filter() method. + * + * Note: we apply a 2.0 gain to the coefficients to compensate for the loss + * of splitting the signal via two filters. + */ + private void convertHalfBandToHilbert(float[] coefficients) + { + mHilbertFilter = new float[coefficients.length]; + + int middle = coefficients.length / 2; + + for(int x = 0; x < coefficients.length; x++) + { + if(x < middle) + { + mHilbertFilter[x] = 2.0f * -FastMath.abs(coefficients[x]); + } + else if(x > middle) + { + mHilbertFilter[x] = 2.0f * FastMath.abs(coefficients[x]); + } + else + { + mHilbertFilter[x] = 2.0f * coefficients[x]; + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/ScalarHilbertTransform.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/ScalarHilbertTransform.java new file mode 100644 index 00000000..3c66beeb --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/ScalarHilbertTransform.java @@ -0,0 +1,137 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.hilbert; + +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; + +import java.util.Arrays; + +/** + * Implements a Hilbert transform using scalar operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class ScalarHilbertTransform extends HilbertTransform +{ + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + float accumulator; + + for(int x = 0; x < bufferLength; x++) + { + accumulator = 0.0f; + + for(int y = 0; y < mCoefficients.length; y++) + { + accumulator += mCoefficients[y] * mQBuffer[x + y]; + } + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator; + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } + + public static void main(String[] args) + { + int iterations = 1_000_000; + int bufferSize = 2048; + + IRealOscillator realOscillator = OscillatorFactory.getRealOscillator(2, 40); + float[] realSamples = realOscillator.generate(bufferSize); + ScalarHilbertTransform hilbertTransform = new ScalarHilbertTransform(); + VectorHilbertTransformDefaultBits vectorDefault = new VectorHilbertTransformDefaultBits(); + VectorHilbertTransform512Bits vector512 = new VectorHilbertTransform512Bits(); + VectorHilbertTransform256Bits vector256 = new VectorHilbertTransform256Bits(); + VectorHilbertTransform128Bits vector128 = new VectorHilbertTransform128Bits(); + VectorHilbertTransform64Bits vector64 = new VectorHilbertTransform64Bits(); + + System.out.println("Starting ..."); + + float accumulator = 0.0f; + long start = System.currentTimeMillis(); + + boolean validate = true; + + if(validate) + { + ComplexSamples scalar = hilbertTransform.filter(realSamples); + ComplexSamples vector = vector512.filter(realSamples); + + System.out.println("SI: " + Arrays.toString(scalar.i())); + System.out.println("VI: " + Arrays.toString(vector.i())); + + System.out.println("SQ: " + Arrays.toString(scalar.q())); + System.out.println("VQ: " + Arrays.toString(vector.q())); + } + else + { + for(int count = 0; count < iterations; count++) + { +// ComplexSamples filtered = hilbertTransform.filter(realSamples); +// ComplexSamples filtered = vectorDefault.filter(realSamples); + ComplexSamples filtered = vector512.filter(realSamples); +// ComplexSamples filtered = vector256.filter(realSamples); +// ComplexSamples filtered = vector128.filter(realSamples); +// ComplexSamples filtered = vector64.filter(realSamples); + + accumulator += filtered.q()[2]; + } + + System.out.println("Accumulator: " + accumulator); + System.out.println("Elapsed: " + ((System.currentTimeMillis() - start) / 1000.0f) + " seconds"); + } + + System.out.println("Finished!"); + + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform128Bits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform128Bits.java new file mode 100644 index 00000000..3aa34efd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform128Bits.java @@ -0,0 +1,96 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransform128Bits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator; + + //There are 24 filter taps + FloatVector filterA = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filterB = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filterC = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filterD = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filterE = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + FloatVector filterF = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20); + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = filterA.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = filterB.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); + accumulator = filterC.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = filterD.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); + accumulator = filterE.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + accumulator = filterF.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform256Bits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform256Bits.java new file mode 100644 index 00000000..782a53cd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform256Bits.java @@ -0,0 +1,90 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransform256Bits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator; + + //There are 24 filter taps + FloatVector filterA = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filterB = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filterC = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = filterA.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = filterB.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = filterC.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform512Bits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform512Bits.java new file mode 100644 index 00000000..240a0121 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform512Bits.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +import java.util.Arrays; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransform512Bits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + + public VectorHilbertTransform512Bits() + { + super(); + + //Expand 24-tap filter array to 32 with zero-padding to align with AVX512 lane width. + mCoefficients = Arrays.copyOf(mCoefficients, 32); + //Move the last 8 taps to the end of the array to align for processing + System.arraycopy(mCoefficients, 16, mCoefficients, 24, 8); + for(int x = 16; x < 24; x++) + { + mCoefficients[x] = 0.0f; + } + } + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator; + + //There are 24 filter taps - 16 in first vector and 8 taps in second, aligned to the right side + //A: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + //B: - - - - - - - - - 16 17 18 19 20 21 22 23 + //We overlap the samples loading so that we don't overshoot the end of the buffer. Since the + //first 8 filter taps are zero, they don't add anything to the dot.product result. + FloatVector filterA = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filterB = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = filterA.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = filterB.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform64Bits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform64Bits.java new file mode 100644 index 00000000..e242ae85 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform64Bits.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransform64Bits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator; + + //There are 24 filter taps + FloatVector filterA = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filterB = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2); + FloatVector filterC = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filterD = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6); + FloatVector filterE = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filterF = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10); + FloatVector filterG = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filterH = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 14); + FloatVector filterI = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + FloatVector filterJ = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 18); + FloatVector filterK = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20); + FloatVector filterL = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 22); + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = filterA.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = filterB.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator); + accumulator = filterC.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); + accumulator = filterD.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator); + accumulator = filterE.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = filterF.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator); + accumulator = filterG.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); + accumulator = filterH.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator); + accumulator = filterI.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + accumulator = filterJ.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator); + accumulator = filterK.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); + accumulator = filterL.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator); + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransformDefaultBits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransformDefaultBits.java new file mode 100644 index 00000000..138ecb00 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransformDefaultBits.java @@ -0,0 +1,89 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransformDefaultBits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator, filter, samples; + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int y = 0; y < mCoefficients.length; y += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, y); + samples = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + y); + accumulator = filter.fma(samples, accumulator); + } + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/iir/SinglePoleIirFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/iir/SinglePoleIirFilter.java index c6d1ad97..a7557113 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/iir/SinglePoleIirFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/iir/SinglePoleIirFilter.java @@ -8,23 +8,23 @@ public class SinglePoleIirFilter /** * Decay value, in range: 0 <-> 1.0 */ - private double mAlpha; + private float mAlpha; /** * 1.0 - alpha */ - private double mOneMinusAlpha; + private float mOneMinusAlpha; /** * Current filter output value. */ - private double mOutput; + private float mOutput; /** * Constructs a single pole IIR filter * @param alpha decay value in range (0.0 - 1.0) */ - public SinglePoleIirFilter(double alpha) + public SinglePoleIirFilter(float alpha) { if(alpha < 0.0 || alpha > 1.0) { @@ -32,7 +32,7 @@ public class SinglePoleIirFilter } mAlpha = alpha; - mOneMinusAlpha = 1.0 - alpha; + mOneMinusAlpha = 1.0f - alpha; } /** @@ -40,7 +40,7 @@ public class SinglePoleIirFilter * @param sample to process/filter * @return filtered output */ - public double filter(double sample) + public double filter(float sample) { mOutput = (mOutput * mOneMinusAlpha) + (mAlpha * sample); return mOutput; 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 949ebea7..5a5f1771 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 @@ -1,98 +1,107 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.filter.resample; import com.laszlosystems.libresample4j.Resampler; import com.laszlosystems.libresample4j.SampleBuffers; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.FloatBuffer; +/** + * Real sample buffer resampler + */ 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 Listener mResampledListener; private BufferManager mBufferManager; private double mResampleFactor; /** - * Resampler for real sample buffers. - * @param inputRate - * @param outputRate + * Constructs an instance. + * @param inputRate sample rate + * @param outputRate sample rate + * @param bufferSize for the input and output float buffers sized large enough to hold at least two of the + * input or output buffer lengths, whichever is larger + * @param outputArrayLength to create uniform length output arrays */ - public RealResampler(double inputRate, double outputRate, int inputBufferSize, int outputBufferSize) + public RealResampler(double inputRate, double outputRate, int bufferSize, int outputArrayLength) { mResampleFactor = outputRate / inputRate; mResampler = new Resampler(true, mResampleFactor, mResampleFactor); - mBufferManager = new BufferManager(inputBufferSize, outputBufferSize); + mBufferManager = new BufferManager(bufferSize, outputArrayLength); } /** * Primary input method to the resampler - * @param reusableFloatBuffer to resample + * @param samples to resample */ - public void resample(ReusableFloatBuffer reusableFloatBuffer) + public void resample(float[] samples) { - mBufferManager.load(reusableFloatBuffer); + mBufferManager.load(samples); mResampler.process(mResampleFactor, mBufferManager, false); } /** - * Registers the listener to receive the resampled buffer output - * @param resampledBufferListener to receive buffers + * Registers the listener to receive the resampled output buffers + * @param listener to receive buffers */ - public void setListener(Listener resampledBufferListener) + public void setListener(Listener listener) { - mResampledListener = resampledBufferListener; + mResampledListener = listener; } /** - * Buffer manager for input and output buffers used during the resampling process. The manager will - * auto-resize the input and output buffers to meet the size of the incoming sample buffers. + * Buffer manager for input and output buffers used during the resampling process. */ public class BufferManager implements SampleBuffers { - private int mOutputBufferSize; + private int mOutputArrayLength; private FloatBuffer mInputBuffer; private FloatBuffer mOutputBuffer; - public BufferManager(int inputBufferSize, int outputBufferSize) + /** + * Constructs an instance + * @param bufferSize for the input and output float buffers + * @param outputArrayLength to create uniform length output sample arrays + */ + public BufferManager(int bufferSize, int outputArrayLength) { - mOutputBufferSize = outputBufferSize; + mOutputArrayLength = outputArrayLength; //The size of the underlying byte buffer in bytes is 4 times the requested input buffer size in floats - mInputBuffer = ByteBuffer.allocate(inputBufferSize * 4).asFloatBuffer(); - mOutputBuffer = ByteBuffer.allocate(inputBufferSize * 4).asFloatBuffer(); + mInputBuffer = ByteBuffer.allocate(bufferSize * 4).asFloatBuffer(); + mOutputBuffer = ByteBuffer.allocate(bufferSize * 4).asFloatBuffer(); } /** - * Queues the buffer sample for resampling + * Queues the buffer samples for resampling */ - public void load(ReusableFloatBuffer reusableFloatBuffer) + public void load(float[] samples) { - mInputBuffer.put(reusableFloatBuffer.getSamples()); - reusableFloatBuffer.decrementUserCount(); + mInputBuffer.put(samples); } @Override @@ -126,21 +135,17 @@ public class RealResampler { mOutputBuffer.put(samples, offset, length); - while(mOutputBuffer.position() > mOutputBufferSize) + while(mOutputBuffer.position() > mOutputArrayLength) { - ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize); + float[] resampled = new float[mOutputArrayLength]; mOutputBuffer.flip(); - mOutputBuffer.get(outputBuffer.getSamples()); + mOutputBuffer.get(resampled); mOutputBuffer.compact(); if(mResampledListener != null) { - mResampledListener.receive(outputBuffer); - } - else - { - outputBuffer.decrementUserCount(); + mResampledListener.receive(resampled); } } } diff --git a/src/main/java/io/github/dsheirer/dsp/fm/FmDemodulatorFactory.java b/src/main/java/io/github/dsheirer/dsp/fm/FmDemodulatorFactory.java new file mode 100644 index 00000000..f57780c3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/FmDemodulatorFactory.java @@ -0,0 +1,76 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory for creating FM demodulators + */ +public class FmDemodulatorFactory +{ + private static final Logger mLog = LoggerFactory.getLogger(FmDemodulatorFactory.class); + + /** + * Creates the optimal FM demodulator using calibration data to select the optimal + * implementation from scalar and vector options. + * @return demodulator instance + */ + public static IFmDemodulator getFmDemodulator() + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorFMDemodulator(); + case SCALAR: + return new ScalarFMDemodulator(); + default: + mLog.warn("Unrecognized optimal operation for FM demodulator: " + implementation.name()); + return new ScalarFMDemodulator(); + } + } + + /** + * Creates the optimal Squelching FM demodulator using calibration data to select the optimal + * implementation from scalar and vector options. + * @return demodulator instance + */ + public static ISquelchingFmDemodulator getSquelchingFmDemodulator(float alpha, float threshold, int ramp) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorSquelchingFMDemodulator(alpha, threshold, ramp); + case SCALAR: + return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp); + default: + mLog.warn("Unrecognized optimal operation for Squelching FM demodulator: " + implementation.name()); + return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/fm/IFmDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/IFmDemodulator.java new file mode 100644 index 00000000..a1fec0e7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/IFmDemodulator.java @@ -0,0 +1,14 @@ +package io.github.dsheirer.dsp.fm; + +/** + * Interface for FM demodulator + */ +public interface IFmDemodulator +{ + /** + * Demodulate the complex sample array and return an array of real audio samples. + * @param samples to demodulate + * @return demodulated samples. + */ + float[] demodulate(float[] i, float[] q); +} diff --git a/src/main/java/io/github/dsheirer/dsp/fm/ISquelchingFmDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/ISquelchingFmDemodulator.java new file mode 100644 index 00000000..fb6c9896 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/ISquelchingFmDemodulator.java @@ -0,0 +1,42 @@ +package io.github.dsheirer.dsp.fm; + +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.SourceEvent; + +/** + * Interface for FM demodulator that provides squelch control. + */ +public interface ISquelchingFmDemodulator extends IFmDemodulator +{ + /** + * Indicates if the squelch state has changed. + */ + boolean isSquelchChanged(); + + /** + * Indicates if the squelch state is currently muted + */ + boolean isMuted(); + + /** + * Sets the threshold to use for power squelch + * @param threshold in decibels + */ + void setSquelchThreshold(double threshold); + + /** + * Resets the demodulator + */ + void reset(); + + /** + * Registers the listener to receive notifications of squelch change events from the power squelch. + */ + void setSourceEventListener(Listener listener); + + /** + * Receives a source event + */ + void receive(SourceEvent sourceEvent); +} + diff --git a/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/ScalarFMDemodulator.java similarity index 63% rename from src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java rename to src/main/java/io/github/dsheirer/dsp/fm/ScalarFMDemodulator.java index 34421994..8491ec4e 100644 --- a/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/fm/ScalarFMDemodulator.java @@ -1,34 +1,31 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.fm; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import io.github.dsheirer.sample.complex.Complex; import org.apache.commons.math3.util.FastMath; /** * FM Demodulator for demodulating complex samples and producing demodulated floating point samples. */ -public class FMDemodulator +public class ScalarFMDemodulator implements IFmDemodulator { - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("FMDemodulator"); private float mPreviousI = 0.0f; private float mPreviousQ = 0.0f; protected float mGain; @@ -36,7 +33,7 @@ public class FMDemodulator /** * Creates an FM demodulator instance with a default gain of 1.0. */ - public FMDemodulator() + public ScalarFMDemodulator() { this(1.0f); } @@ -45,7 +42,7 @@ public class FMDemodulator * 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) + public ScalarFMDemodulator(float gain) { mGain = gain; } @@ -94,6 +91,50 @@ public class FMDemodulator return (float)(angle * mGain); } + public float[] demodulate(float[] i, float[] q) + { + float[] demodulated = new float[i.length]; + + float demodI, demodQ; + + //Demodulate the first sample + demodI = (i[0] * mPreviousI) - (q[0] * -mPreviousQ); + demodQ = (q[0] * mPreviousI) + (i[0] * -mPreviousQ); + + //Check for divide by zero + if(demodI != 0) + { + demodulated[0] = (float)FastMath.atan(demodQ / demodI); + } + else + { + demodulated[0] = (float)FastMath.atan(demodQ / Float.MIN_VALUE); + } + + //Store last sample to previous for processing with next sample buffer + mPreviousI = i[i.length - 1]; + mPreviousQ = q[q.length - 1]; + + //Demodulate the remainder of the sample array + for(int x = 1; x < i.length; x++) + { + demodI = (i[x] * i[x - 1]) - (q[x] * -q[x - 1]); + demodQ = (q[x] * i[x - 1]) + (i[x] * -q[x - 1]); + + //Check for divide by zero + if(demodI != 0) + { + demodulated[x] = (float)FastMath.atan(demodQ / demodI); + } + else + { + demodulated[x] = (float)FastMath.atan(demodQ / Float.MIN_VALUE); + } + } + + return demodulated; + } + /** * Demodulates the complex samples and returns the demodulated value. * @param previous @@ -123,30 +164,6 @@ public class FMDemodulator return angle; } - /** - * 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 ReusableFloatBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer) - { - ReusableFloatBuffer 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(); - - return demodulatedBuffer; - } - public void dispose() { //no-op diff --git a/src/main/java/io/github/dsheirer/dsp/fm/ScalarSquelchingFMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/ScalarSquelchingFMDemodulator.java new file mode 100644 index 00000000..6845b757 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/ScalarSquelchingFMDemodulator.java @@ -0,0 +1,98 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.squelch.PowerSquelch; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.SourceEvent; + +/** + * FM Demodulator for demodulating complex samples and producing demodulated floating point samples. + * + * Implements listener of source events to process runtime squelch threshold change request events + * which are forwarded to the power squelch control. + */ +public class ScalarSquelchingFMDemodulator extends ScalarFMDemodulator implements ISquelchingFmDemodulator, Listener +{ + private PowerSquelch mPowerSquelch; + private boolean mSquelchChanged = false; + + /** + * Creates an FM demodulator instance with a default gain of 1.0. + */ + public ScalarSquelchingFMDemodulator(float alpha, float threshold, int ramp) + { + mPowerSquelch = new PowerSquelch(alpha, threshold, ramp); + } + + /** + * Registers the listener to receive notifications of squelch change events from the power squelch. + */ + public void setSourceEventListener(Listener listener) + { + mPowerSquelch.setSourceEventListener(listener); + } + + /** + * Sets the threshold for squelch control + * @param threshold (dB) + */ + public void setSquelchThreshold(double threshold) + { + mPowerSquelch.setSquelchThreshold(threshold); + } + + /** + * Indicates if the squelch state has changed during the processing of buffer(s) + */ + public boolean isSquelchChanged() + { + return mSquelchChanged; + } + + /** + * Sets or resets the squelch changed flag. + */ + private void setSquelchChanged(boolean changed) + { + mSquelchChanged = changed; + } + + /** + * Indicates if the squelch state is currently muted + */ + public boolean isMuted() + { + return mPowerSquelch.isMuted(); + } + + @Override + public void receive(SourceEvent sourceEvent) + { + //Only forward squelch threshold change request events + if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD) + { + mPowerSquelch.receive(sourceEvent); + } + else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD) + { + mPowerSquelch.receive(sourceEvent); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/fm/SquelchingFMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/SquelchingFMDemodulator.java deleted file mode 100644 index fe586a44..00000000 --- a/src/main/java/io/github/dsheirer/dsp/fm/SquelchingFMDemodulator.java +++ /dev/null @@ -1,146 +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.dsp.squelch.PowerSquelch; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.source.SourceEvent; - -/** - * FM Demodulator for demodulating complex samples and producing demodulated floating point samples. - * - * Implements listener of source events to process runtime squelch threshold change request events - * which are forwarded to the power squelch control. - */ -public class SquelchingFMDemodulator extends FMDemodulator implements Listener -{ - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("SquelchingFMDemodulator"); - private PowerSquelch mPowerSquelch; - private boolean mSquelchChanged = false; - - /** - * Creates an FM demodulator instance with a default gain of 1.0. - */ - public SquelchingFMDemodulator(double alpha, double threshold, int ramp) - { - mPowerSquelch = new PowerSquelch(alpha, threshold, ramp); - } - - /** - * Registers the listener to receive notifications of squelch change events from the power squelch. - */ - public void setSourceEventListener(Listener listener) - { - mPowerSquelch.setSourceEventListener(listener); - } - - /** - * 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. - */ - @Override - public ReusableFloatBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer) - { - setSquelchChanged(false); - - ReusableFloatBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount()); - - float[] basebandSamples = basebandSampleBuffer.getSamples(); - float[] demodulatedSamples = demodulatedBuffer.getSamples(); - float i,q; - - for(int x = 0; x < basebandSamples.length; x += 2) - { - i = basebandSamples[x]; - q = basebandSamples[x + 1]; - - mPowerSquelch.process(i, q); - - if(mPowerSquelch.isUnmuted() || mPowerSquelch.isDecay()) - { - demodulatedSamples[x / 2] = demodulate(i, q); - } - else - { - demodulatedSamples[x / 2] = 0.0f; - } - - if(mPowerSquelch.isSquelchChanged()) - { - setSquelchChanged(true); - } - } - - basebandSampleBuffer.decrementUserCount(); - - return demodulatedBuffer; - } - - /** - * Sets the threshold for squelch control - * @param threshold (dB) - */ - public void setSquelchThreshold(double threshold) - { - mPowerSquelch.setSquelchThreshold(threshold); - } - - /** - * Indicates if the squelch state has changed during the processing of buffer(s) - */ - public boolean isSquelchChanged() - { - return mSquelchChanged; - } - - /** - * Sets or resets the squelch changed flag. - */ - private void setSquelchChanged(boolean changed) - { - mSquelchChanged = changed; - } - - /** - * Indicates if the squelch state is currently muted - */ - public boolean isMuted() - { - return mPowerSquelch.isMuted(); - } - - @Override - public void receive(SourceEvent sourceEvent) - { - //Only forward squelch threshold change request events - if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD) - { - mPowerSquelch.receive(sourceEvent); - } - else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD) - { - mPowerSquelch.receive(sourceEvent); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/fm/VectorFMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/VectorFMDemodulator.java new file mode 100644 index 00000000..0d0a89a3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/VectorFMDemodulator.java @@ -0,0 +1,141 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Random; + +/** + * FM demodulator that uses JDK 17+ SIMD vector intrinsics + */ +public class VectorFMDemodulator implements IFmDemodulator +{ + private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator.class); + protected static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + protected static final float ZERO = 0.0f; + private static final int BUFFER_OVERLAP = 1; + + //Initialize buffers to be non-null and resize once the first buffer arrives + private float[] mIBuffer = new float[1]; + private float[] mQBuffer = new float[1]; + + public VectorFMDemodulator() + { + } + + @Override public float[] demodulate(float[] i, float[] q) + { + VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES); + + if(mIBuffer.length != (i.length + BUFFER_OVERLAP)) + { + mIBuffer = new float[i.length + BUFFER_OVERLAP]; + mQBuffer = new float[q.length + BUFFER_OVERLAP]; + } + + //Copy last sample to beginning of buffer arrays + mIBuffer[0] = mIBuffer[mIBuffer.length - 1]; + mQBuffer[0] = mQBuffer[mQBuffer.length - 1]; + + //Copy new samples to end of buffer arrays + System.arraycopy(i, 0, mIBuffer, 1, i.length); + System.arraycopy(q, 0, mQBuffer, 1, q.length); + + float[] demodulated = new float[i.length]; + + FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ; + + for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length()) + { + previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer); + previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer); + currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1); + currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1); + + demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg())); + demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg())); + + //Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors + demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO)); + + demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN); + demod.intoArray(demodulated, bufferPointer); + } + + return demodulated; + } + + public static void main(String[] args) + { + int iterations = 3_000_000; + int sampleSize = 2048; + + Random random = new Random(); + + float[] iSamples = new float[sampleSize]; + float[] qSamples = new float[sampleSize]; + for(int x = 0; x < iSamples.length; x++) + { + iSamples[x] = random.nextFloat() * 2.0f - 1.0f; + qSamples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + boolean validation = false; + + ScalarFMDemodulator legacy = new ScalarFMDemodulator(); + VectorFMDemodulator vector = new VectorFMDemodulator(); + + if(validation) + { + float[] legacySamples = legacy.demodulate(iSamples, qSamples); + float[] vectorSamples = vector.demodulate(iSamples, qSamples); + System.out.println("LEGACY:" + Arrays.toString(legacySamples)); + System.out.println("VECTOR:" + Arrays.toString(vectorSamples)); + } + else + { + System.out.println("Test Starting ..."); + long start = System.currentTimeMillis(); + + double accumulator = 0.0; + + for(int i = 0; i < iterations; i++) + { + float[] samples = legacy.demodulate(iSamples, qSamples); +// float[] samples = vector.demodulate(iSamples, qSamples); + accumulator += samples[3]; + } + + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/fm/VectorSquelchingFMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/VectorSquelchingFMDemodulator.java new file mode 100644 index 00000000..915b7cbd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/VectorSquelchingFMDemodulator.java @@ -0,0 +1,229 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.squelch.PowerSquelch; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Random; + +/** + * FM Demodulator for demodulating complex samples and producing demodulated floating point samples. + * + * Implements listener of source events to process runtime squelch threshold change request events + * which are forwarded to the power squelch control. + */ +public class VectorSquelchingFMDemodulator extends VectorFMDemodulator + implements ISquelchingFmDemodulator, Listener +{ + private static final Logger mLog = LoggerFactory.getLogger(VectorSquelchingFMDemodulator.class); + private static final float TWO = 2.0f; + private PowerSquelch mPowerSquelch; + private boolean mSquelchChanged = false; + + /** + * Creates an FM demodulator instance with a default gain of 1.0. + */ + public VectorSquelchingFMDemodulator(float alpha, float threshold, int ramp) + { + mPowerSquelch = new PowerSquelch(alpha, threshold, ramp); + } + + public void reset() + { + } + + /** + * Registers the listener to receive notifications of squelch change events from the power squelch. + */ + public void setSourceEventListener(Listener listener) + { + mPowerSquelch.setSourceEventListener(listener); + } + + /** + * Demodulates the complex (I/Q) sample arrays + * @param i inphase samples + * @param q quadrature samples + * @return demodulated real samples + */ + @Override + public float[] demodulate(float[] i, float[] q) + { + setSquelchChanged(false); + + float[] demodulated = super.demodulate(i, q); + float[] magnitude = getMagnitude(i, q); + + for(int x = 0; x < i.length; x++) + { + mPowerSquelch.process(magnitude[x]); + + if(!(mPowerSquelch.isUnmuted() || mPowerSquelch.isDecay())) + { + demodulated[x] = ZERO; + } + + if(mPowerSquelch.isSquelchChanged()) + { + setSquelchChanged(true); + } + } + + return demodulated; + } + + /** + * Calculates the magnitude of the complex I/Q sample arrays using SIMD vector intrinsics. + * @param i sample array + * @param q sample array + * @return magnitude array + */ + public static float[] getMagnitude(float[] i, float[] q) + { + VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES); + + float[] magnitudes = new float[i.length]; + FloatVector iVector, qVector; + + for(int samplePointer = 0; samplePointer < i.length; samplePointer += VECTOR_SPECIES.length()) + { + iVector = FloatVector.fromArray(VECTOR_SPECIES, i, samplePointer); + qVector = FloatVector.fromArray(VECTOR_SPECIES, q, samplePointer); + iVector.pow(TWO).add(qVector.pow(TWO)).intoArray(magnitudes, samplePointer); + } + + return magnitudes; + } + + /** + * Sets the threshold for squelch control + * @param threshold (dB) + */ + public void setSquelchThreshold(double threshold) + { + mPowerSquelch.setSquelchThreshold(threshold); + } + + /** + * Indicates if the squelch state has changed during the processing of buffer(s) + */ + public boolean isSquelchChanged() + { + return mSquelchChanged; + } + + /** + * Sets or resets the squelch changed flag. + */ + private void setSquelchChanged(boolean changed) + { + mSquelchChanged = changed; + } + + /** + * Indicates if the squelch state is currently muted + */ + public boolean isMuted() + { + return mPowerSquelch.isMuted(); + } + + @Override + public void receive(SourceEvent sourceEvent) + { + //Only forward squelch threshold change request events + if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD) + { + mPowerSquelch.receive(sourceEvent); + } + else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD) + { + mPowerSquelch.receive(sourceEvent); + } + } + + public static void main(String[] args) + { + int iterations = 3_000_000; + int sampleSize = 2048; + + Random random = new Random(); + + float[] iSamples = new float[sampleSize]; + float[] qSamples = new float[sampleSize]; + for(int x = 0; x < iSamples.length; x++) + { + iSamples[x] = random.nextFloat() * 2.0f - 1.0f; + qSamples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + + float POWER_SQUELCH_ALPHA_DECAY = 0.0004f; + float POWER_SQUELCH_THRESHOLD_DB = -78.0f; + int POWER_SQUELCH_RAMP = 4; + + ScalarFMDemodulator fm = new ScalarFMDemodulator(); + VectorFMDemodulator vectorFM = new VectorFMDemodulator(); + ScalarSquelchingFMDemodulator sqLegacy = new ScalarSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + VectorSquelchingFMDemodulator vectorSq = new VectorSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + + boolean validation = false; + + if(validation) + { + float[] noSqFmSamples = fm.demodulate(iSamples, qSamples); + float[] vectorNoSqFmSamples = vectorFM.demodulate(iSamples, qSamples); + float[] sqFmSamples = sqLegacy.demodulate(iSamples, qSamples); + float[] vectorSqFmSamples = vectorSq.demodulate(iSamples, qSamples); + System.out.println(" NO SQ FM:" + Arrays.toString(noSqFmSamples)); + System.out.println("VECTOR NO SQ FM:" + Arrays.toString(vectorNoSqFmSamples)); + System.out.println(" SQ FM:" + Arrays.toString(sqFmSamples)); + System.out.println(" VECTOR SQ FM:" + Arrays.toString(vectorSqFmSamples)); + } + else + { + System.out.println("Test Starting ..."); + long start = System.currentTimeMillis(); + + double accumulator = 0.0; + + for(int i = 0; i < iterations; i++) + { +// float[] samples = sqLegacy.demodulate(iSamples, qSamples); + float[] samples = vectorSq.demodulate(iSamples, qSamples); + accumulator += samples[3]; + } + + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java index 0d2170a3..ef44f1a6 100644 --- a/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java +++ b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java @@ -1,35 +1,32 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.bits.MessageFramer; +import io.github.dsheirer.dsp.filter.FilterFactory; 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.real.IRealFilter; 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.ReusableFloatBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +46,7 @@ import org.slf4j.LoggerFactory; * 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 +public class LTRDecoder implements Listener, ISyncDetectListener, ISyncStateListener { private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class); @@ -95,7 +92,7 @@ public class LTRDecoder implements Listener, ISyncDetectLis 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 IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients); private MessageFramer mMessageFramer; private boolean mSampleDecision; @@ -154,12 +151,12 @@ public class LTRDecoder implements Listener, ISyncDetectLis * @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling. */ @Override - public void receive(ReusableFloatBuffer buffer) + public void receive(float[] buffer) { - ReusableFloatBuffer dcFiltered = mDCFilter.filter(buffer); - ReusableFloatBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered); + float[] dcFiltered = mDCFilter.filter(buffer); + float[] lowPassFiltered = mLowPassFilter.filter(dcFiltered); - for(float sample : lowPassFiltered.getSamples()) + for(float sample : lowPassFiltered) { mSampleDecision = sample > 0.0; @@ -178,8 +175,6 @@ public class LTRDecoder implements Listener, ISyncDetectLis mSynchronizationMonitor.increment(); } } - - lowPassFiltered.decrementUserCount(); } /** 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 ff5269c1..ac3d81d6 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java @@ -1,64 +1,28 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gain; import io.github.dsheirer.buffer.DoubleCircularBuffer; import io.github.dsheirer.buffer.RealCircularBuffer; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import org.apache.commons.math3.util.FastMath; -/******************************************************************************* - * SDR Trunk - * 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. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL Moe Wheatley OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are - * those of the authors and should not be interpreted as representing - * official policies, either expressed or implied, of Moe Wheatley. - ******************************************************************************/ public class AutomaticGainControl { private static final double SAMPLE_RATE = 48000; @@ -135,8 +99,6 @@ public class AutomaticGainControl 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() { } @@ -148,20 +110,15 @@ public class AutomaticGainControl * @param input samples to process * @return output samples with gain applied */ - public ReusableFloatBuffer process(ReusableFloatBuffer input) + public float[] process(float[] input) { - ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount()); + float[] output = new float[input.length]; - float[] inputSamples = input.getSamples(); - float[] outputSamples = output.getSamples(); - - for(int x = 0; x < inputSamples.length; x++) + for(int x = 0; x < input.length; x++) { - outputSamples[x] = process(inputSamples[x]); + output[x] = process(input[x]); } - input.decrementUserCount(); - return output; } diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexAutomaticGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/ComplexAutomaticGainControl.java deleted file mode 100644 index 83de223d..00000000 --- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexAutomaticGainControl.java +++ /dev/null @@ -1,249 +0,0 @@ -package io.github.dsheirer.dsp.gain; - -import io.github.dsheirer.buffer.ComplexCircularBuffer; -import io.github.dsheirer.buffer.DoubleCircularBuffer; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.Provider; -import io.github.dsheirer.sample.complex.Complex; -import org.apache.commons.math3.util.FastMath; - -import java.util.concurrent.atomic.AtomicBoolean; - -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL Moe Wheatley OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are - * those of the authors and should not be interpreted as representing - * official policies, either expressed or implied, of Moe Wheatley. - ******************************************************************************/ -public class ComplexAutomaticGainControl implements Listener, - Provider -{ - private static final double SAMPLE_RATE = 48000; - - /* Signal delay line - time delay in seconds */ - private static final double DELAY_TIME_CONSTANT = 0.015; - - /* 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; - - private static final double ATTACK_RISE_ALPHA = 1.0 - - FastMath.exp( -1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT ); - - private static final double ATTACK_FALL_ALPHA = 1.0 - - FastMath.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; - - private static final double DECAY_RISE_ALPHA = 1.0 - - FastMath.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO ) ); - - private static final double DECAY_FALL_ALPHA = 1.0 - - FastMath.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 ) ); - - /* Hang timer release decay time constant in seconds */ - @SuppressWarnings( "unused" ) - private static final double RELEASE_TIME_CONSTANT = 0.05; - - /* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */ - private static final double THRESHOLD = -100; - - /* Limit output to about 3db of maximum */ - private static final double AGC_OUT_SCALE = 0.7; - - /* Keep max input and output the same */ - private static final double MAX_AMPLITUDE = 1.0; - private static final double MAX_MANUAL_AMPLITUDE = 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; - - private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE * - FastMath.pow( 10.0, MANUAL_GAIN / 20.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 KNEE = THRESHOLD / 20.0; - - private static final double GAIN_SLOPE = SLOPE_FACTOR / 100.0; - - private static final double FIXED_GAIN = AGC_OUT_SCALE * - FastMath.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 ComplexCircularBuffer mDelayBuffer = - new ComplexCircularBuffer( (int)( SAMPLE_RATE * DELAY_TIME_CONSTANT ) ); - private DoubleCircularBuffer mMagnitudeBuffer = - new DoubleCircularBuffer( (int)( SAMPLE_RATE * WINDOW_TIME_CONSTANT ) ); - - private Listener mListener; - - public ComplexAutomaticGainControl() - { - } - - @Override - public void receive( Complex currentSample ) - { - Complex delayedSample = mDelayBuffer.get( currentSample ); - - double gain = MANUAL_AGC_GAIN; - - if( mAGCEnabled.get() ) - { - float max = currentSample.maximumAbsolute(); - - double currentMagnitude = FastMath.log10( max + MIN_CONSTANT ) - - FastMath.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 = FastMath.max(mAttackAverage, mDecayAverage); - - if( magnitude < KNEE ) - { - gain = FIXED_GAIN; - } - else - { - gain = AGC_OUT_SCALE * FastMath.pow( 10.0, - magnitude * ( GAIN_SLOPE - 1.0 ) ); - } - - } - - delayedSample.multiply( (float)gain ); - - if( mListener != null ) - { - mListener.receive( delayedSample ); - } - } - - /** - * 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(); - } - - @Override - public void setListener( Listener listener ) - { - mListener = listener; - } - - @Override - public void removeListener( Listener listener ) - { - mListener = null; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java deleted file mode 100644 index 0eef1a31..00000000 --- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 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.gain; - -import io.github.dsheirer.buffer.FloatCircularBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.complex.Complex; -import io.github.dsheirer.sample.complex.ComplexSampleListener; - -public class ComplexFeedForwardGainControl implements ComplexSampleListener -{ - public static final float OBJECTIVE_ENVELOPE = 1.0f; - public static final float MINIMUM_ENVELOPE = 0.0001f; - - private ComplexSampleListener mListener; - private FloatCircularBuffer mEnvelopeHistory; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ComplexFeedForwardGainControl"); - - private float mMaxEnvelope = 0.0f; - private float mGain = 1.0f; - - /** - * Dynamic gain control for incoming sample stream to amplify or attenuate - * all samples toward an objective unity)gain, using the maximum envelope - * value detected in the stream history window. - * - * Uses the specified damping factor to limit gain swings. Damping factor - * is applied against the delta between current gain value and a recalculated - * gain value to limit how quickly the gain value will increase or decrease. - * - * @param window - history size to use in detecting maximum envelope value - */ - public ComplexFeedForwardGainControl(int window) - { - mEnvelopeHistory = new FloatCircularBuffer(window); - } - - public void dispose() - { - mListener = null; - } - - @Override - public void receive(float inphase, float quadrature) - { - process(inphase, quadrature); - - /* Apply current gain value to the sample and send to the listener */ - if(mListener != null) - { - mListener.receive(inphase *= mGain, quadrature *= mGain); - } - } - - private void process(float inphase, float quadrature) - { - float envelope = Complex.envelope(inphase, quadrature); - - if(envelope > mMaxEnvelope) - { - mMaxEnvelope = envelope; - - adjustGain(); - } - - /* Replace oldest envelope value with current envelope value */ - float oldestEnvelope = mEnvelopeHistory.getAndPut(envelope); - - /* If the oldest envelope value was the max envelope value, then we - * have to rediscover the max value from the envelope history */ - if(mMaxEnvelope == oldestEnvelope && mMaxEnvelope != envelope) - { - mMaxEnvelope = MINIMUM_ENVELOPE; - - for(float value : mEnvelopeHistory.getBuffer()) - { - if(value > mMaxEnvelope) - { - mMaxEnvelope = value; - } - } - - adjustGain(); - } - } - - private void adjustGain() - { - mGain = OBJECTIVE_ENVELOPE / mMaxEnvelope; - } - - public void setListener(ComplexSampleListener listener) - { - mListener = listener; - } - - public float[] filter(float[] complesSamples) - { - mMaxEnvelope = MINIMUM_ENVELOPE; - float currentEnvelope; - - for(int x = 0; x < complesSamples.length; x += 2) - { - currentEnvelope = Complex.envelope(complesSamples[x], complesSamples[x + 1]); - - if(currentEnvelope > mMaxEnvelope) - { - mMaxEnvelope = currentEnvelope; - } - } - - adjustGain(); - - float[] processed = new float[complesSamples.length]; - - for(int x = 0; x < complesSamples.length; x += 2) - { - processed[x] = complesSamples[x] * mGain; - processed[x + 1] = complesSamples[x + 1] * mGain; - } - - return processed; - } - - /** - * Applies gain to the complex sample buffer - * @param buffer to apply gain - * @return reusable buffer with the user count incremented to 1 - */ - public ReusableComplexBuffer filter(ReusableComplexBuffer buffer) - { - float[] samples = buffer.getSamples(); - - ReusableComplexBuffer filteredBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); - filteredBuffer.setTimestamp(buffer.getTimestamp()); - - float[] filtered = filteredBuffer.getSamples(); - - mMaxEnvelope = MINIMUM_ENVELOPE; - - float currentEnvelope; - - for(int x = 0; x < samples.length; x += 2) - { - currentEnvelope = Complex.envelope(samples[x], samples[x + 1]); - - if(currentEnvelope > mMaxEnvelope) - { - mMaxEnvelope = currentEnvelope; - } - } - - adjustGain(); - - for(int x = 0; x < samples.length; x += 2) - { - filtered[x] = samples[x] * mGain; - filtered[x + 1] = samples[x + 1] * mGain; - } - - buffer.decrementUserCount(); - - return filteredBuffer; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/LegacyComplexGain.java similarity index 86% rename from src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java rename to src/main/java/io/github/dsheirer/dsp/gain/LegacyComplexGain.java index 70dcdd0e..162e87bc 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/LegacyComplexGain.java @@ -3,12 +3,12 @@ package io.github.dsheirer.dsp.gain; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.complex.Complex; -public class ComplexGain implements Listener +public class LegacyComplexGain implements Listener { private float mGain; private Listener mListener; - public ComplexGain(float gain) + public LegacyComplexGain(float gain) { mGain = gain; } diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java new file mode 100644 index 00000000..6f8c0469 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java @@ -0,0 +1,62 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Gain control to apply a fixed level of gain to complex samples. + */ +public abstract class ComplexGain +{ + protected float mGain; + + /** + * Constructs an instance + * @param gain to apply to complex samples + */ + public ComplexGain(float gain) + { + mGain = gain; + } + + /** + * Gain value that will be applied to complex samples + * @return gain value + */ + protected float getGain() + { + return mGain; + } + + /** + * Sets the gain value to apply to the complex samples + * @param gain value + */ + protected void setGain(float gain) + { + mGain = gain; + } + + /** + * Applies a gain value to the complex samples. + * + * Note: the gain is applied directly to the buffer, modifying and returning the + * original sample buffer. + * @param samples to amplify + * @return amplified samples + */ + public ComplexSamples apply(ComplexSamples samples) + { + return apply(samples.i(), samples.q()); + } + + /** + * Applies a gain value to the complex samples. + * + * Note: the gain is applied directly to the buffer, modifying and returning the + * original sample buffer. + * @param i samples to amplify + * @param q samples to amplify + * @return amplified samples + */ + public abstract ComplexSamples apply(float[] i, float q[]); +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainControl.java new file mode 100644 index 00000000..afe8a477 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainControl.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gain.complex; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; + +import java.text.DecimalFormat; +import java.util.Arrays; + +public class ComplexGainControl implements IComplexGainControl +{ + public static final float OBJECTIVE_ENVELOPE = 1.0f; + public static final float MINIMUM_ENVELOPE = 0.0001f; + + /** + * Dynamic gain control for incoming sample stream to amplify or attenuate + * all samples toward an objective unity)gain, using the maximum envelope + * value detected in the stream history window. + * + * Uses the specified damping factor to limit gain swings. Damping factor + * is applied against the delta between current gain value and a recalculated + * gain value to limit how quickly the gain value will increase or decrease. + */ + public ComplexGainControl() + { + } + + /** + * Processes the complex I & Q samples and applies gain to achieve an objective unity + * gain for the single sample in the buffer that has the largest envelope. + * @param i samples + * @param q samples + * @return processed/amplified buffer + */ + @Override public ComplexSamples process(float[] i, float[] q) + { + float maxEnvelope = MINIMUM_ENVELOPE; + + for(int x = 0; x < i.length; x++) + { + maxEnvelope = Math.max(maxEnvelope, Complex.envelope(i[x], q[x])); + } + + float gain = OBJECTIVE_ENVELOPE / maxEnvelope; + + float[] iProcessed = new float[i.length]; + float[] qProcessed = new float[q.length]; + + for(int x = 0; x < i.length; x++) + { + iProcessed[x] = i[x] * gain; + qProcessed[x] = q[x] * gain; + } + + return new ComplexSamples(iProcessed, qProcessed); + } + + public static void main(String[] args) + { + int sampleSize = 2048; + ComplexSamples samples = SampleUtils.generateComplexRandomVectorLength(sampleSize); + ComplexGainControl scalar = new ComplexGainControl(); + VectorComplexGainControl vector = new VectorComplexGainControl(); + + boolean validate = false; + + if(validate) + { + ComplexSamples scalarSamples = scalar.process(samples.i(), samples.q()); + ComplexSamples vectorSamples = vector.process(samples.i(), samples.q()); + + System.out.println("SCALAR I: " + Arrays.toString(scalarSamples.i())); + System.out.println("VECTOR I: " + Arrays.toString(vectorSamples.i())); + System.out.println("SCALAR Q: " + Arrays.toString(scalarSamples.q())); + System.out.println("VECTOR Q: " + Arrays.toString(vectorSamples.q())); + } + else + { + double accumulator = 0.0d; + int iterations = 10_000_000; + long start = System.currentTimeMillis(); + + for(int x = 0; x < iterations; x++) + { +// ComplexSamples processed = scalar.process(samples.i(), samples.q()); + ComplexSamples processed = vector.process(samples.i(), samples.q()); + accumulator += processed.i()[3]; + } + + double elapsed = System.currentTimeMillis() - start; + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainFactory.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainFactory.java new file mode 100644 index 00000000..0ae9532a --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainFactory.java @@ -0,0 +1,71 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gain.complex; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; + +/** + * Factory for selecting and creating the optimal implementation of complex gain + * control for this hardware. + */ +public class ComplexGainFactory +{ + /** + * Instantiates the optimal complex gain control implementation for this hardware, + * as determined by the Calibration Manager. + */ + public static IComplexGainControl getComplexGainControl() + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.GAIN_CONTROL_COMPLEX); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + { + return new VectorComplexGainControl(); + } + case SCALAR: + default: + return new ComplexGainControl(); + } + } + + /** + * Instantiates the optimal complex gain implementation for this hardware, + * as determined by the Calibration Manager. + */ + public static ComplexGain getComplexGain(float gain) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.GAIN_CONTROL_COMPLEX); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + { + return new VectorComplexGain(gain); + } + case SCALAR: + default: + return new ScalarComplexGain(gain); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/IComplexGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/IComplexGainControl.java new file mode 100644 index 00000000..bc2be9eb --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/IComplexGainControl.java @@ -0,0 +1,17 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Interface for complex gain control + */ +public interface IComplexGainControl +{ + /** + * Processes a buffer of complex I & Q samples and applies gain to achieve an objective unity gain. + * @param i samples + * @param q samples + * @return amplified gain samples + */ + ComplexSamples process(float[] i, float[] q); +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ScalarComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ScalarComplexGain.java new file mode 100644 index 00000000..d6cb9875 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ScalarComplexGain.java @@ -0,0 +1,35 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Scalar implementation of complex gain + */ +public class ScalarComplexGain extends ComplexGain +{ + /** + * Constructs an instance + * @param gain to apply to complex samples + */ + public ScalarComplexGain(float gain) + { + super(gain); + } + + /** + * Applies gain to the complex samples buffer + * @param i samples to amplify + * @param q samples to amplify + * @return amplified samples + */ + @Override public ComplexSamples apply(float[] i, float[] q) + { + for(int x = 0; x < i.length; x++) + { + i[x] *= mGain; + q[x] *= mGain; + } + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGain.java new file mode 100644 index 00000000..899eda23 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGain.java @@ -0,0 +1,42 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector implementation of complex gain that uses JDK 17+ SIMD instructions + */ +public class VectorComplexGain extends ComplexGain +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + /** + * Constructs an instance + * + * @param gain to apply to complex samples + */ + public VectorComplexGain(float gain) + { + super(gain); + } + + /** + * Applies gain to the complex samples buffer + * @param i samples to amplify + * @param q samples to amplify + * @return amplified samples. + */ + @Override public ComplexSamples apply(float[] i, float[] q) + { + VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES); + + for(int x = 0; x < i.length; x += VECTOR_SPECIES.length()) + { + FloatVector.fromArray(VECTOR_SPECIES, i, x).mul(mGain).intoArray(i, x); + FloatVector.fromArray(VECTOR_SPECIES, q, x).mul(mGain).intoArray(q, x); + } + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGainControl.java new file mode 100644 index 00000000..2d6d15b5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGainControl.java @@ -0,0 +1,52 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Gain control that applies gain to a complex sample buffer to achieve unity gain for + * the single sample that has the largest envelope within the buffer. Uses JDK 17+ SIMD + * vector intrinsics to process the buffer samples. + */ +public class VectorComplexGainControl implements IComplexGainControl +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final float OBJECTIVE_ENVELOPE = 1.0f; + private static final float MINIMUM_ENVELOPE = 0.0001f; + private static final float ENVELOPE_ESTIMATE = 0.4f; + + @Override public ComplexSamples process(float[] i, float[] q) + { + VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES); + + //Determine the largest envelope + FloatVector iAbs, qAbs, maxEnvelope; + + maxEnvelope = FloatVector.zero(VECTOR_SPECIES).add(MINIMUM_ENVELOPE); + + for(int bufferPointer = 0; bufferPointer < i.length; bufferPointer += VECTOR_SPECIES.length()) + { + iAbs = FloatVector.fromArray(VECTOR_SPECIES, i, bufferPointer).abs(); + qAbs = FloatVector.fromArray(VECTOR_SPECIES, q, bufferPointer).abs(); + maxEnvelope = maxEnvelope.max(iAbs.add(qAbs.mul(ENVELOPE_ESTIMATE))); + maxEnvelope = maxEnvelope.max(qAbs.add(iAbs.mul(ENVELOPE_ESTIMATE))); + } + + float gain = OBJECTIVE_ENVELOPE / maxEnvelope.reduceLanes(VectorOperators.MAX); + + float[] iProcessed = new float[i.length]; + float[] qProcessed = new float[q.length]; + + //Apply gain to all I & Q samples + for(int bufferPointer = 0; bufferPointer < i.length; bufferPointer += VECTOR_SPECIES.length()) + { + FloatVector.fromArray(VECTOR_SPECIES, i, bufferPointer).mul(gain).intoArray(iProcessed, bufferPointer); + FloatVector.fromArray(VECTOR_SPECIES, q, bufferPointer).mul(gain).intoArray(qProcessed, bufferPointer); + } + + return new ComplexSamples(iProcessed, qProcessed); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java deleted file mode 100644 index 30594c41..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java +++ /dev/null @@ -1,192 +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.dsp.mixer; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.sample.complex.Complex; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.FloatBuffer; - -public abstract class AbstractOscillator implements IOscillator -{ - private final static Logger mLog = LoggerFactory.getLogger(AbstractOscillator.class); - - private FloatBuffer mSampleBuffer; - private boolean mEnabled; - private double mFrequency; - private double mSampleRate; - - public AbstractOscillator(double frequency, double sampleRate) - { - mFrequency = frequency; - mSampleRate = sampleRate; - mEnabled = frequency != 0; - update(); - } - - /** - * Frequency of the tone being generated by this oscillator - */ - public double getFrequency() - { - return mFrequency; - } - - /** - * Sets or changes the frequency of this oscillator - */ - public void setFrequency(double frequency) - { - mEnabled = frequency != 0; - mFrequency = frequency; - update(); - } - - /** - * Sample rate of this oscillator - */ - public double getSampleRate() - { - return mSampleRate; - } - - /** - * Sets or changes the sample rate of this oscillator - */ - public void setSampleRate(double sampleRate) - { - mSampleRate = sampleRate; - update(); - } - - /** - * Indicates if this oscillator has a non-zero frequency setting. When used as a frequency translating - * mixer, setting a frequency of zero indicates that no frequency correction is needed, so this flag can - * indicate when to apply the oscillator to frequency correct samples. - * - * @return true if the current frequency value is non-zero. - */ - @Override - public boolean isEnabled() - { - return mEnabled; - } - - /** - * Commands sub-class implementations to update internal structures to account for changes in frequency or - * sample rate. The boolean reset indicator optionally commands a reset of the current I/Q values. - */ - protected abstract void update(); - - /** - * Performs complex heterodyne against the samples using this oscillator - * @param samples to mix with this oscillator - */ - @Override - public float[] mixComplex(float[] samples) - { - for(int x = 0; x < samples.length; x += 2) - { - float i = Complex.multiplyInphase(samples[x], samples[x + 1], inphase(), quadrature()); - float q = Complex.multiplyQuadrature(samples[x], samples[x + 1], inphase(), quadrature()); - - samples[x] = i; - samples[x + 1] = q; - - rotate(); - } - - return samples; - } - - /** - * Generates an array of real samples from this oscillator. - * @param sampleCount number of samples to generate and length of the resulting float array. - * @return - */ - @Override - public float[] generateReal(int sampleCount) - { - float[] samples = new float[sampleCount]; - - for(int x = 0; x < sampleCount; x++) - { - rotate(); - samples[x] = quadrature(); - } - - return samples; - } - - /** - * Generates an array of complex samples from this oscillator. - * @param sampleCount number of samples to generate and length of the resulting float array. - * @return - */ - @Override - public float[] generateComplex(int sampleCount) - { - float[] samples = new float[sampleCount * 2]; - - for(int x = 0; x < sampleCount * 2; x += 2) - { - samples[x] = inphase(); - samples[x + 1] = quadrature(); - rotate(); - } - - return samples; - } - - /** - * Generates enough complex samples to fill the reusable complex buffer - * @param reusableComplexBuffer to fill with complex samples - */ - @Override - public void generateComplex(ReusableComplexBuffer reusableComplexBuffer) - { - int sampleCount = reusableComplexBuffer.getSampleCount(); - - if(mSampleBuffer == null || mSampleBuffer.capacity() != sampleCount * 2) - { - mSampleBuffer = FloatBuffer.allocate(sampleCount * 2); - } - - mSampleBuffer.rewind(); - - for(int x = 0; x < sampleCount; x++) - { - mSampleBuffer.put(inphase()); - mSampleBuffer.put(quadrature()); - rotate(); - } - - reusableComplexBuffer.reloadFrom(mSampleBuffer, System.currentTimeMillis()); - } - - /** - * Generates enough real samples to fill the reusable buffer - * @param reusableFloatBuffer to fill with real samples - */ - @Override - public void generateReal(ReusableFloatBuffer reusableFloatBuffer) - { - reusableFloatBuffer.reloadFrom(generateReal(reusableFloatBuffer.getSampleCount()), System.currentTimeMillis()); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java new file mode 100644 index 00000000..3f531d60 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java @@ -0,0 +1,123 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.mixer; + +import io.github.dsheirer.dsp.oscillator.IComplexOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; +import io.github.dsheirer.dsp.oscillator.ScalarComplexOscillator; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +/** + * Base complex mixer that wraps an oscillator implementation and mixes complex sample buffers + */ +public abstract class ComplexMixer +{ + private IComplexOscillator mOscillator; + + /** + * Constructs an instance + * @param frequency to generate + * @param sampleRate to generate + * @param disableSIMD to disable use of SIMD vector instructions. + */ + public ComplexMixer(double frequency, double sampleRate, boolean disableSIMD) + { + if(disableSIMD) + { + mOscillator = new ScalarComplexOscillator(frequency, sampleRate); + } + else + { + mOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate); + } + } + + public ComplexMixer(double frequency, double sampleRate) + { + mOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate); + } + + /** + * Sets the frequency of the underlying oscillator + * @param frequency in Hertz + */ + public void setFrequency(double frequency) + { + mOscillator.setFrequency(frequency); + } + + /** + * Indicates if this mixer's oscillator is set to a non-zero frequency value. + */ + public boolean hasFrequency() + { + return mOscillator.hasFrequency(); + } + + /** + * Sets the sample rate of the underlying oscillator + * @param sampleRate in Hertz + */ + public void setSampleRate(double sampleRate) + { + mOscillator.setSampleRate(sampleRate); + } + + /** + * Generates complex samples from the underlying oscillator + * @param sampleCount to generate + * @return complex samples + */ + protected ComplexSamples generate(int sampleCount) + { + return mOscillator.generateComplexSamples(sampleCount); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator. + * @param samples to mix + * @return mixed samples + */ + public ComplexSamples mix(ComplexSamples samples) + { + return mix(samples.i(), samples.q()); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator + * @param samples interleaved + * @return mixed samples + */ + public ComplexSamples mix(InterleavedComplexSamples samples) + { + return mix(samples.toDeinterleaved()); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator. + * @param i complex samples to mix + * @param q complex samples to mix + * @return mixed samples + */ + public abstract ComplexSamples mix(float[] i, float[] q); + + +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixerFactory.java b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixerFactory.java new file mode 100644 index 00000000..4610cda4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixerFactory.java @@ -0,0 +1,62 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.mixer; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory for creating complex mixer instances from scalar and vector implementations. + */ +public class ComplexMixerFactory +{ + private static final Logger mLog = LoggerFactory.getLogger(ComplexMixerFactory.class); + + /** + * Creates an instance of the optimal implementation of a complex mixer for this hardware. + * @param frequency of the mixing oscillator + * @param sampleRate of the mixing oscillator + * @return optimal instance + */ + public static ComplexMixer getMixer(double frequency, double sampleRate) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.MIXER_COMPLEX); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + { + return new VectorComplexMixer(frequency, sampleRate); + } + case SCALAR: + { + return new ScalarComplexMixer(frequency, sampleRate); + } + default: + { + mLog.warn("Unrecognized complex mixer implementation: " + implementation); + return new ScalarComplexMixer(frequency, sampleRate); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java b/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java deleted file mode 100644 index d1645698..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java +++ /dev/null @@ -1,69 +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.dsp.mixer; - -public class FS4DownConverter -{ - private int mPointer = 0; - - /** - * Performs complex frequency conversion at one-fourth (-FS/4) of the sample rate using simple value - * assignments. - * - * See: Digital Signal Processing 3e, Lyons, p.674-675 - */ - public FS4DownConverter() - { - } - - public float[] mixComplex(float[] samples) - { - float real; - - for(int x = 0; x < samples.length; x += 2) - { - switch(mPointer) - { - case 0: - //no-op - break; - case 1: - real = samples[x]; - samples[x] = samples[x + 1]; - samples[x + 1] = -real; - break; - case 2: - samples[x] = -samples[x]; - samples[x + 1] = -samples[x + 1]; - break; - case 3: - real = samples[x]; - samples[x] = -samples[x + 1]; - samples[x + 1] = real; - break; - } - - mPointer++; - - if(mPointer >= 4) - { - mPointer = 0; - } - } - - return samples; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java deleted file mode 100644 index 5546bff7..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java +++ /dev/null @@ -1,115 +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.dsp.mixer; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -/** - * Oscillator interface. - */ -public interface IOscillator -{ - /** - * Frequency of the tone being generated by the oscillator - * @return frequency in hertz - */ - double getFrequency(); - - /** - * Sets the frequency of the tone being generated by the oscillator. - * @param frequency in hertz - */ - void setFrequency(double frequency); - - /** - * Sample rate of the oscillator - * @return sample rate in hertz - */ - double getSampleRate(); - - /** - * Sets the sample rate of the oscillator - * @param sampleRate in hertz - */ - void setSampleRate(double sampleRate); - - - /** - * Indicates if this oscillator is enabled, meaning that it has a non-zero frequency value. - * @return true if the frequency value is non-zero - */ - boolean isEnabled(); - - /** - * Current quadrature/imaginary value of the oscillator. Note: if you are using this value iteratively, make sure - * you invoke rotate() after each time you invoke this method. - * @return current quadrature value of the oscillator. - */ - float quadrature(); - - /** - * Current inphase/real value of the oscillator. Note: if you are using this value iteratively, make sure you - * invoke rotate() after each time you invoke this method. - * @return current inphase value of the oscillator. - */ - float inphase(); - - - /** - * Rotates the oscillator by one sample period to generate new inphase() and quadrature() values. - */ - void rotate(); - - /** - * Generates the specified number of real samples into a sample array. - * @param sampleCount number of samples to generate and length of the resulting float array. - * @return generated samples - */ - float[] generateReal(int sampleCount); - - - /** - * Generates the specified number of complex samples where the samples are arranged as i0,q0,i1,q1 ... iN-1,qN-1 - * @param sampleCount number of samples to generate. - * @return generated complex samples with an array length of 2 * N - */ - float[] generateComplex(int sampleCount); - - - /** - * Generates complex samples and fills the reusable complex buffer using the current system time in millis for - * the update timestamp. - * - * @param reusableComplexBuffer to fill with complex samples - */ - void generateComplex(ReusableComplexBuffer reusableComplexBuffer); - - /** - * Generates real samples and fills the reusable buffer using the current system time in millis for - * the update timestamp. - * - * @param reusableFloatBuffer to fill with complex samples - */ - void generateReal(ReusableFloatBuffer reusableFloatBuffer); - - /** - * Mixes (heterodynes) the complex sample array using the current settings of this oscillator. - * @param complexSamples to mix to a new frequency - * @return mixed/heterdyned samples - */ - float[] mixComplex(float[] complexSamples); -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java deleted file mode 100644 index 9b46665f..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java +++ /dev/null @@ -1,140 +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.dsp.mixer; - -import org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.concurrent.TimeUnit; - -public class LowPhaseNoiseOscillator extends AbstractOscillator -{ - private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class); - - private static final double TWO_PI = 2.0 * FastMath.PI; - private static final double THREE_HALVES = 3.0 / 2.0; - private double mInphase = 1.0; - private double mQuadrature = 0.0; - private double mPreviousInphase = 1.0; - private double mPreviousQuadrature = 0.0; - private double mCosineAngle; - private double mSineAngle; - private double mGain = 1.0; - - /** - * Ultra-low phase noise complex oscillator as described in Digital Signal Processing 3e, Lyons, p.786 - * - * @param sampleRate for the oscillator - * @param frequency to generate - */ - public LowPhaseNoiseOscillator(double frequency, double sampleRate) - { - super(frequency, sampleRate); - - update(); - } - - @Override - public float quadrature() - { - return (float)mQuadrature; - } - - @Override - public float inphase() - { - return (float)mInphase; - } - - /** - * Updates the internal values after a frequency or sample rate change - */ - protected void update() - { - double anglePerSample = TWO_PI * getFrequency() / getSampleRate(); - - mCosineAngle = FastMath.cos(anglePerSample); - mSineAngle = FastMath.sin(anglePerSample); - } - - /** - * Rotates the oscillator to generate the next complex sample. - */ - public void rotate() - { - mInphase = ((mPreviousInphase * mCosineAngle) - (mPreviousQuadrature * mSineAngle)) * mGain; - mQuadrature = ((mPreviousInphase * mSineAngle) + (mPreviousQuadrature * mCosineAngle)) * mGain; - - mPreviousInphase = mInphase; - mPreviousQuadrature = mQuadrature; - - //Update the gain value for the next rotation - mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature)); - } - - public static long process(AbstractOscillator oscillator, int iterations, int sampleCount) - { - long start = System.nanoTime(); - - for(int x = 0; x < iterations; x++) - { - float[] samples = oscillator.generateComplex(sampleCount); - } - - long duration = System.nanoTime() - start; - - return duration; - } - - public static void main(String[] args) - { - mLog.debug("Starting ..."); - SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS"); - - AbstractOscillator lpno = new LowPhaseNoiseOscillator(100, 800); - AbstractOscillator regular = new Oscillator(100, 800); - - int iterations = 10000; - int sampleCount = 1000000; - - mLog.debug("Warm up - lpno"); - process(lpno, iterations, sampleCount); - process(lpno, iterations, sampleCount); - process(lpno, iterations, sampleCount); - - mLog.debug("Warm up - regular"); - process(regular, iterations, sampleCount); - process(regular, iterations, sampleCount); - process(regular, iterations, sampleCount); - - mLog.debug("Test - lpno"); - process(lpno, iterations, sampleCount); - process(lpno, iterations, sampleCount); - long duration = process(lpno, iterations, sampleCount); - mLog.debug("Duration Low Phase Noise: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration)))); - - mLog.debug("Test - regular"); - process(regular, iterations, sampleCount); - process(regular, iterations, sampleCount); - duration = process(regular, iterations, sampleCount); - mLog.debug("Duration Regular: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration)))); - -// mLog.debug(" Delta: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration2 - duration)))); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java deleted file mode 100644 index 825c5e76..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java +++ /dev/null @@ -1,69 +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.dsp.mixer; - -import io.github.dsheirer.sample.complex.Complex; -import org.apache.commons.math3.util.FastMath; - -public class Oscillator extends AbstractOscillator -{ - private Complex mAnglePerSample; - private Complex mCurrentAngle = new Complex(0.0f, -1.0f); - - /** - * Oscillator that produces complex or float samples corresponding to a sine wave oscillating at the specified - * frequency and sample rate - * - * @param frequency - positive or negative frequency in hertz - * @param sampleRate - in hertz - */ - public Oscillator(double frequency, double sampleRate) - { - super(frequency, sampleRate); - } - - /** - * Steps the current angle by the angle per sample amount - */ - @Override - public void rotate() - { - mCurrentAngle.multiply(mAnglePerSample); - mCurrentAngle.fastNormalize(); - } - - @Override - public float inphase() - { - return mCurrentAngle.inphase(); - } - - @Override - public float quadrature() - { - return mCurrentAngle.quadrature(); - } - - /** - * Updates the internal values after a frequency or sample rate change - */ - @Override - protected void update() - { - float anglePerSample = (float)(2.0d * FastMath.PI * getFrequency() / getSampleRate()); - mAnglePerSample = Complex.fromAngle(anglePerSample); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/ScalarComplexMixer.java b/src/main/java/io/github/dsheirer/dsp/mixer/ScalarComplexMixer.java new file mode 100644 index 00000000..bd4cdd60 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/ScalarComplexMixer.java @@ -0,0 +1,104 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.mixer; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +/** + * Scalar implementation of a complex mixer + */ +public class ScalarComplexMixer extends ComplexMixer +{ + /** + * Constructs an instance + * @param frequency of the mixing oscillator + * @param sampleRate of the mixing oscillator + * @param disableSIMD to disable use of SIMD vector instructions + */ + public ScalarComplexMixer(double frequency, double sampleRate, boolean disableSIMD) + { + super(frequency, sampleRate, disableSIMD); + } + + /** + * Constructs an instance + * @param frequency of the mixing oscillator + * @param sampleRate of the mixing oscillator + */ + public ScalarComplexMixer(double frequency, double sampleRate) + { + super(frequency, sampleRate); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator. + * @param iSamples complex samples to mix + * @param qSamples complex samples to mix + * @return mixed samples + */ + @Override public ComplexSamples mix(float[] iSamples, float[] qSamples) + { + ComplexSamples mixer = generate(iSamples.length); + + float[] iMixer = mixer.i(); + float[] qMixer = mixer.q(); + + float inphase, quadrature; + + for(int x = 0; x < iSamples.length; x++) + { + inphase = (iMixer[x] * iSamples[x]) - (qMixer[x] * qSamples[x]); + quadrature = (qMixer[x] * iSamples[x]) + (iMixer[x] * qSamples[x]); + iMixer[x] = inphase; + qMixer[x] = quadrature; + } + + return mixer; + } + + /** + * Mixes the interleaved complex samples with oscillator generated samples + * @param samples interleaved + * @return mixed samples + */ + @Override + public ComplexSamples mix(InterleavedComplexSamples samples) + { + ComplexSamples mixer = generate(samples.samples().length / 2); + + float[] iMixer = mixer.i(); + float[] qMixer = mixer.q(); + + float inphase, quadrature; + int offset = 0; + + for(int x = 0; x < iMixer.length; x++) + { + offset = 2 * x; + inphase = (iMixer[x] * samples.samples()[offset]) - (qMixer[x] * samples.samples()[offset + 1]); + quadrature = (qMixer[x] * samples.samples()[offset]) + (iMixer[x] * samples.samples()[offset + 1]); + iMixer[x] = inphase; + qMixer[x] = quadrature; + } + + return mixer; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/VectorComplexMixer.java b/src/main/java/io/github/dsheirer/dsp/mixer/VectorComplexMixer.java new file mode 100644 index 00000000..b41bae2d --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/VectorComplexMixer.java @@ -0,0 +1,57 @@ +package io.github.dsheirer.dsp.mixer; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector implementation of a complex mixer that uses JDK 17+ SIMD instructions. + */ +public class VectorComplexMixer extends ComplexMixer +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + + /** + * Constructs an instance + * @param frequency of the oscillator + * @param sampleRate of the oscillator + */ + public VectorComplexMixer(double frequency, double sampleRate) + { + super(frequency, sampleRate); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator. + * @param iSamples complex samples to mix + * @param qSamples complex samples to mix + * @return mixed samples + */ + @Override public ComplexSamples mix(float[] iSamples, float[] qSamples) + { + VectorUtilities.checkComplexArrayLength(iSamples, qSamples, VECTOR_SPECIES); + + //Reuse this complex samples buffer to store the results and return to caller + ComplexSamples mixer = generate(iSamples.length); + + float[] iMixer = mixer.i(); + float[] qMixer = mixer.q(); + + FloatVector iS, qS, iM, qM; + + for(int x = 0; x < iSamples.length; x += VECTOR_SPECIES.length()) + { + iS = FloatVector.fromArray(VECTOR_SPECIES, iSamples, x); + qS = FloatVector.fromArray(VECTOR_SPECIES, qSamples, x); + iM = FloatVector.fromArray(VECTOR_SPECIES, iMixer, x); + qM = FloatVector.fromArray(VECTOR_SPECIES, qMixer, x); + + //Perform complex mixing and store results back to mixer arrays + iM.mul(iS).sub(qM.mul(qS)).intoArray(iMixer, x); + qM.mul(iS).add(iM.mul(qS)).intoArray(qMixer, x); + } + + return mixer; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/AbstractOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/AbstractOscillator.java new file mode 100644 index 00000000..35e80a71 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/AbstractOscillator.java @@ -0,0 +1,116 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +/** + * Base Oscillator class. + * + * @see OscillatorFactory + */ +public abstract class AbstractOscillator implements IOscillator +{ + protected static final double TWO_PI = Math.PI * 2.0d; + private double mFrequency; + private double mSampleRate; + protected float mAnglePerSample; + protected float mCurrentPhase = 0.0f; + + /** + * Constructs an instance + * @param frequency in hertz + * @param sampleRate in hertz + */ + protected AbstractOscillator(double frequency, double sampleRate) + { + mFrequency = frequency; + mSampleRate = sampleRate; + + update(); + } + + /** + * Angle per sample based on current frequency and sample rate + * @return angle per sample in radians + */ + protected float getAnglePerSample() + { + return mAnglePerSample; + } + + /** + * Updates/calculates the angle per sample from the frequency and sample rate values. + */ + protected void update() + { + mAnglePerSample = (float)(TWO_PI * getFrequency() / getSampleRate()); + } + + /** + * Frequency for this oscillator + * @return frequency in Hertz + */ + public double getFrequency() + { + return mFrequency; + } + + /** + * Sets the frequency for this oscillator + * @param frequency in hertz + */ + public void setFrequency(double frequency) + { + mFrequency = frequency; + update(); + } + + @Override + public boolean hasFrequency() + { + return mFrequency != 0; + } + + /** + * Sample rate for this oscillator + * @return sample rate in Hertz + */ + public double getSampleRate() + { + return mSampleRate; + } + + /** + * Sets the sample rate for this oscillator + * @param sampleRate in hertz + */ + public void setSampleRate(double sampleRate) + { + mSampleRate = sampleRate; + update(); + } + + /** + * Indicates if this oscillator is enabled, meaning it has a frequency that is not equal to zero. + */ + public boolean isEnabled() + { + return mFrequency != 0.0d; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/FS4DownConverter.java b/src/main/java/io/github/dsheirer/dsp/oscillator/FS4DownConverter.java new file mode 100644 index 00000000..43ab9d1e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/FS4DownConverter.java @@ -0,0 +1,113 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +public class FS4DownConverter +{ + private int mPointer = 0; + + /** + * Performs complex frequency conversion at one-fourth (-FS/4) of the sample rate using simple value + * assignments. + * + * See: Digital Signal Processing 3e, Lyons, p.674-675 + */ + public FS4DownConverter() + { + } + + public float[] mixComplex(float[] samples) + { + float real; + int pointer = mPointer; + + for(int x = 0; x < samples.length; x += 2) + { + switch(pointer) + { + case 0: + //no-op + break; + case 1: + real = samples[x]; + samples[x] = samples[x + 1]; + samples[x + 1] = -real; + break; + case 2: + samples[x] = -samples[x]; + samples[x + 1] = -samples[x + 1]; + break; + case 3: + real = samples[x]; + samples[x] = -samples[x + 1]; + samples[x + 1] = real; + break; + } + + pointer++; + pointer %= 4; + } + + mPointer = pointer; + + return samples; + } + + public ComplexSamples mixComplex(ComplexSamples samples) + { + float temp; + int pointer = mPointer; + + float[] i = samples.i(); + float[] q = samples.q();; + + for(int x = 0; x < i.length; x++) + { + switch(pointer) + { + case 0: + //no-op + break; + case 1: + temp = i[x]; + i[x] = q[x]; + q[x] = -temp; + break; + case 2: + i[x] = -i[x]; + q[x] = -q[x]; + break; + case 3: + temp = i[x]; + i[x] = -q[x]; + q[x] = temp; + break; + } + + pointer++; + pointer %= 4; + } + + mPointer = pointer; + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/FS4UpConverter.java b/src/main/java/io/github/dsheirer/dsp/oscillator/FS4UpConverter.java similarity index 98% rename from src/main/java/io/github/dsheirer/dsp/mixer/FS4UpConverter.java rename to src/main/java/io/github/dsheirer/dsp/oscillator/FS4UpConverter.java index c57abaaa..93b2d657 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/FS4UpConverter.java +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/FS4UpConverter.java @@ -13,7 +13,7 @@ * If not, see * ******************************************************************************/ -package io.github.dsheirer.dsp.mixer; +package io.github.dsheirer.dsp.oscillator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/IComplexOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/IComplexOscillator.java new file mode 100644 index 00000000..9b79e190 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/IComplexOscillator.java @@ -0,0 +1,39 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +public interface IComplexOscillator extends IOscillator +{ + /** + * Generates the specified number of complex samples into an interleaved sample array. + * @param sampleCount number of complex samples to generate. + * @return generated samples where each sample is interleaved: I0, Q0, I1, Q1 ... + */ + float[] generate(int sampleCount); + + /** + * Generates the specified number of complex samples into a complex samples object. + * @param sampleCount number of complex samples to generate. + * @return generated samples + */ + ComplexSamples generateComplexSamples(int sampleCount); +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/IOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/IOscillator.java new file mode 100644 index 00000000..3bfd0ab1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/IOscillator.java @@ -0,0 +1,59 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +public interface IOscillator +{ + /** + * Frequency of the tone being generated by the oscillator + * @return frequency in hertz + */ + double getFrequency(); + + /** + * Sets the frequency of the tone being generated by the oscillator. + * @param frequency in hertz + */ + void setFrequency(double frequency); + + /** + * Indicates if the current frequency value is non-zero. + */ + boolean hasFrequency(); + + /** + * Sample rate of the oscillator + * @return sample rate in hertz + */ + double getSampleRate(); + + /** + * Sets the sample rate of the oscillator + * @param sampleRate in hertz + */ + void setSampleRate(double sampleRate); + + + /** + * Indicates if this oscillator is enabled, meaning that it has a non-zero frequency value. + * @return true if the frequency value is non-zero + */ + boolean isEnabled(); +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/IRealOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/IRealOscillator.java new file mode 100644 index 00000000..4046e894 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/IRealOscillator.java @@ -0,0 +1,30 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +public interface IRealOscillator extends IOscillator +{ + /** + * Generates the specified number of real samples into a sample array. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return generated samples + */ + float[] generate(int sampleCount); +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/OscillatorFactory.java b/src/main/java/io/github/dsheirer/dsp/oscillator/OscillatorFactory.java new file mode 100644 index 00000000..3aafea0c --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/OscillatorFactory.java @@ -0,0 +1,76 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory class for creating real and complex oscillators. + * + * Uses the CalibrationManager to determine the optimal (scalar vs vector) oscillator implementation type. + */ +public class OscillatorFactory +{ + private static final Logger mLog = LoggerFactory.getLogger(OscillatorFactory.class); + + /** + * Constructs an optimal implementation of a real sample oscillator using calibration data when available. + * @param frequency of the oscillator + * @param sampleRate of the oscillator + * @return constructed oscillator + */ + public static IRealOscillator getRealOscillator(double frequency, double sampleRate) + { + Implementation operation = CalibrationManager.getInstance().getImplementation(CalibrationType.OSCILLATOR_REAL); + + switch(operation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorRealOscillator(frequency, sampleRate); + case SCALAR: + default: + return new ScalarRealOscillator(frequency, sampleRate); + } + } + + /** + * Constructs an optimal implementation of a complex sample oscillator using calibration data when available. + * @param frequency of the oscillator + * @param sampleRate of the oscillator + * @return constructed oscillator + */ + public static IComplexOscillator getComplexOscillator(double frequency, double sampleRate) + { + Implementation operation = CalibrationManager.getInstance().getImplementation(CalibrationType.OSCILLATOR_COMPLEX); + + switch(operation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorComplexOscillator(frequency, sampleRate); + case SCALAR: + default: + return new ScalarComplexOscillator(frequency, sampleRate); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarComplexOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarComplexOscillator.java new file mode 100644 index 00000000..e945ae15 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarComplexOscillator.java @@ -0,0 +1,138 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import org.apache.commons.math3.util.FastMath; + +/** + * Scalar implementation of a complex oscillator. + */ +public class ScalarComplexOscillator extends AbstractOscillator implements IComplexOscillator +{ + private float mCosineAngle; + private float mSineAngle; + private float mPreviousInphase = 1.0f; + private float mPreviousQuadrature = 0.0f; + + /** + * Constructs an instance + * + * @param frequency in hertz + * @param sampleRate in hertz + */ + public ScalarComplexOscillator(double frequency, double sampleRate) + { + super(frequency, sampleRate); + } + + /** + * Updates the internal values after a frequency or sample rate change + */ + @Override + protected void update() + { + super.update(); + float angle = getAnglePerSample(); + mCosineAngle = (float)FastMath.cos(getAnglePerSample()); + mSineAngle = (float)FastMath.sin(getAnglePerSample()); + } + + @Override + public float[] generate(int sampleCount) + { + float[] samples = new float[sampleCount * 2]; + float cosineAngle = (float)FastMath.cos(getAnglePerSample()); + float sineAngle = (float)FastMath.sin(getAnglePerSample()); + + float previousInphase, previousQuadrature, gain; + + previousInphase = mPreviousInphase; + previousQuadrature = mPreviousQuadrature; + + int gainPointer = 0; + + for(int samplePointer = 0; samplePointer < samples.length; samplePointer += 2) + { + if(++gainPointer % 10 == 0) + { + gainPointer = 0; + gain = (3.0f - ((previousInphase * previousInphase) + (previousQuadrature * previousQuadrature))) / 2.0f; + + samples[samplePointer] = ((previousInphase * cosineAngle) - (previousQuadrature * sineAngle)) * gain; + samples[samplePointer + 1] = ((previousInphase * sineAngle) + (previousQuadrature * cosineAngle)) * gain; + } + else + { + samples[samplePointer] = ((previousInphase * cosineAngle) - (previousQuadrature * sineAngle)); + samples[samplePointer + 1] = ((previousInphase * sineAngle) + (previousQuadrature * cosineAngle)); + } + + previousInphase = samples[samplePointer]; + previousQuadrature = samples[samplePointer + 1]; + } + + mPreviousInphase = previousInphase; + mPreviousQuadrature = previousQuadrature; + + return samples; + } + + @Override + public ComplexSamples generateComplexSamples(int sampleCount) + { + float[] iSamples = new float[sampleCount]; + float[] qSamples = new float[sampleCount]; + float cosineAngle = (float)FastMath.cos(getAnglePerSample()); + float sineAngle = (float)FastMath.sin(getAnglePerSample()); + + float previousInphase, previousQuadrature, gain; + + previousInphase = mPreviousInphase; + previousQuadrature = mPreviousQuadrature; + + int gainPointer = 0; + + for(int samplePointer = 0; samplePointer < iSamples.length; samplePointer++) + { + if(++gainPointer % 100 == 0) + { + gainPointer = 0; + gain = (3.0f - ((previousInphase * previousInphase) + (previousQuadrature * previousQuadrature))) / 2.0f; + + iSamples[samplePointer] = ((previousInphase * cosineAngle) - (previousQuadrature * sineAngle)) * gain; + qSamples[samplePointer] = ((previousInphase * sineAngle) + (previousQuadrature * cosineAngle)) * gain; + } + else + { + iSamples[samplePointer] = ((previousInphase * cosineAngle) - (previousQuadrature * sineAngle)); + qSamples[samplePointer] = ((previousInphase * sineAngle) + (previousQuadrature * cosineAngle)); + } + + previousInphase = iSamples[samplePointer]; + previousQuadrature = qSamples[samplePointer]; + } + + mPreviousInphase = previousInphase; + mPreviousQuadrature = previousQuadrature; + + return new ComplexSamples(iSamples, qSamples); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarRealOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarRealOscillator.java new file mode 100644 index 00000000..faf0a00f --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarRealOscillator.java @@ -0,0 +1,49 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +public class ScalarRealOscillator extends AbstractOscillator implements IRealOscillator +{ + /** + * Constructs an instance + * + * @param frequency in hertz + * @param sampleRate in hertz + */ + public ScalarRealOscillator(double frequency, double sampleRate) + { + super(frequency, sampleRate); + } + + @Override + public float[] generate(int sampleCount) + { + float[] samples = new float[sampleCount]; + + for(int samplePointer = 0; samplePointer < sampleCount; samplePointer++) + { + mCurrentPhase += mAnglePerSample; + mCurrentPhase %= TWO_PI; + samples[samplePointer] = (float)Math.sin(mCurrentPhase); + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/VectorComplexOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/VectorComplexOscillator.java new file mode 100644 index 00000000..3c362dde --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/VectorComplexOscillator.java @@ -0,0 +1,189 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.util.FastMath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +/** + * Complex oscillator that uses JDK17 SIMD vector operations to generate complex sample arrays. + * + * Note: this class uses a bank of oscillators that are each rotated synchronously, where the oscillator is similar to + * the ComplexOscillator class, but where each oscillator is offset in phase by one sample more than the previous and + * the entire bank is rotated at the sample phase times the SIMD lane width for each sample generation increment. + */ +public class VectorComplexOscillator extends AbstractOscillator implements IComplexOscillator +{ + private static final Logger mLog = LoggerFactory.getLogger(VectorComplexOscillator.class); + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + + private float[] mPreviousInphases; + private float[] mPreviousQuadratures; + private float[] mGainInitials; //Set to 3.0f as the first constant in the gain calculation + + /** + * Constructs an instance + * + * @param frequency in hertz + * @param sampleRate in hertz + */ + public VectorComplexOscillator(double frequency, double sampleRate) + { + super(frequency, sampleRate); + + mGainInitials = new float[VECTOR_SPECIES.length()]; + Arrays.fill(mGainInitials, 3.0f); + } + + @Override + protected void update() + { + super.update(); + + float cosineAngle = (float)FastMath.cos(getAnglePerSample()); + float sineAngle = (float)FastMath.sin(getAnglePerSample()); + + if(mPreviousInphases == null || mPreviousQuadratures == null) + { + mPreviousInphases = new float[VECTOR_SPECIES.length()]; + mPreviousQuadratures = new float[VECTOR_SPECIES.length()]; + + mPreviousInphases[0] = 1.0f; + } + + float gain; + + //Setup the previous sample arrays where each index is offset one sample of rotation from the previous sample. + // We don't touch index 0 so that it can maintain the previous phase offset and all other indices are updated + // relative to index 0. + for(int x = 1; x < VECTOR_SPECIES.length(); x++) + { + gain = (3.0f - ((mPreviousInphases[x - 1] * mPreviousInphases[x - 1]) + + (mPreviousQuadratures[x - 1] * mPreviousQuadratures[x - 1]))) / 2.0f; + mPreviousInphases[x] = ((mPreviousInphases[x - 1] * cosineAngle) - (mPreviousQuadratures[x - 1] * sineAngle)) * gain; + mPreviousQuadratures[x] = ((mPreviousInphases[x - 1] * sineAngle) + (mPreviousQuadratures[x -1] * cosineAngle)) * gain; + } + } + + /** + * Generates complex samples. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return generated samples + */ + @Override public float[] generate(int sampleCount) + { + if(sampleCount % VECTOR_SPECIES.length() != 0) + { + throw new IllegalArgumentException("Requested sample count [" + sampleCount + + "] must be a power of 2 and a multiple of the SIMD lane width [" + VECTOR_SPECIES.length() + "]"); + } + + float[] samples = new float[sampleCount * 2]; + FloatVector previousInphase = FloatVector.fromArray(VECTOR_SPECIES, mPreviousInphases, 0); + FloatVector previousQuadrature = FloatVector.fromArray(VECTOR_SPECIES, mPreviousQuadratures, 0); + FloatVector gainInitials = FloatVector.fromArray(VECTOR_SPECIES, mGainInitials, 0); + + //Sine and cosine angle per sample, with the rotation angle multiplied by the SIMD lane width + float cosAngle = (float)(FastMath.cos(getAnglePerSample() * VECTOR_SPECIES.length())); + float sinAngle = (float)(FastMath.sin(getAnglePerSample() * VECTOR_SPECIES.length())); + + FloatVector gain, inphase, quadrature; + + int gainCounter = 0; + + for(int samplePointer = 0; samplePointer < sampleCount; samplePointer += VECTOR_SPECIES.length()) + { + if(++gainCounter % 10 == 0) + { + gain = gainInitials.sub(previousInphase.pow(2.0f).add(previousQuadrature.pow(2.0f))).div(2.0f); + inphase = previousInphase.mul(cosAngle).sub(previousQuadrature.mul(sinAngle)).mul(gain); + quadrature = previousInphase.mul(sinAngle).add(previousQuadrature.mul(cosAngle)).mul(gain); + } + else + { + inphase = previousInphase.mul(cosAngle).sub(previousQuadrature.mul(sinAngle)); + quadrature = previousInphase.mul(sinAngle).add(previousQuadrature.mul(cosAngle)); + } + + float[] interleaved = VectorUtilities.interleave(inphase, quadrature); + System.arraycopy(interleaved, 0, samples, samplePointer * 2, interleaved.length); + + previousInphase = inphase; + previousQuadrature = quadrature; + } + + previousInphase.intoArray(mPreviousInphases, 0); + previousQuadrature.intoArray(mPreviousQuadratures, 0); + + return samples; + } + + /** + * Generates complex samples. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return generated samples + */ + @Override public ComplexSamples generateComplexSamples(int sampleCount) + { + if(sampleCount % VECTOR_SPECIES.length() != 0) + { + throw new IllegalArgumentException("Requested sample count [" + sampleCount + + "] must be a power of 2 and a multiple of the SIMD lane width [" + VECTOR_SPECIES.length() + "]"); + } + + float[] iSamples = new float[sampleCount]; + float[] qSamples = new float[sampleCount]; + + FloatVector previousInphase = FloatVector.fromArray(VECTOR_SPECIES, mPreviousInphases, 0); + FloatVector previousQuadrature = FloatVector.fromArray(VECTOR_SPECIES, mPreviousQuadratures, 0); + FloatVector gainInitials = FloatVector.fromArray(VECTOR_SPECIES, mGainInitials, 0); + + //Sine and cosine angle per sample, with the rotation angle multiplied by the SIMD lane width + float cosAngle = (float)(FastMath.cos(getAnglePerSample() * VECTOR_SPECIES.length())); + float sinAngle = (float)(FastMath.sin(getAnglePerSample() * VECTOR_SPECIES.length())); + + FloatVector gain, inphase, quadrature; + + for(int samplePointer = 0; samplePointer < sampleCount; samplePointer += VECTOR_SPECIES.length()) + { + gain = gainInitials.sub(previousInphase.pow(2.0f).add(previousQuadrature.pow(2.0f))).div(2.0f); + inphase = previousInphase.mul(cosAngle).sub(previousQuadrature.mul(sinAngle)).mul(gain); + quadrature = previousInphase.mul(sinAngle).add(previousQuadrature.mul(cosAngle)).mul(gain); + + inphase.intoArray(iSamples, samplePointer); + quadrature.intoArray(qSamples, samplePointer); + + previousInphase = inphase; + previousQuadrature = quadrature; + } + + previousInphase.intoArray(mPreviousInphases, 0); + previousQuadrature.intoArray(mPreviousQuadratures, 0); + + return new ComplexSamples(iSamples, qSamples); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/VectorRealOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/VectorRealOscillator.java new file mode 100644 index 00000000..f9dd5523 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/VectorRealOscillator.java @@ -0,0 +1,63 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.oscillator; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Real oscillator that uses SIMD vector intrinsics from JDK17+ Project Panama. + */ +public class VectorRealOscillator extends AbstractOscillator implements IRealOscillator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private float[] mOffsets; + + public VectorRealOscillator(double frequency, double sampleRate) + { + super(frequency, sampleRate); + + mOffsets = new float[VECTOR_SPECIES.length()]; + for(int x = 0; x < mOffsets.length; x++) + { + mOffsets[x] = x + 1.0f; + } + } + + @Override + public float[] generate(int sampleCount) + { + float[] samples = new float[sampleCount]; + + for(int samplePointer = 0; samplePointer < sampleCount; samplePointer += VECTOR_SPECIES.length()) + { + FloatVector generated = FloatVector.fromArray(VECTOR_SPECIES, mOffsets, 0); + generated = generated.mul(mAnglePerSample); + generated = generated.add(mCurrentPhase); + mCurrentPhase = generated.lane(VECTOR_SPECIES.length() - 1); + mCurrentPhase %= TWO_PI; + generated = generated.lanewise(VectorOperators.SIN); + generated.intoArray(samples, samplePointer); + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java index 5bec6eb5..21f056f7 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java @@ -1,31 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.psk; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; import org.apache.commons.math3.util.FastMath; public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionDirectedDemodulator @@ -35,7 +32,7 @@ public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionD private Listener mComplexSymbolListener; private Listener mPLLErrorListener; private Listener mPLLFrequencyListener; - private Listener mFilteredGainAppliedComplexBufferListener; + private Listener mFilteredGainAppliedComplexBufferListener; private double mSampleRate; /** @@ -58,15 +55,14 @@ public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionD } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { if(mFilteredGainAppliedComplexBufferListener != null) { - reusableComplexBuffer.incrementUserCount(); - mFilteredGainAppliedComplexBufferListener.receive(reusableComplexBuffer); + mFilteredGainAppliedComplexBufferListener.receive(complexSamples); } - super.receive(reusableComplexBuffer); + super.receive(complexSamples); } /** @@ -148,12 +144,4 @@ public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionD { mPLLFrequencyListener = listener; } - - /** - * Regsiters the listener to receive complex sample buffers that have been filtered with automatic gain control applied - */ - public void setFilteredGainAppliedComplexBufferListener(Listener listener) - { - mFilteredGainAppliedComplexBufferListener = listener; - } } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java index 6b77c083..9f6df37a 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java @@ -1,31 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.psk; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; import org.apache.commons.math3.util.FastMath; public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator @@ -35,7 +32,7 @@ public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator private Listener mComplexSymbolListener; private Listener mPLLErrorListener; private Listener mPLLFrequencyListener; - private Listener mFilteredGainAppliedComplexBufferListener; + private Listener mFilteredGainAppliedComplexBufferListener; private double mSampleRate; /** @@ -55,15 +52,14 @@ public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { if(mFilteredGainAppliedComplexBufferListener != null) { - reusableComplexBuffer.incrementUserCount(); - mFilteredGainAppliedComplexBufferListener.receive(reusableComplexBuffer); + mFilteredGainAppliedComplexBufferListener.receive(complexSamples); } - super.receive(reusableComplexBuffer); + super.receive(complexSamples); } /** @@ -149,7 +145,7 @@ public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator /** * Registers the listener to receive complex sample buffers that have been filtered with automatic gain control applied */ - public void setFilteredGainAppliedComplexBufferListener(Listener listener) + public void setFilteredGainAppliedComplexBufferListener(Listener listener) { mFilteredGainAppliedComplexBufferListener = listener; } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/PSKDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/PSKDemodulator.java index aebc59e6..3acf1662 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/PSKDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/PSKDemodulator.java @@ -1,25 +1,28 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.psk; import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; import io.github.dsheirer.sample.complex.ComplexSampleListener; +import io.github.dsheirer.sample.complex.ComplexSamples; public abstract class PSKDemodulator implements ComplexSampleListener { @@ -77,19 +80,18 @@ public abstract class PSKDemodulator implements ComplexSampleListener } /** - * Processes a (filtered) buffer containing complex samples for decoding - * @param reusableComplexBuffer with complex samples + * Processes a complex sample buffer + * @param samples */ - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - float[] samples = reusableComplexBuffer.getSamples(); + float[] i = samples.i(); + float[] q = samples.q(); - for(int x = 0; x < samples.length; x += 2) + for(int x = 0; x < i.length; x++) { - receive(samples[x], samples[x + 1]); + receive(i[x], q[x]); } - - reusableComplexBuffer.decrementUserCount(); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java b/src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java index 3311d031..42aa0bd8 100644 --- a/src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java +++ b/src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java @@ -1,8 +1,26 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.squelch; import io.github.dsheirer.dsp.filter.iir.SinglePoleIirFilter; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +34,7 @@ public class PowerMonitor private int mPowerLevelBroadcastCount = 0; private int mPowerLevelBroadcastThreshold; private Listener mSourceEventListener; - private SinglePoleIirFilter mPowerFilter = new SinglePoleIirFilter(0.1); + private SinglePoleIirFilter mPowerFilter = new SinglePoleIirFilter(0.1f); /** * Constructs an instance @@ -42,7 +60,7 @@ public class PowerMonitor * @param inphase complex sample component * @param quadrature complex sample component */ - public void process(double inphase, double quadrature) + public void process(float inphase, float quadrature) { mPowerLevelBroadcastCount++; @@ -59,20 +77,13 @@ public class PowerMonitor } /** - * Processes a complex baseband sample buffer. Note: this method does not decrement the user count - * on the buffer. - * - * @param buffer to process + * Processes I&Q complex baseband sample buffers. */ - public void process(ReusableComplexBuffer buffer) + public void process(float[] i, float[] q) { - float[] samples = buffer.getSamples(); - int offset; - - for(int x = 0; x < buffer.getSampleCount(); x++) + for(int x = 0; x < i.length; x++) { - offset = x * 2; - process(samples[offset], samples[offset + 1]); + process(i[x], q[x]); } } diff --git a/src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java b/src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java index 3991a3ff..8a59b6f7 100644 --- a/src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java +++ b/src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java @@ -42,7 +42,7 @@ public class PowerSquelch implements Listener * @param ramp count of samples before transition between mute and unmute. Setting this value to zero * causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping. */ - public PowerSquelch(double alpha, double squelchThreshold, int ramp) + public PowerSquelch(float alpha, double squelchThreshold, int ramp) { mFilter = new SinglePoleIirFilter(alpha); setSquelchThreshold(squelchThreshold); @@ -85,9 +85,19 @@ public class PowerSquelch implements Listener * @param inphase complex sample component * @param quadrature complex sample component */ - public void process(double inphase, double quadrature) + public void process(float inphase, float quadrature) { - mPower = mFilter.filter(inphase * inphase + quadrature * quadrature); + process(inphase * inphase + quadrature * quadrature); + } + + /** + * Processes a complex IQ sample and changes squelch state when the signal power is above or below the + * threshold value. + * @param magnitude of a complex sample (inphase * inphase + quadrature * quadrature) + */ + public void process(float magnitude) + { + mPower = mFilter.filter(magnitude); mPowerLevelBroadcastCount++; if(mPowerLevelBroadcastCount % mPowerLevelBroadcastThreshold == 0) diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java b/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java index 7a87c079..493788a0 100644 --- a/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java +++ b/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,32 +14,30 @@ * * 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; import io.github.dsheirer.bits.IBinarySymbolProcessor; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; + /** * Assembles reusable byte buffers from an incoming stream of boolean values. */ -public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReusableByteBufferProvider +public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(BinaryToByteBufferAssembler.class); - private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("BinaryToByteBufferAssembler"); - private ReusableByteBuffer mCurrentBuffer; - private int mBufferPointer; + private ByteBuffer mCurrentBuffer; private int mBufferSize; private byte mCurrentByte; private int mBitCount; - private Listener mBufferListener; + private Listener mBufferListener; /** * Constructs an assembler to produce reusable byte buffers of the specified size @@ -64,8 +61,7 @@ public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReu mBufferListener.receive(mCurrentBuffer); } - mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize); - mBufferPointer = 0; + mCurrentBuffer = ByteBuffer.allocate(mBufferSize); } @Override @@ -82,11 +78,11 @@ public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReu if(mBitCount >= 8) { - mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte; + mCurrentBuffer.put(mCurrentByte); mCurrentByte = 0; mBitCount = 0; - if(mBufferPointer >= mBufferSize) + if(!mCurrentBuffer.hasRemaining()) { getNextBuffer(); } @@ -98,7 +94,7 @@ public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReu * Registers the listener to receive fully assembled byte buffers from this assembler. */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mBufferListener = listener; } @@ -107,7 +103,7 @@ public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReu * Removes the listener from receiving buffers from this assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mBufferListener = null; } diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java b/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java index edc34e28..5116e70a 100644 --- a/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java +++ b/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java @@ -1,41 +1,42 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.dsp.symbol; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; + /** * Assembles reusable byte buffers from an incoming stream of Dibits. */ -public class DibitToByteBufferAssembler implements Listener, IReusableByteBufferProvider +public class DibitToByteBufferAssembler implements Listener, IByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(DibitToByteBufferAssembler.class); - private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("DibitToByteBufferAssembler"); - private ReusableByteBuffer mCurrentBuffer; - private int mBufferPointer; + private ByteBuffer mCurrentBuffer; private int mBufferSize; private byte mCurrentByte; private int mDibitCount; - private Listener mBufferListener; + private Listener mBufferListener; /** * Constructs an assembler to produce reusable byte buffers of the specified size @@ -59,8 +60,7 @@ public class DibitToByteBufferAssembler implements Listener, IReusableByt mBufferListener.receive(mCurrentBuffer); } - mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize); - mBufferPointer = 0; + mCurrentBuffer = ByteBuffer.allocate(mBufferSize); } @Override @@ -88,11 +88,11 @@ public class DibitToByteBufferAssembler implements Listener, IReusableByt if(mDibitCount >= 4) { - mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte; + mCurrentBuffer.put(mCurrentByte); mCurrentByte = 0x00; mDibitCount = 0; - if(mBufferPointer >= mBufferSize) + if(!mCurrentBuffer.hasRemaining()) { getNextBuffer(); } @@ -103,7 +103,7 @@ public class DibitToByteBufferAssembler implements Listener, IReusableByt * Registers the listener to receive fully assembled byte buffers from this assembler. */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mBufferListener = listener; } @@ -112,7 +112,7 @@ public class DibitToByteBufferAssembler implements Listener, IReusableByt * Removes the listener from receiving buffers from this assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mBufferListener = null; } diff --git a/src/main/java/io/github/dsheirer/dsp/window/ScalarWindow.java b/src/main/java/io/github/dsheirer/dsp/window/ScalarWindow.java new file mode 100644 index 00000000..4b07f0dd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/ScalarWindow.java @@ -0,0 +1,29 @@ +package io.github.dsheirer.dsp.window; + +/** + * Scalar window implementation. + */ +public class ScalarWindow extends Window +{ + /** + * Constructs an instance. + * + * @param coefficients coefficients + */ + public ScalarWindow(float[] coefficients) + { + super(coefficients); + } + + @Override public float[] apply(float[] samples) + { + validate(samples); + + for(int x = 0; x < mCoefficients.length; x++) + { + samples[x] *= mCoefficients[x]; + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/window/VectorWindow.java b/src/main/java/io/github/dsheirer/dsp/window/VectorWindow.java new file mode 100644 index 00000000..b49901b4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/VectorWindow.java @@ -0,0 +1,45 @@ +package io.github.dsheirer.dsp.window; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector SIMD window implementation. + */ +public class VectorWindow extends Window +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + + /** + * Constructs an instance. + * + * @param coefficients coefficients + */ + public VectorWindow(float[] coefficients) + { + super(coefficients); + } + + @Override public float[] apply(float[] samples) + { + validate(samples); + + int x = 0; + + //Applies the coefficients to the samples in multiples of the SIMD lane width + for(; x < VECTOR_SPECIES.loopBound(mCoefficients.length); x += VECTOR_SPECIES.length()) + { + FloatVector.fromArray(VECTOR_SPECIES, samples, x) + .mul(FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, x)) + .intoArray(samples, x); + } + + //Cleanup loop to calculate the final indices that are not a multiple of the SIMD lane width + for(; x < mCoefficients.length; x++) + { + samples[x] *= mCoefficients[x]; + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/window/Window.java b/src/main/java/io/github/dsheirer/dsp/window/Window.java new file mode 100644 index 00000000..52cf6bfa --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/Window.java @@ -0,0 +1,38 @@ +package io.github.dsheirer.dsp.window; + +/** + * Base window implementation. + */ +public abstract class Window +{ + protected float[] mCoefficients; + + /** + * Constructs an instance. + * @param coefficients coefficients + */ + public Window(float[] coefficients) + { + mCoefficients = coefficients; + } + + /** + * Validates that the window coefficients and samples array lengths are the same. + * @param samples to validate + */ + protected void validate(float[] samples) + { + if(mCoefficients.length != samples.length) + { + throw new IllegalArgumentException("Sample array length [" + samples.length + + "] must match window coefficients length[" + mCoefficients.length + "]"); + } + } + + /** + * Applies this window to the samples + * @param samples to window + * @return windowed samples + */ + public abstract float[] apply(float[] samples); +} diff --git a/src/main/java/io/github/dsheirer/dsp/window/WindowFactory.java b/src/main/java/io/github/dsheirer/dsp/window/WindowFactory.java new file mode 100644 index 00000000..9a52ec38 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/WindowFactory.java @@ -0,0 +1,442 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2021 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.window; + +import org.apache.commons.math3.util.FastMath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +/** + * Window factory and corresponding utility methods. + */ +public class WindowFactory +{ + private final static Logger mLog = LoggerFactory.getLogger(WindowFactory.class); + + private static final float PI = (float)FastMath.PI; + private static final float TWO_PI = (float)FastMath.PI * 2.0f; + private static final float FOUR_PI = (float)FastMath.PI * 4.0f; + private static final float SIX_PI = (float)FastMath.PI * 6.0f; + private static final float EIGHT_PI = (float)FastMath.PI * 8.0f; + + /** + * Creates a window of the specified type and length. + * + * @param type of window + * @param length of window + * @return window of the specified type and length + */ + public static float[] getWindow(WindowType type, int length) + { + switch(type) + { + case BLACKMAN: + return getBlackman(length); + case BLACKMAN_HARRIS_4: + return getBlackmanHarris4(length); + case BLACKMAN_HARRIS_7: + return getBlackmanHarris7(length); + case BLACKMAN_NUTALL: + return getBlackmanNutall(length); + case COSINE: + return getCosine(length); + case FLAT_TOP: + return getFlatTop(length); + case HAMMING: + return getHamming(length); + case HANN: + return getHann(length); + case KAISER: + throw new IllegalArgumentException("Kaiser Window cannot be created via this method. Use the getKaiser() method instead."); + case NUTALL: + return getNutall(length); + case NONE: + default: + return getRectangular(length); + } + } + + /** + * Creates an all-pass or rectangular window. + * + * @param length of the window + * @return rectangular window + */ + public static float[] getRectangular(int length) + { + float[] coefficients = new float[length]; + + Arrays.fill(coefficients, 1.0f); + + return coefficients; + } + + /** + * Creates a cosine window of the specified length. + * + * @param length of the window + * @return cosine window + */ + public static float[] getCosine(int length) + { + float[] coefficients = new float[length]; + + if(length % 2 == 0) //Even length + { + int half = (int)((length - 1) / 2); + + for(int x = -half; x < length / 2 + 1; x++) + { + coefficients[x + half] = (float)FastMath.cos( + ((float)x * PI) / ((float)length + 1.0f)); + } + } + else //Odd length + { + int half = (int)length / 2; + + for(int x = -half; x < half + 1; x++) + { + coefficients[x + half] = (float)FastMath.cos(((float)x * PI) / ((float)length + 1.0f)); + } + } + + return coefficients; + } + + /** + * Creates a 3-term Blackman window. + * + * @param length of the window + * @return Blackman window + */ + public static float[] getBlackman(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + + float a0 = 0.426590713672f; + float a1 = 0.496560619089f; + float a2 = 0.0768486672399f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos((TWO_PI * (float)x) / denominator)) + + (a2 * (float)FastMath.cos((FOUR_PI * (float)x) / denominator)); + } + + return coefficients; + } + + /** + * Creates a 4-term Nutall window + * + * @param length of the window + * @return Nutall window + */ + public static float[] getNutall(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + float a0 = 0.355768f; + float a1 = 0.487396f; + float a2 = 0.144232f; + float a3 = 0.012604f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) + + (a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) - + (a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Nutall window + * + * @param length of the window + * @return Blackman-Nutall window + */ + public static float[] getBlackmanNutall(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + float a0 = 0.3635819f; + float a1 = 0.4891775f; + float a2 = 0.1365995f; + float a3 = 0.0106411f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) + + (a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) - + (a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static float[] getBlackmanHarris4(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + float a0 = 0.35875f; + float a1 = 0.48829f; + float a2 = 0.14128f; + float a3 = 0.01168f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) + + (a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) - + (a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 7-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static float[] getBlackmanHarris7(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + + float a0 = 0.27105140069342f; + float a1 = 0.43329793923448f; + float a2 = 0.21812299954311f; + float a3 = 0.06592544638803f; + float a4 = 0.01081174209837f; + float a5 = 0.00077658482522f; + float a6 = 0.00001388721735f; + + + for(int x = 0; x < length; x++) + { + float w = TWO_PI * (float)x / denominator; + + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(w)) + + (a2 * (float)FastMath.cos(2.0 * w)) - + (a3 * (float)FastMath.cos(3.0 * w)) + + (a4 * (float)FastMath.cos(4.0 * w)) - + (a5 * (float)FastMath.cos(5.0 * w)) + + (a6 * (float)FastMath.cos(6.0 * w)); + } + + return coefficients; + } + + /** + * Creates a Hamming window + * + * @param length of the window + * @return Hamming window + */ + public static float[] getHamming(int length) + { + float[] coefficients = new float[length]; + + for(int x = 0; x < length; x++) + { + coefficients[x] = 0.54f - (0.46f * (float)FastMath.cos((TWO_PI * x) / (length - 1))); + } + + return coefficients; + } + + /** + * Creates a Hann window + * + * @param length of the window + * @return Hann window + */ + public static float[] getHann(int length) + { + float[] coefficients = new float[length]; + + for(int x = 0; x < length; x++) + { + coefficients[x] = 0.5f - (0.5f * (float)FastMath.cos(TWO_PI * (float)x / (length - 1))); + } + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static float[] getFlatTop(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + + float a0 = 0.215578948f; + float a1 = 0.41663158f; + float a2 = 0.277263158f; + float a3 = 0.083578947f; + float a4 = 0.006947368f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) + + (a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) - + (a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)) + + (a4 * (float)FastMath.cos(EIGHT_PI * (float)x / denominator)); + } + + return coefficients; + } + + /** + * Calculates the beta (roll-off factor) for a Kaiser window based on the specified attenuation. + * + * @param attenuation in decibels + * @return beta value in the range of 0.0 to 1.0 + */ + public static float getKaiserBeta(float attenuation) + { + if(attenuation > 50.0) + { + return 0.1102f * (attenuation - 8.7f); + } + else if(attenuation >= 21.0) + { + return (0.5842f * (float)FastMath.pow(attenuation - 21.0, 0.4)) + (0.07886f * (attenuation - 21.0f)); + } + else + { + return 0.0f; + } + } + + /** + * Creates a Kaiser window. + * + * @param length of the window + * @param attenuation desired + * @return window + */ + public static float[] getKaiser(int length, float attenuation) + { + float[] coefficients = new float[length]; + + float beta = getKaiserBeta(attenuation); + + float betaBesselZerothOrder = getBesselZerothOrder(beta); + + float temp; + + for(int x = 0; x < coefficients.length; x++) + { + temp = beta * (float)FastMath.sqrt(1.0 - FastMath.pow(2.0 * x / (length - 1) - 1.0, 2)); + coefficients[x] = getBesselZerothOrder(temp) / betaBesselZerothOrder; + } + + return coefficients; + } + + + /** + * Zeroth order modified Bessel function. + * + * @param x Value. + * @return Return value. + */ + public final static float getBesselZerothOrder(final float x) + { + float f = 1; + final float x2 = x * x * 0.25f; + float xc = x2; + float v = 1 + x2; + for (int i = 2; i < 100; i++) + { + f *= i; + xc *= x2; + final float a = xc / (f * f); + v += a; + if (a < 1e-20) break; + } + + return v; + } + + /** + * Apply the window against an array of float-type samples + */ + public static float[] apply(float[] coefficients, float[] samples) + { + for(int x = 0; x < coefficients.length; x++) + { + samples[x] = samples[x] * coefficients[x]; + } + + return samples; + } + + /** + * Apply the window type against the float array of samples + * + * @param type of window to use + * @param samples to be windowed + * @return windowed samples + */ + public static float[] apply(WindowType type, float[] samples) + { + float[] coefficients = getWindow(type, samples.length); + + return apply(coefficients, samples); + } + + public static void main(String[] args) + { + float[] kaiser = getKaiser(16, 80.0f); + + mLog.debug("Window:" + Arrays.toString(kaiser)); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/window/WindowType.java b/src/main/java/io/github/dsheirer/dsp/window/WindowType.java new file mode 100644 index 00000000..e0fa57c7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/WindowType.java @@ -0,0 +1,38 @@ +package io.github.dsheirer.dsp.window; + +import java.util.EnumSet; + +/** + * Window types + */ +public enum WindowType +{ + BLACKMAN("Blackman"), + BLACKMAN_HARRIS_4("Blackman-Harris 4"), + BLACKMAN_HARRIS_7("Blackman-Harris 7"), + BLACKMAN_NUTALL("Blackman-Nutall"), + COSINE("Cosine"), + FLAT_TOP("Flat Top"), + HAMMING("Hamming"), + HANN("Hann"), + KAISER("Kaiser"), + NUTALL("Nutall"), + NONE("None"); + + private String mLabel; + + WindowType(String label) + { + mLabel = label; + } + + + public String toString() + { + return mLabel; + } + + public static final EnumSet NO_PARAMETER_WINDOWS = EnumSet.of(WindowType.BLACKMAN, WindowType.BLACKMAN_HARRIS_4, + WindowType.BLACKMAN_HARRIS_7, WindowType.BLACKMAN_NUTALL, WindowType.COSINE, WindowType.FLAT_TOP, + WindowType.HAMMING, WindowType.HANN, WindowType.NUTALL); +} diff --git a/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder.java b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder.java index 5a30881b..6a4fa435 100644 --- a/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder.java +++ b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,17 +14,12 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.edac.trellis; import org.apache.commons.math3.util.FastMath; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - public abstract class ViterbiDecoder { private int mInputBitLength; @@ -129,9 +124,8 @@ public abstract class ViterbiDecoder */ public Path decode(int[] transmittedOutputValues) { - Collection survivingPaths = new ArrayList<>(); - - survivingPaths.add(new Path(createStartingNode())); + Path[] survivingPaths = new Path[getInputValueCount()]; + survivingPaths[0] = new Path(createStartingNode()); //Add all but the last transmitted value to the starting path, creating a set of survivors at each iteration. for(int x = 0; x < transmittedOutputValues.length - 1; x++) @@ -149,7 +143,7 @@ public abstract class ViterbiDecoder * returned that represents each path that terminates at a distinct input/state value that has the least error * value. * - * @param survivingPaths from the previous time instant to evaluate for the next time instant in the trellis + * @param previousPaths from the previous time instant to evaluate for the next time instant in the trellis * using the transmitted output value. * * @param transmittedOutputValue that will be decoded for this time instant. @@ -158,9 +152,9 @@ public abstract class ViterbiDecoder * output value * */ - public Collection add(Collection survivingPaths, int transmittedOutputValue) + public Path[] add(Path[] previousPaths, int transmittedOutputValue) { - Map survivingPathMap = new HashMap<>(); + Path[] survivorPaths = new Path[getInputValueCount()]; for(int x = 0; x < getInputValueCount(); x++) { @@ -168,31 +162,34 @@ public abstract class ViterbiDecoder Node node = createNode(x, transmittedOutputValue); //Evaluate each surviving path by creating a copy and adding the node to it and see if it survives - for(Path path: survivingPaths) + for(Path path: previousPaths) { - Path pathToEvaluate = path.copyOf(); - - pathToEvaluate.add(node); - - if(survivingPathMap.containsKey(node.getInputValue())) + if(path != null) { - Path survivingPath = survivingPathMap.get(node.getInputValue()); + Path pathToEvaluate = path.copyOf(); - //Replace the current survivor path if this evaluate path has a lower error value - if(pathToEvaluate.getError() < survivingPath.getError()) + pathToEvaluate.add(node); + + if(survivorPaths[node.getInputValue()] != null) { - survivingPathMap.put(node.getInputValue(), pathToEvaluate); + Path survivingPath = survivorPaths[node.getInputValue()]; + + //Replace the current survivor path if this evaluate path has a lower error value + if(pathToEvaluate.getError() < survivingPath.getError()) + { + survivorPaths[node.getInputValue()] = pathToEvaluate; + } + } + else + { + //No survivor yet -- set this evaluate path as the first survivor + survivorPaths[node.getInputValue()] = pathToEvaluate; } - } - else - { - //No survivor yet -- set this evaluate path as the first survivor - survivingPathMap.put(node.getInputValue(), pathToEvaluate); } } } - return survivingPathMap.values(); + return survivorPaths; } /** @@ -207,7 +204,7 @@ public abstract class ViterbiDecoder * @return the single survivor path that remains after adding the flushing node to each of the candidate argument * survivor paths. */ - public Path flush(Collection survivingPaths, int transmittedOutputValue) + public Path flush(Path[] survivingPaths, int transmittedOutputValue) { Node flushingNode = createFlushingNode(transmittedOutputValue); @@ -215,11 +212,14 @@ public abstract class ViterbiDecoder for(Path survivingPath: survivingPaths) { - survivingPath.add(flushingNode); - - if(bestPath == null || (survivingPath.getError() < bestPath.getError())) + if(survivingPath != null) { - bestPath = survivingPath; + survivingPath.add(flushingNode); + + if(bestPath == null || (survivingPath.getError() < bestPath.getError())) + { + bestPath = survivingPath; + } } } diff --git a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java index accf5a29..f7529795 100644 --- a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java +++ b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.gui; @@ -37,6 +34,7 @@ import io.github.dsheirer.gui.playlist.channelMap.ViewChannelMapEditorRequest; import io.github.dsheirer.gui.preference.PreferenceEditorType; import io.github.dsheirer.gui.preference.UserPreferencesEditor; import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; +import io.github.dsheirer.gui.preference.calibration.CalibrationDialog; import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.jmbe.JmbeEditor; import io.github.dsheirer.jmbe.JmbeEditorRequest; @@ -68,6 +66,7 @@ public class JavaFxWindowManager extends Application public static final String ICON_MANAGER = "iconmanager"; public static final String PLAYLIST_EDITOR = "playlist"; public static final String USER_PREFERENCES_EDITOR = "preferences"; + public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog"; public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map"; public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager"; public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor"; @@ -187,6 +186,12 @@ public class JavaFxWindowManager extends Application Platform.exit(); } + public CalibrationDialog getCalibrationDialog(UserPreferences userPreferences) + { + createJFXPanel(); + return new CalibrationDialog(userPreferences); + } + public Stage getIconManagerStage() { if(mIconManagerStage == null) diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java index b2691b74..3e499d0b 100644 --- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java +++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -33,7 +33,10 @@ import io.github.dsheirer.controller.channel.ChannelSelectionManager; import io.github.dsheirer.eventbus.MyEventBus; import io.github.dsheirer.gui.icon.ViewIconManagerRequest; import io.github.dsheirer.gui.playlist.ViewPlaylistRequest; +import io.github.dsheirer.gui.preference.CalibrateRequest; +import io.github.dsheirer.gui.preference.PreferenceEditorType; import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; +import io.github.dsheirer.gui.preference.calibration.CalibrationDialog; import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.log.ApplicationLog; import io.github.dsheirer.map.MapService; @@ -55,6 +58,9 @@ import io.github.dsheirer.spectrum.ShowTunerMenuItem; import io.github.dsheirer.spectrum.SpectralDisplayPanel; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import javafx.application.Platform; +import javafx.scene.control.ButtonType; import jiconfont.icons.font_awesome.FontAwesome; import jiconfont.swing.IconFontSwing; import net.miginfocom.swing.MigLayout; @@ -62,24 +68,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JSeparator; -import javax.swing.KeyStroke; -import javax.swing.UIManager; +import javax.swing.*; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.plaf.metal.MetalLookAndFeel; -import java.awt.AWTException; -import java.awt.Desktop; -import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.Point; -import java.awt.Robot; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; @@ -92,6 +85,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Locale; +import java.util.Optional; public class SDRTrunk implements Listener { @@ -170,6 +164,11 @@ public class SDRTrunk implements Listener EventLogManager eventLogManager = new EventLogManager(aliasModel, mUserPreferences); mPlaylistManager = new PlaylistManager(mUserPreferences, mSourceManager, aliasModel, eventLogManager, mIconModel); mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences, mPlaylistManager); + + CalibrationManager calibrationManager = CalibrationManager.getInstance(mUserPreferences); + final boolean calibrating = !calibrationManager.isCalibrated() && + !mUserPreferences.getVectorCalibrationPreference().isHideCalibrationDialog(); + new ChannelSelectionManager(mPlaylistManager.getChannelModel()); AudioPlaybackManager audioPlaybackManager = new AudioPlaybackManager(mUserPreferences); @@ -215,7 +214,29 @@ public class SDRTrunk implements Listener try { mMainGui.setVisible(true); - autoStartChannels(); + + if(calibrating) + { + Platform.runLater(() -> + { + CalibrationDialog calibrationDialog = mJavaFxWindowManager.getCalibrationDialog(mUserPreferences); + Optional calibrate = calibrationDialog.showAndWait(); + if(calibrate.isPresent() && calibrate.get().getText().equals("Calibrate")) + { + //Request focus and execute calibration + MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.VECTOR_CALIBRATION)); + MyEventBus.getGlobalEventBus().post(new CalibrateRequest()); + } + else + { + autoStartChannels(); + } + }); + } + else + { + autoStartChannels(); + } } catch(Exception e) { diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java index db8754ce..b70e8f8e 100644 --- a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,14 +14,15 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.gui.channelizer; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -32,7 +32,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; import io.github.dsheirer.source.tuner.test.TestTuner; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.ComplexDftProcessor; import io.github.dsheirer.spectrum.DFTSize; import io.github.dsheirer.spectrum.SpectrumPanel; import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; @@ -40,16 +40,10 @@ import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.JToggleButton; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import java.awt.Dimension; -import java.awt.EventQueue; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; @@ -85,8 +79,6 @@ public class ChannelizerViewer extends JFrame public ChannelizerViewer(int channelsPerRow) { mTestTuner = new TestTuner(new UserPreferences()); - -// mChannelCount = (int)(mTestTuner.getTunerController().getUsableBandwidth() / CHANNEL_BANDWIDTH); mChannelCount = (int)(mTestTuner.getTunerController().getBandwidth() / CHANNEL_BANDWIDTH); mChannelsPerRow = channelsPerRow; @@ -310,9 +302,9 @@ public class ChannelizerViewer extends JFrame return tunerChannels; } - public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor + public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor { - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; @@ -323,20 +315,19 @@ public class ChannelizerViewer extends JFrame mSpectrumPanel.setSampleSize(28); add(mSpectrumPanel); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); } public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - mDFTProcessor.receive(reusableComplexBuffer); + mComplexDftProcessor.receive(nativeBuffer); } @Override @@ -346,10 +337,10 @@ public class ChannelizerViewer extends JFrame } } - public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor + public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor { private TunerChannelSource mSource; - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; private JToggleButton mLoggingButton; @@ -362,8 +353,7 @@ public class ChannelizerViewer extends JFrame mSpectrumPanel.setSampleSize(32); add(mSpectrumPanel, "span"); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); @@ -371,21 +361,7 @@ public class ChannelizerViewer extends JFrame if(mSource != null) { - mSource.setListener(new Listener() - { - @Override - public void receive(ReusableComplexBuffer complexBuffer) - { - if(mLoggingEnabled) - { - mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples())); - } - - complexBuffer.incrementUserCount(); - mDFTProcessor.receive(complexBuffer); - complexBuffer.decrementUserCount(); - } - }); + mSource.setListener(complexSamples -> mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()))); mSource.start(); } @@ -423,15 +399,13 @@ public class ChannelizerViewer extends JFrame public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { - reusableComplexBuffer.incrementUserCount(); - mDFTProcessor.receive(reusableComplexBuffer); - reusableComplexBuffer.decrementUserCount(); + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); } @Override @@ -441,12 +415,12 @@ public class ChannelizerViewer extends JFrame } } - public class DiscreteChannelPanel extends JPanel implements Listener, ISourceEventProcessor + public class DiscreteChannelPanel extends JPanel implements Listener, ISourceEventProcessor { private final Logger mLog = LoggerFactory.getLogger(DiscreteChannelPanel.class); private TunerChannelSource mSource; - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; private JToggleButton mLoggingButton; @@ -472,25 +446,22 @@ public class ChannelizerViewer extends JFrame }); // add(mLoggingButton); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(25000.0)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); if(mSource != null) { - mSource.setListener(new Listener() + mSource.setListener(new Listener() { @Override - public void receive(ReusableComplexBuffer complexBuffer) + public void receive(ComplexSamples complexSamples) { if(mLoggingEnabled) { - mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples())); + mLog.debug("Samples:" + Arrays.toString(complexSamples.toInterleaved().samples())); } - complexBuffer.incrementUserCount(); - mDFTProcessor.receive(complexBuffer); - complexBuffer.decrementUserCount(); + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); } }); @@ -509,15 +480,13 @@ public class ChannelizerViewer extends JFrame public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { - reusableComplexBuffer.incrementUserCount(); - mDFTProcessor.receive(reusableComplexBuffer); - reusableComplexBuffer.decrementUserCount(); + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); } @Override @@ -579,12 +548,11 @@ public class ChannelizerViewer extends JFrame for(TunerChannelSource tunerChannelSource : sources) { - tunerChannelSource.setListener(new Listener() + tunerChannelSource.setListener(new Listener() { @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { - reusableComplexBuffer.decrementUserCount(); } }); diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java index 30b907f0..cde0495c 100644 --- a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,14 +14,15 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.gui.channelizer; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -33,7 +33,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; import io.github.dsheirer.source.tuner.test.TestTuner; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.ComplexDftProcessor; import io.github.dsheirer.spectrum.DFTSize; import io.github.dsheirer.spectrum.SpectrumPanel; import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; @@ -41,22 +41,13 @@ import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.JToggleButton; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import java.awt.Dimension; -import java.awt.EventQueue; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class ChannelizerViewer2 extends JFrame @@ -325,9 +316,9 @@ public class ChannelizerViewer2 extends JFrame return tunerChannels; } - public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor + public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor { - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; @@ -338,20 +329,19 @@ public class ChannelizerViewer2 extends JFrame mSpectrumPanel.setSampleSize(16); add(mSpectrumPanel); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); } public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - mDFTProcessor.receive(reusableComplexBuffer); + mComplexDftProcessor.receive(nativeBuffer); } @Override @@ -361,10 +351,10 @@ public class ChannelizerViewer2 extends JFrame } } - public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor + public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor { private TunerChannelSource mSource; - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; private JToggleButton mLoggingButton; @@ -377,8 +367,7 @@ public class ChannelizerViewer2 extends JFrame mSpectrumPanel.setSampleSize(16); add(mSpectrumPanel, "span"); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); @@ -387,20 +376,9 @@ public class ChannelizerViewer2 extends JFrame if(mSource != null) { mLog.debug("Channel: " + mSource.getTunerChannel() + " Rate:" + mSource.getSampleRate()); - mSource.setListener(new Listener() + mSource.setListener((Listener) complexSamples -> { - @Override - public void receive(ReusableComplexBuffer complexBuffer) - { - if(mLoggingEnabled) - { - mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples())); - } - - complexBuffer.incrementUserCount(); - mDFTProcessor.receive(complexBuffer); - complexBuffer.decrementUserCount(); - } + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); }); mSource.start(); @@ -423,14 +401,7 @@ public class ChannelizerViewer2 extends JFrame } mLoggingButton = new JToggleButton("Logging"); - mLoggingButton.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - mLoggingEnabled = mLoggingButton.isSelected(); - } - }); + mLoggingButton.addActionListener(e -> mLoggingEnabled = mLoggingButton.isSelected()); } public TunerChannelSource getSource() @@ -440,15 +411,13 @@ public class ChannelizerViewer2 extends JFrame public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - reusableComplexBuffer.incrementUserCount(); - mDFTProcessor.receive(reusableComplexBuffer); - reusableComplexBuffer.decrementUserCount(); + mComplexDftProcessor.receive(nativeBuffer); } @Override diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/HeterodyneChannelizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/HeterodyneChannelizerViewer.java new file mode 100644 index 00000000..99dbd6e2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/channelizer/HeterodyneChannelizerViewer.java @@ -0,0 +1,566 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.channelizer; + +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.settings.SettingsManager; +import io.github.dsheirer.source.ISourceEventProcessor; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.source.SourceException; +import io.github.dsheirer.source.tuner.Tuner; +import io.github.dsheirer.source.tuner.channel.ChannelSpecification; +import io.github.dsheirer.source.tuner.channel.TunerChannel; +import io.github.dsheirer.source.tuner.channel.TunerChannelSource; +import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; +import io.github.dsheirer.source.tuner.test.TestTuner; +import io.github.dsheirer.spectrum.ComplexDftProcessor; +import io.github.dsheirer.spectrum.DFTSize; +import io.github.dsheirer.spectrum.SpectrumPanel; +import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class HeterodyneChannelizerViewer extends JFrame +{ + private final static Logger mLog = LoggerFactory.getLogger(HeterodyneChannelizerViewer.class); + + private static final int CHANNEL_BANDWIDTH = 12500; + private static final int CHANNEL_FFT_FRAME_RATE = 20; + + private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel()); + private JPanel mPrimaryPanel; + private JPanel mControlPanel; + private JLabel mToneFrequencyLabel; + private PrimarySpectrumPanel mPrimarySpectrumPanel; + private ChannelArrayPanel mChannelPanel; + private DiscreteIndexChannelArrayPanel mDiscreteIndexChannelPanel; + private int mChannelCount; + private int mChannelsPerRow; + private long mBaseFrequency = 100000000; //100 MHz + private DFTSize mMainPanelDFTSize = DFTSize.FFT04096; + private DFTSize mChannelPanelDFTSize = DFTSize.FFT04096; + private TestTuner mTestTuner; + + /** + * GUI Test utility for researching channelizers. + */ + public HeterodyneChannelizerViewer() + { + mTestTuner = new TestTuner(new UserPreferences()); + mChannelCount = 5; + mChannelsPerRow = 5; + init(); + } + + private void init() + { + setTitle("Heterodyne Channelizer Viewer"); + setSize(1200, 800); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + setLocationRelativeTo(null); + add(getPrimaryPanel()); + } + + private JPanel getPrimaryPanel() + { + if(mPrimaryPanel == null) + { + mPrimaryPanel = new JPanel(); + mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][][grow,fill][grow,fill]")); + mPrimaryPanel.add(getSpectrumPanel(), "wrap"); + mPrimaryPanel.add(getControlPanel(), "wrap"); + mPrimaryPanel.add(getChannelArrayPanel(), "wrap"); +// mPrimaryPanel.add(getDiscreteIndexChannelPanel()); + } + + return mPrimaryPanel; + } + + private PrimarySpectrumPanel getSpectrumPanel() + { + if(mPrimarySpectrumPanel == null) + { + mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager, + mTestTuner.getTunerController().getSampleRate()); + mPrimarySpectrumPanel.setPreferredSize(new Dimension(1200, 200)); + mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize); + mTestTuner.getTunerController().addBufferListener(mPrimarySpectrumPanel); + } + + return mPrimarySpectrumPanel; + } + + private JPanel getControlPanel() + { + if(mControlPanel == null) + { + mControlPanel = new JPanel(); + mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", "")); + + mControlPanel.add(new JLabel("Tone:"), "align left"); + long minimumFrequency = -(long)mTestTuner.getTunerController().getSampleRate() / 2 + 1; + long maximumFrequency = (long)mTestTuner.getTunerController().getSampleRate() / 2 - 1; + long toneFrequency = 0; + + SpinnerNumberModel model = new SpinnerNumberModel(toneFrequency, minimumFrequency, maximumFrequency, + 100); + + model.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent e) + { + long toneFrequency = model.getNumber().longValue(); + mTestTuner.getTunerController().setToneFrequency(toneFrequency); + mToneFrequencyLabel.setText(String.valueOf(getToneFrequency())); + } + }); + + JSpinner spinner = new JSpinner(model); + + mControlPanel.add(spinner); + mControlPanel.add(new JLabel("Hz")); + + mControlPanel.add(new JLabel("Frequency:"), "push,align right"); + mToneFrequencyLabel = new JLabel(String.valueOf(getToneFrequency())); + mControlPanel.add(mToneFrequencyLabel, "push,align left"); + + mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right"); + } + + return mControlPanel; + } + + private long getToneFrequency() + { + return mTestTuner.getTunerController().getFrequency() + mTestTuner.getTunerController().getToneFrequency(); + } + + private ChannelArrayPanel getChannelArrayPanel() + { + if(mChannelPanel == null) + { + mChannelPanel = new ChannelArrayPanel(); + } + + return mChannelPanel; + } + + private DiscreteIndexChannelArrayPanel getDiscreteIndexChannelPanel() + { + if(mDiscreteIndexChannelPanel == null) + { + mDiscreteIndexChannelPanel = new DiscreteIndexChannelArrayPanel(); + } + + return mDiscreteIndexChannelPanel; + } + + public class ChannelArrayPanel extends JPanel + { + private final Logger mLog = LoggerFactory.getLogger(ChannelArrayPanel.class); + + public ChannelArrayPanel() + { + int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE; + if(bufferSize % 2 == 1) + { + bufferSize++; + } + + init(); + } + + private void init() + { + setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); + + double spectralBandwidth = mTestTuner.getTunerController().getSampleRate(); + double halfSpectralBandwidth = spectralBandwidth / 2.0; + + int channelToLog = -1; + + long baseFrequency = mBaseFrequency + (CHANNEL_BANDWIDTH / 2); + + for(int x = 0; x < mChannelCount; x++) + { + long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH); + + mLog.debug("Channel " + x + "/" + mChannelCount + " Frequency: " + frequency); + + ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH, (x == channelToLog)); + channelPanel.setDFTSize(mChannelPanelDFTSize); + + if(x % mChannelsPerRow == mChannelsPerRow - 1) + { + add(channelPanel, "grow,push,wrap 2px"); + } + else + { + add(channelPanel, "grow,push"); + } + } + } + } + + public class DiscreteIndexChannelArrayPanel extends JPanel + { + public DiscreteIndexChannelArrayPanel() + { + int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE; + if(bufferSize % 2 == 1) + { + bufferSize++; + } + + init(); + } + + private void init() + { + setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); + + ChannelSpecification channelSpecification = new ChannelSpecification(25000.0, 12500, 6000.0, 6250.0); + for(int x = 0; x < mChannelCount; x++) + { + TunerChannel tunerChannel = new TunerChannel(100000000, 12500); + TunerChannelSource source = mTestTuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); + DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x); + channelPanel.setDFTSize(mChannelPanelDFTSize); + + mLog.debug("Testing Channel [" + x + "] is set to [" + source.getTunerChannel().getFrequency() + "]"); + + if(x % mChannelsPerRow == mChannelsPerRow - 1) + { + add(channelPanel, "grow,push,wrap 2px"); + } + else + { + add(channelPanel, "grow,push"); + } + } + } + } + + /** + * Returns a list of tuner channels that will fit within the tuner's bandwidth, minus a half channel each at the + * lower and upper ends of the spectrum. + * + * @param tuner to create channels for + * @return list of contiguous channels filling the tuner bandwidth + */ + public static List getTunerChannels(Tuner tuner) + { + List tunerChannels = new ArrayList<>(); + + long baseFrequency = tuner.getTunerController().getFrequency(); + baseFrequency -= tuner.getTunerController().getSampleRate() / 2; + baseFrequency += (CHANNEL_BANDWIDTH / 2); + + int channelCount = (int)(tuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH) - 1; + + for(int x = 0; x < channelCount; x++) + { + long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH); + TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH); + tunerChannels.add(tunerChannel); + } + + return tunerChannels; + } + + public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor + { + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + + public PrimarySpectrumPanel(SettingsManager settingsManager, double sampleRate) + { + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(28); + add(mSpectrumPanel); + + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDecibelConverter.addListener(mSpectrumPanel); + } + + public void setDFTSize(DFTSize dftSize) + { + mComplexDftProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(INativeBuffer nativeBuffer) + { + mComplexDftProcessor.receive(nativeBuffer); + } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } + } + + public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor + { + private TunerChannelSource mSource; + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + private JToggleButton mLoggingButton; + private boolean mLoggingEnabled; + + public ChannelPanel(SettingsManager settingsManager, double sampleRate, long frequency, int bandwidth, boolean enableLogging) + { + setLayout(new MigLayout("insets 0 0 0 0", "[center,grow,fill][]", "[grow,fill][]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(32); + add(mSpectrumPanel, "span"); + + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDecibelConverter.addListener(mSpectrumPanel); + + TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); + ChannelSpecification channelSpecification = new ChannelSpecification(25000.0, 12500, 6000.0, 6250.0); + mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); + + if(mSource != null) + { + mSource.setListener(complexSamples -> mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()))); + + mSource.start(); + } + else + { + mLog.error("Couldn't get a source from the tuner for frequency: " + frequency); + } + + if(mSource != null) + { + add(new JLabel("Center:" + frequency)); + } + else + { + add(new JLabel("NO SRC:" + frequency)); + } + + mLoggingButton = new JToggleButton("Logging"); + mLoggingButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + mLoggingEnabled = mLoggingButton.isSelected(); + } + }); +// add(mLoggingButton); + + } + + public TunerChannelSource getSource() + { + return mSource; + } + + public void setDFTSize(DFTSize dftSize) + { + mComplexDftProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(ComplexSamples complexSamples) + { + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); + } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } + } + + public class DiscreteChannelPanel extends JPanel implements Listener, ISourceEventProcessor + { + private final Logger mLog = LoggerFactory.getLogger(DiscreteChannelPanel.class); + + private TunerChannelSource mSource; + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + private JToggleButton mLoggingButton; + private boolean mLoggingEnabled; + + public DiscreteChannelPanel(SettingsManager settingsManager, TunerChannelSource source, int index) + { + mSource = source; + setLayout(new MigLayout("insets 0 0 0 0", "[center,grow,fill][]", "[grow,fill][]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(32); + add(mSpectrumPanel, "span"); + add(new JLabel("Index:" + index)); + + mLoggingButton = new JToggleButton("Logging"); + mLoggingButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + mLoggingEnabled = mLoggingButton.isSelected(); + } + }); +// add(mLoggingButton); + + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDecibelConverter.addListener(mSpectrumPanel); + + if(mSource != null) + { + mSource.setListener(new Listener() + { + @Override + public void receive(ComplexSamples complexSamples) + { + if(mLoggingEnabled) + { + mLog.debug("Samples:" + Arrays.toString(complexSamples.toInterleaved().samples())); + } + + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); + } + }); + + mSource.start(); + } + else + { + mLog.error("Couldn't get a source from the tuner for index: " + index); + } + } + + public TunerChannelSource getSource() + { + return mSource; + } + + public void setDFTSize(DFTSize dftSize) + { + mComplexDftProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(ComplexSamples complexSamples) + { + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); + } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } + } + + + public static void main(String[] args) + { + boolean useGUI = true; + + if(useGUI) + { + final HeterodyneChannelizerViewer frame = new HeterodyneChannelizerViewer(); + + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + frame.setVisible(true); + } + }); + } + else + { + TestTuner tuner = new TestTuner(new UserPreferences()); + + List tunerChannels = getTunerChannels(tuner); + + List sources = new ArrayList<>(); + + int maxSourceCount = 30; + int sourceCount = 0; + for(TunerChannel tunerChannel : tunerChannels) + { + if(sourceCount < maxSourceCount) + { + TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel, null); + + if(source != null) + { + sources.add(source); + sourceCount++; + } + else + { + mLog.debug("Couldn't get source for: " + tunerChannel); + } + } + } + + mLog.debug("Starting [" + sources.size() + "] tuner channel sources"); + + for(TunerChannelSource tunerChannelSource : sources) + { + tunerChannelSource.setListener(new Listener() + { + @Override + public void receive(ComplexSamples complexSamples) + { + } + }); + + tunerChannelSource.start(); + } + + while(true) + { + ; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java index 39b3b3c4..4518134e 100644 --- a/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java @@ -1,35 +1,36 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.channelizer; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2; import io.github.dsheirer.dsp.filter.design.FilterDesignException; -import io.github.dsheirer.dsp.mixer.FS4DownConverter; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; -import io.github.dsheirer.sample.IOverflowListener; +import io.github.dsheirer.dsp.oscillator.FS4DownConverter; +import io.github.dsheirer.dsp.oscillator.IComplexOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; 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.ReusableComplexBufferQueue; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.settings.SettingsManager; -import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.ComplexDftProcessor; import io.github.dsheirer.spectrum.DFTSize; import io.github.dsheirer.spectrum.SpectrumPanel; import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; @@ -38,15 +39,10 @@ import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import java.awt.Dimension; -import java.awt.EventQueue; +import java.awt.*; import java.util.concurrent.TimeUnit; public class SynthesizerViewer extends JFrame @@ -141,9 +137,9 @@ public class SynthesizerViewer extends JFrame return mChannel2Panel; } - public class PrimarySpectrumPanel extends JPanel implements Listener + public class PrimarySpectrumPanel extends JPanel implements Listener { - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; @@ -154,36 +150,26 @@ public class SynthesizerViewer extends JFrame mSpectrumPanel.setSampleSize(16); add(mSpectrumPanel); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(CHANNEL_SAMPLE_RATE)); - mDFTProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDftProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); mComplexDecibelConverter.addListener(mSpectrumPanel); - - mDFTProcessor.setOverflowListener(new IOverflowListener() - { - @Override - public void sourceOverflow(boolean overflow) - { - mLog.debug("Buffer " + (overflow ? "overflow" : "reset")); - } - }); } public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - mDFTProcessor.receive(reusableComplexBuffer); + mComplexDftProcessor.receive(nativeBuffer); } } - public class ChannelPanel extends JPanel implements Listener + public class ChannelPanel extends JPanel implements Listener { - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; private boolean mLoggingEnabled = false; @@ -196,22 +182,21 @@ public class SynthesizerViewer extends JFrame add(mSpectrumPanel, "wrap"); add(channelControlPanel); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(CHANNEL_SAMPLE_RATE)); - mDFTProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDftProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); mComplexDecibelConverter.addListener(mSpectrumPanel); } public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - mDFTProcessor.receive(reusableComplexBuffer); + mComplexDftProcessor.receive(nativeBuffer); } } @@ -241,7 +226,7 @@ public class SynthesizerViewer extends JFrame private static final int MAX_FREQUENCY = 6250; private static final int DEFAULT_FREQUENCY = 50; - private IOscillator mOscillator = new LowPhaseNoiseOscillator(DEFAULT_FREQUENCY, CHANNEL_SAMPLE_RATE); + private IComplexOscillator mOscillator = OscillatorFactory.getComplexOscillator(DEFAULT_FREQUENCY, CHANNEL_SAMPLE_RATE); public ChannelControlPanel() { @@ -264,7 +249,7 @@ public class SynthesizerViewer extends JFrame add(new JLabel("Hz")); } - public IOscillator getOscillator() + public IComplexOscillator getOscillator() { return mOscillator; } @@ -275,7 +260,6 @@ public class SynthesizerViewer extends JFrame private TwoChannelSynthesizerM2 mSynthesizer; private FS4DownConverter mFS4DownConverter = new FS4DownConverter(); private int mSamplesPerCycle = CHANNEL_SAMPLE_RATE / DATA_GENERATOR_FRAME_RATE; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("SynthesizerViewer"); public DataGenerationManager() { @@ -293,22 +277,15 @@ public class SynthesizerViewer extends JFrame @Override public void run() { - ReusableComplexBuffer channel1Buffer = mReusableComplexBufferQueue.getBuffer(mSamplesPerCycle); - getChannel1ControlPanel().getOscillator().generateComplex(channel1Buffer); + ComplexSamples channel1Buffer = getChannel1ControlPanel().getOscillator().generateComplexSamples(mSamplesPerCycle); + ComplexSamples channel2Buffer = getChannel2ControlPanel().getOscillator().generateComplexSamples(mSamplesPerCycle); - ReusableComplexBuffer channel2Buffer = mReusableComplexBufferQueue.getBuffer(mSamplesPerCycle); - getChannel2ControlPanel().getOscillator().generateComplex(channel2Buffer); + ComplexSamples synthesizedBuffer = mSynthesizer.process(channel1Buffer, channel2Buffer); + getChannel1Panel().receive(new FloatNativeBuffer(channel1Buffer, System.currentTimeMillis())); - channel1Buffer.incrementUserCount(); - channel2Buffer.incrementUserCount(); + getChannel2Panel().receive(new FloatNativeBuffer(channel2Buffer, System.currentTimeMillis())); - ReusableComplexBuffer synthesizedBuffer = mSynthesizer.process(channel1Buffer, channel2Buffer); - - getChannel1Panel().receive(channel1Buffer); - - getChannel2Panel().receive(channel2Buffer); - - getSpectrumPanel().receive(synthesizedBuffer); + getSpectrumPanel().receive(new FloatNativeBuffer(synthesizedBuffer, System.currentTimeMillis())); } } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java b/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java index 41019694..2987b195 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java @@ -1,30 +1,27 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.gui.instrument; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import io.github.dsheirer.source.IControllableFileSource; import io.github.dsheirer.source.IFrameLocationListener; import io.github.dsheirer.source.wave.ComplexWaveSource; @@ -65,7 +62,8 @@ public class PlaybackController extends HBox implements IFrameLocationListener private AbstractDecoderPane mSampleRateListener; private IControllableFileSource mControllableFileSource; - private ReusableBufferBroadcaster mReusableBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mRealBufferBroadcaster = new Broadcaster<>(); + private Broadcaster mNativeBufferBroadcaster = new Broadcaster<>(); public PlaybackController() { @@ -96,7 +94,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener { mControllableFileSource = new ComplexWaveSource(file); mControllableFileSource.setListener(this); - ((ComplexWaveSource)mControllableFileSource).setListener(mReusableBufferBroadcaster); + ((ComplexWaveSource)mControllableFileSource).setListener(mNativeBufferBroadcaster); mControllableFileSource.open(); if(mSampleRateListener != null) @@ -130,7 +128,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener { mControllableFileSource = new RealWaveSource(file); mControllableFileSource.setListener(this); - ((RealWaveSource)mControllableFileSource).setListener(mReusableBufferBroadcaster); + ((RealWaveSource)mControllableFileSource).setListener(mRealBufferBroadcaster); mControllableFileSource.open(); if(mSampleRateListener != null) @@ -181,19 +179,35 @@ public class PlaybackController extends HBox implements IFrameLocationListener } /** - * Adds listener to receive complex buffers from this playback + * Adds listener to receive real buffers from this playback */ - public void addListener(Listener listener) + public void addRealListener(Listener listener) { - mReusableBufferBroadcaster.addListener(listener); + mRealBufferBroadcaster.addListener(listener); } /** - * Removes the listener from receiving complex buffers from this playback + * Removes the listener from receiving real buffers from this playback */ - public void removeListener(Listener listener) + public void removeRealListener(Listener listener) { - mReusableBufferBroadcaster.removeListener(listener); + mRealBufferBroadcaster.removeListener(listener); + } + + /** + * Adds listener to receive complex buffers from this playback + */ + public void addComplexListener(Listener listener) + { + mNativeBufferBroadcaster.addListener(listener); + } + + /** + * Removes the listener from receiving compex buffers from this playback + */ + public void removeComplexListener(Listener listener) + { + mNativeBufferBroadcaster.removeListener(listener); } /** diff --git a/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java b/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java index f5e5034b..d94b2183 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,12 +14,13 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.gui.instrument; import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane; import io.github.dsheirer.gui.instrument.decoder.DecoderPaneFactory; +import io.github.dsheirer.gui.instrument.decoder.RealDecoderPane; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder; import javafx.scene.layout.BorderPane; @@ -56,11 +56,26 @@ public class ViewerDesktop extends BorderPane private void setDecoderPane(AbstractDecoderPane decoderPane) { - getPlaybackController().removeListener(getDecoderPane()); + if(getDecoderPane() instanceof RealDecoderPane) + { + getPlaybackController().removeRealListener(getDecoderPane()); + } + else + { + getPlaybackController().removeComplexListener(getDecoderPane()); + } getChildren().remove(getDecoderPane()); mDecoderPane = decoderPane; setCenter(getDecoderPane()); - getPlaybackController().addListener(getDecoderPane()); + + if(getDecoderPane() instanceof RealDecoderPane) + { + getPlaybackController().addRealListener(getDecoderPane()); + } + else + { + getPlaybackController().addComplexListener(getDecoderPane()); + } getPlaybackController().setSampleRateListener(getDecoderPane()); } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexPhaseLineChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexPhaseLineChart.java deleted file mode 100644 index 0d6a15c6..00000000 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexPhaseLineChart.java +++ /dev/null @@ -1,124 +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.gui.instrument.chart; - -import io.github.dsheirer.buffer.ComplexCircularBuffer; -import io.github.dsheirer.dsp.gain.ComplexGain; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.complex.Complex; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.SimpleIntegerProperty; -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.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ComplexPhaseLineChart extends LineChart implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(ComplexPhaseLineChart.class); - - private static final Complex ANGLE_OFFSET_45_DEGREES = Complex.fromAngle(FastMath.PI / 4.0); - private ComplexCircularBuffer mComplexCircularBuffer; - private ComplexGain mComplexGain = new ComplexGain(600.0f); - private ObservableList> mPhaseValues = FXCollections.observableArrayList(); - private IntegerProperty mLengthProperty = new SimpleIntegerProperty(40); - - public ComplexPhaseLineChart(int length) - { - super(new NumberAxis("Time", 1, length - 10, 2), - new NumberAxis("Phase", -FastMath.PI, FastMath.PI, FastMath.PI / 2.0)); - - Series phaseSeries = new Series<>("Phase", mPhaseValues); - ObservableList> observableList = FXCollections.observableArrayList(phaseSeries); - - setData(observableList); - init(length); - } - - private void init(int length) - { - mLengthProperty.addListener(new ChangeListener() - { - @Override - public void changed(ObservableValue observable, Number oldValue, Number newValue) - { - mComplexCircularBuffer = new ComplexCircularBuffer(newValue.intValue()); - } - }); - mComplexCircularBuffer = new ComplexCircularBuffer(length); - length().setValue(length); - - for(int x = 10; x <= length; x++) - { - mPhaseValues.add(new Data(x - 10, 0.0f)); - } - } - - /** - * Length of the x-axis for displaying samples - */ - public IntegerProperty length() - { - return mLengthProperty; - } - - @Override - public void receive(ReusableComplexBuffer complexBuffer) - { - float[] samples = complexBuffer.getSamples(); - - Complex sample; - - for(int x = 0; x < samples.length; x += 2) - { - sample = new Complex(samples[x], samples[x + 1]); - Complex copy = sample.copy(); - mComplexGain.apply(copy); - mComplexCircularBuffer.put(copy); - } - - complexBuffer.decrementUserCount(); - - updateChart(); - } - - private void updateChart() - { - Complex[] samples = mComplexCircularBuffer.getAll(); - - for(int x = 10; x < samples.length; x++) - { - Complex previous = samples[x - 10].copy(); - Complex sample = samples[x].copy(); - - if(previous != null && sample != null) - { -// previous.normalize(); -// sample.normalize(); - previous.add(ANGLE_OFFSET_45_DEGREES); - sample.multiply(previous.conjugate()); -// sample.normalize(); - mPhaseValues.get(x - 9).setYValue(sample.angle()); - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java index 55d19566..ffe65c67 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java @@ -1,31 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.gui.instrument.chart; import io.github.dsheirer.buffer.ComplexCircularBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; import io.github.dsheirer.sample.complex.ComplexSampleListener; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; @@ -38,7 +35,7 @@ import javafx.scene.chart.XYChart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ComplexSampleLineChart extends LineChart implements Listener, ComplexSampleListener +public class ComplexSampleLineChart extends LineChart implements Listener, ComplexSampleListener { private final static Logger mLog = LoggerFactory.getLogger(ComplexSampleLineChart.class); @@ -97,16 +94,12 @@ public class ComplexSampleLineChart extends LineChart implements Listener - * - ******************************************************************************/ -package io.github.dsheirer.gui.instrument.chart; - -import io.github.dsheirer.buffer.DoubleCircularBuffer; -import io.github.dsheirer.dsp.fm.FMDemodulator; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.complex.Complex; -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 FMDemodulatedLineChart extends LineChart implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatedLineChart.class); - - private DoubleCircularBuffer mDoubleCircularBuffer; - private int mSamplesPerSymbol; - private int mTwiceSamplesPerSymbol; - private int mSeriesCount; - private ObservableList> mData = FXCollections.observableArrayList(); - private Complex mPreviousSample = new Complex(0,0); - private int mSeriesPointer; - private int mSamplePointer; - - public FMDemodulatedLineChart(int samplesPerSymbol, int seriesCount) - { - super(new NumberAxis("Time", 1, samplesPerSymbol * 2, 2), - new NumberAxis("Demodulated", -1.0, 1.0, 0.25)); - - mSamplesPerSymbol = samplesPerSymbol; - mTwiceSamplesPerSymbol = mSamplesPerSymbol * 2; - mSeriesCount = seriesCount; - - init(); - } - - private void init() - { - int seriesLength = 2 * mSamplesPerSymbol; - mDoubleCircularBuffer = new DoubleCircularBuffer(seriesLength * mSeriesCount); - - for(int x = 0; x < mSeriesCount; x++) - { - ObservableList> series = FXCollections.observableArrayList(); - - for(int y = 0; y < seriesLength; y++) - { - series.add(new Data(y + 1, 0.0)); - } - - mData.add(new Series<>(series)); - } - - setData(mData); - } - - @Override - public void receive(ReusableComplexBuffer complexBuffer) - { - float[] samples = complexBuffer.getSamples(); - - Complex sample; - - for(int x = 0; x < samples.length; x += 2) - { - sample = new Complex(samples[x], samples[x + 1]); - double demodulated = FMDemodulator.demodulate(mPreviousSample, sample); - demodulated *= 2.0; - - if(demodulated > 1.0) - { - demodulated = 1.0; - } - else if(demodulated < -1.0) - { - demodulated = -1.0; - } - mDoubleCircularBuffer.put(demodulated); - mPreviousSample = sample; - updateChart(demodulated); - } - - complexBuffer.decrementUserCount(); - } - - private void updateChart(double value) - { - Series series = mData.get(mSeriesPointer); - Data data = series.getData().get(mSamplePointer++); - data.setYValue(value); - - if(mSamplePointer >= mTwiceSamplesPerSymbol) - { - mSamplePointer = 0; - mSeriesPointer++; - - if(mSeriesPointer >= mSeriesCount) - { - mSeriesPointer = 0; - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/RealSampleLineChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/RealSampleLineChart.java index 708518f3..41ea1522 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/RealSampleLineChart.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/RealSampleLineChart.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.instrument.chart; import io.github.dsheirer.buffer.RealCircularBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.chart.LineChart; @@ -27,7 +29,7 @@ import javafx.scene.layout.StackPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RealSampleLineChart extends LineChart implements Listener +public class RealSampleLineChart extends LineChart implements Listener { private final static Logger mLog = LoggerFactory.getLogger(RealSampleLineChart.class); private ObservableList> mSamples = FXCollections.observableArrayList(); @@ -65,9 +67,9 @@ public class RealSampleLineChart extends LineChart implements Listener sample = mSamples.get(x); sample.setYValue(bufferSamples[x]); } - - buffer.decrementUserCount(); } } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java index 6dc963c8..83991a71 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.gui.instrument.chart; @@ -28,15 +25,15 @@ import eu.hansolo.fx.charts.data.XYChartItem; import eu.hansolo.fx.charts.series.XYSeries; import io.github.dsheirer.buffer.ComplexCircularBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SampleXYChart extends PolarChart implements Listener +public class SampleXYChart extends PolarChart implements Listener { private static ObservableList sChartItemList = FXCollections.observableArrayList(); @@ -67,17 +64,13 @@ public class SampleXYChart extends PolarChart implements Listener series = getXYPane().getListOfSeries().get(0); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java index 546b807d..beaf818c 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.instrument.decoder; import io.github.dsheirer.gui.instrument.chart.AFSK1200SampleBufferChart; @@ -23,7 +26,6 @@ import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; @@ -52,7 +54,7 @@ public abstract class AbstractAFSK1200Pane extends RealDecoderPane private void init() { //This is force-cast without type checking .... - addListener((Listener) getDecoder()); + addListener((Listener) getDecoder()); addListener(getSampleLineChart()); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractDecoderPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractDecoderPane.java index 5c324989..f87c8c11 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractDecoderPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractDecoderPane.java @@ -1,26 +1,28 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.instrument.decoder; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.scene.layout.VBox; -public abstract class AbstractDecoderPane extends VBox implements Listener +public abstract class AbstractDecoderPane extends VBox implements Listener { abstract SampleType getSampleType(); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/ComplexDecoderPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/ComplexDecoderPane.java index 0bd39097..acf71e4a 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/ComplexDecoderPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/ComplexDecoderPane.java @@ -1,30 +1,33 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.instrument.decoder; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.sample.Broadcaster; 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.text.Text; -public class ComplexDecoderPane extends AbstractDecoderPane +public class ComplexDecoderPane extends AbstractDecoderPane { - private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mBufferBroadcaster = new Broadcaster(); private DecoderType mDecoderType; public ComplexDecoderPane(DecoderType decoderType) @@ -47,18 +50,18 @@ public class ComplexDecoderPane extends AbstractDecoderPane listener) + public void addListener(Listener listener) { - mComplexBufferBroadcaster.addListener(listener); + mBufferBroadcaster.addListener(listener); } - public void removeListener(Listener listener) + public void removeListener(Listener listener) { - mComplexBufferBroadcaster.removeListener(listener); + mBufferBroadcaster.removeListener(listener); } } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java index 53338a3a..6e704215 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.gui.instrument.decoder; @@ -26,17 +23,18 @@ import io.github.dsheirer.gui.instrument.chart.DoubleLineChart; import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart; import io.github.dsheirer.gui.instrument.chart.PhaseLineChart; import io.github.dsheirer.gui.instrument.chart.SymbolChart; -import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.dmr.DMRDecoderInstrumented; import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.Broadcaster; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Iterator; + public class DMRPane extends ComplexDecoderPane { private final static Logger mLog = LoggerFactory.getLogger(DMRPane.class); @@ -49,7 +47,7 @@ public class DMRPane extends ComplexDecoderPane private PhaseLineChart mPLLPhaseErrorLineChart; private DoubleLineChart mPLLFrequencyLineChart; private DoubleLineChart mSamplesPerSymbolLineChart; - private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); private DMRDecoderInstrumented mDecoder = new DMRDecoderInstrumented(new DecodeConfigDMR()); public DMRPane() @@ -60,7 +58,15 @@ public class DMRPane extends ComplexDecoderPane private void init() { - addListener(getDecoder()); + addListener(nativeBuffer -> + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + getDecoder().receive(iterator.next()); + } + }); getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster); getDecoder().setComplexSymbolListener(getSymbolChart()); @@ -74,14 +80,7 @@ public class DMRPane extends ComplexDecoderPane HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS); getChildren().addAll(getSampleChartBox(), getDecoderChartBox()); - mDecoder.setMessageListener(new Listener() - { - @Override - public void receive(IMessage message) - { - mLog.debug(message.toString()); - } - }); + mDecoder.setMessageListener(message -> mLog.debug(message.toString())); } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java index 853d7dbd..deb7adfe 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.gui.instrument.decoder; @@ -29,13 +26,16 @@ import io.github.dsheirer.gui.instrument.chart.SymbolChart; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FMInstrumented; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Iterator; + public class P25Phase1C4FMPane extends ComplexDecoderPane { private final static Logger mLog = LoggerFactory.getLogger(P25Phase1C4FMPane.class); @@ -48,7 +48,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane private PhaseLineChart mPLLPhaseErrorLineChart; private DoubleLineChart mPLLFrequencyLineChart; private DoubleLineChart mSamplesPerSymbolLineChart; - private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); private P25P1DecoderC4FMInstrumented mDecoder = new P25P1DecoderC4FMInstrumented(); public P25Phase1C4FMPane() @@ -59,7 +59,15 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane private void init() { - addListener(getDecoder()); + addListener(nativeBuffer -> + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + getDecoder().receive(iterator.next()); + } + }); getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster); getDecoder().setComplexSymbolListener(getSymbolChart()); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java index 56c87588..a5d95169 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.gui.instrument.decoder; @@ -30,13 +27,16 @@ import io.github.dsheirer.gui.instrument.chart.SymbolChart; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSMInstrumented; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Iterator; + public class P25Phase1LSMPane extends ComplexDecoderPane { private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class); @@ -49,7 +49,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane private PhaseLineChart mPLLPhaseErrorLineChart; private DoubleLineChart mPLLFrequencyLineChart; private SamplesPerSymbolChart mSamplesPerSymbolLineChart; - private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); private P25P1DecoderLSMInstrumented mDecoder = new P25P1DecoderLSMInstrumented(); public P25Phase1LSMPane() @@ -60,7 +60,15 @@ public class P25Phase1LSMPane extends ComplexDecoderPane private void init() { - addListener(getDecoder()); + addListener(nativeBuffer -> + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + getDecoder().receive(iterator.next()); + } + }); getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster); getDecoder().setComplexSymbolListener(getSymbolConstellationChart()); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java index 833fed72..9a017b02 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.gui.instrument.decoder; @@ -31,13 +28,16 @@ import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSKInstrumented; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Iterator; + public class P25Phase2HDQPSKPane extends ComplexDecoderPane { private final static Logger mLog = LoggerFactory.getLogger(P25Phase2HDQPSKPane.class); @@ -56,7 +56,7 @@ public class P25Phase2HDQPSKPane extends ComplexDecoderPane private SampleXYChart mSampleXYChart; - private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); private P25P2DecoderHDQPSKInstrumented mDecoder = new P25P2DecoderHDQPSKInstrumented(new DecodeConfigP25Phase2()); public P25Phase2HDQPSKPane() @@ -67,7 +67,15 @@ public class P25Phase2HDQPSKPane extends ComplexDecoderPane private void init() { - addListener(getDecoder()); + addListener(nativeBuffer -> + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + getDecoder().receive(iterator.next()); + } + }); getDecoder().getDemodulator().setFilteredGainAppliedComplexBufferListener(getSampleXYChart()); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/RealDecoderPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/RealDecoderPane.java index 1b2f15f1..7bff926c 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/RealDecoderPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/RealDecoderPane.java @@ -1,30 +1,32 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.instrument.decoder; import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.sample.Broadcaster; 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.ReusableFloatBuffer; import javafx.scene.text.Text; -public class RealDecoderPane extends AbstractDecoderPane +public class RealDecoderPane extends AbstractDecoderPane { - private ReusableBufferBroadcaster mBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mBufferBroadcaster = new Broadcaster(); private DecoderType mDecoderType; public RealDecoderPane(DecoderType decoderType) @@ -47,17 +49,17 @@ public class RealDecoderPane extends AbstractDecoderPane } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] buffer) { - mBufferBroadcaster.broadcast(reusableFloatBuffer); + mBufferBroadcaster.broadcast(buffer); } - public void addListener(Listener listener) + public void addListener(Listener listener) { mBufferBroadcaster.addListener(listener); } - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mBufferBroadcaster.removeListener(listener); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java index cd93cd62..b9614cf5 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.gui.playlist; @@ -176,7 +173,7 @@ public class PlaylistEditor extends BorderPane String filename = TimeStamp.getTimeStamp("_") + "_screen_capture.png"; final Path captureFile = mUserPreferences.getDirectoryPreference().getDirectoryScreenCapture().resolve(filename); - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.submit(() -> { try { ImageIO.write(bufferedImage, "png", captureFile.toFile()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java index 07dbafab..def7a7d8 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -137,7 +137,7 @@ public class BeepEditor extends ActionEditor final TestMessage testMessage = new TestMessage(); final Alias testAlias = new Alias("Test Alias"); - ThreadPool.SCHEDULED.submit(() -> { + ThreadPool.CACHED.submit(() -> { try { action.execute(testAlias, testMessage); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java index 0736c032..e88452ce 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -200,7 +200,7 @@ public class ClipEditor extends ActionEditor final TestMessage testMessage = new TestMessage(); final Alias testAlias = new Alias("Test Alias"); - ThreadPool.SCHEDULED.submit(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java index 8a5c02e1..d5efc61b 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -200,7 +200,7 @@ public class ScriptEditor extends ActionEditor final TestMessage testMessage = new TestMessage(); final Alias testAlias = new Alias("Test Alias"); - ThreadPool.SCHEDULED.submit(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java index e1494896..9ff926c5 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.gui.playlist.channel; @@ -365,7 +362,7 @@ public abstract class ChannelConfigurationEditor extends Editor if(!getItem().processingProperty().get()) { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { try { mPlaylistManager.getChannelProcessingManager().start(getItem()); @@ -386,7 +383,7 @@ public abstract class ChannelConfigurationEditor extends Editor } else { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { try { mPlaylistManager.getChannelProcessingManager().stop(getItem()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java index 5222597d..7cba16ab 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -155,7 +155,7 @@ public class AgencyEditor extends VBox mUserPreferences.getRadioReferencePreference().setPreferredAgencyId(agency.getAgencyId(), mLevel); - ThreadPool.SCHEDULED.submit(() -> { + ThreadPool.CACHED.submit(() -> { try { final AgencyInfo agencyInfo = mRadioReference.getService().getAgencyInfo(agency); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java index f11e1cd4..db5f42e3 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.gui.playlist.radioreference; @@ -400,7 +397,7 @@ public class AgencyFrequencyEditor extends GridPane if(subCategory != null) { - ThreadPool.SCHEDULED.submit(() -> { + ThreadPool.CACHED.submit(() -> { try { List frequencies = new ArrayList<>(); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java index 90b76992..cb48e837 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -230,7 +230,7 @@ public class FrequencyEditor extends VBox { if(modeId != null) { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { Integer parsed = null; try diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java index 8667fb88..9530a130 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.gui.playlist.radioreference; @@ -44,7 +41,13 @@ import javafx.scene.control.Label; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TextField; -import javafx.scene.layout.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.util.StringConverter; import jiconfont.icons.font_awesome.FontAwesome; @@ -119,7 +122,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer accept(credentials)); + ThreadPool.CACHED.execute(() -> accept(credentials)); } } } @@ -134,7 +137,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer { + ThreadPool.CACHED.execute(() -> { try { List countries = mRadioReference.getService().getCountries(); @@ -565,7 +568,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer { + ThreadPool.CACHED.execute(() -> { try { final CountryInfo countryInfo = mRadioReference.getService().getCountryInfo(country.getCountryId()); @@ -623,7 +626,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer { + ThreadPool.CACHED.execute(() -> { try { final StateInfo stateInfo = mRadioReference.getService().getStateInfo(state.getStateId()); @@ -679,7 +682,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer { + ThreadPool.CACHED.execute(() -> { try { final CountyInfo countyInfo = mRadioReference.getService().getCountyInfo(county.getCountyId()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java index 6217fc72..26523033 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -276,7 +276,7 @@ public class SystemEditor extends VBox mUserPreferences.getRadioReferencePreference().setPreferredSystemId(system.getSystemId(), mLevel); //Retrieve the radio reference data on a separate thread and then load the editors on the FX thread - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { try { if(mRadioReferenceDecoder == null) diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java index da505e72..102f67c2 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.gui.playlist.streaming; @@ -234,7 +231,7 @@ public class StreamingEditor extends SplitPane { if(mPlaylistManager.getRadioReference().availableProperty().get()) { - ThreadPool.SCHEDULED.submit(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java b/src/main/java/io/github/dsheirer/gui/preference/CalibrateRequest.java similarity index 67% rename from src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java rename to src/main/java/io/github/dsheirer/gui/preference/CalibrateRequest.java index bd686ddd..7eaafbb5 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java +++ b/src/main/java/io/github/dsheirer/gui/preference/CalibrateRequest.java @@ -17,24 +17,15 @@ * **************************************************************************** */ -package io.github.dsheirer.dsp.filter.decimate; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +package io.github.dsheirer.gui.preference; /** - * No decimation filter. + * View calibration preference and execute calibration request */ -public class ComplexDecimateX0Filter implements IComplexDecimationFilter +public class CalibrateRequest extends ViewUserPreferenceEditorRequest { - @Override - public float[] decimateComplex(float[] samples) + public CalibrateRequest() { - return samples; - } - - @Override - public ReusableComplexBuffer decimate(ReusableComplexBuffer buffer) - { - return buffer; + super(PreferenceEditorType.VECTOR_CALIBRATION); } } diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java index 39da9e07..7baf12bc 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -19,6 +19,7 @@ package io.github.dsheirer.gui.preference; +import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreferenceEditor; import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor; import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor; import io.github.dsheirer.gui.preference.duplicate.DuplicateCallPreferenceEditor; @@ -53,6 +54,8 @@ public class PreferenceEditorFactory return new TunerPreferenceEditor(userPreferences); case TALKGROUP_FORMAT: return new TalkgroupFormatPreferenceEditor(userPreferences); + case VECTOR_CALIBRATION: + return new VectorCalibrationPreferenceEditor(userPreferences); } return null; diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java index de2263fd..de98f775 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -32,6 +32,7 @@ public enum PreferenceEditorType AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"), SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"), TALKGROUP_FORMAT("Talkgroup & Radio ID"), + VECTOR_CALIBRATION("Vector Calibration"), DEFAULT("Default"); private String mLabel; diff --git a/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java b/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java index 69e7e3ad..78e12781 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -173,6 +173,11 @@ public class UserPreferencesEditor extends BorderPane treeRoot.getChildren().add(audioItem); audioItem.setExpanded(true); + TreeItem cpuItem = new TreeItem<>("CPU"); + cpuItem.getChildren().add(new TreeItem(PreferenceEditorType.VECTOR_CALIBRATION)); + treeRoot.getChildren().add(cpuItem); + cpuItem.setExpanded(true); + TreeItem decoderItem = new TreeItem<>("Decoder"); decoderItem.getChildren().add(new TreeItem(PreferenceEditorType.JMBE_LIBRARY)); treeRoot.getChildren().add(decoderItem); diff --git a/src/main/java/io/github/dsheirer/gui/preference/calibration/CalibrationDialog.java b/src/main/java/io/github/dsheirer/gui/preference/calibration/CalibrationDialog.java new file mode 100644 index 00000000..5f9f138d --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/calibration/CalibrationDialog.java @@ -0,0 +1,73 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.preference.calibration; + +import io.github.dsheirer.preference.UserPreferences; +import javafx.geometry.Insets; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +/** + * Dialog to prompt the user to run the calibrations. + */ +public class CalibrationDialog extends Dialog +{ + private UserPreferences mUserPreferences; + + public CalibrationDialog(UserPreferences userPreferences) + { + mUserPreferences = userPreferences; + + setTitle("Calibrate CPU"); + setHeaderText("Perform CPU Calibration?"); + + Label instructionsLabel = new Label("The sdrtrunk application can run more efficiently by using Vector or Single " + + "Instruction Multiple Data (SIMD) CPU operations. sdrtrunk must calibrate your CPU in order " + + "to determine the best implementation.\n\n" + + "Calibration takes a few minutes to complete.\n\n" + + "If you click 'Cancel', you will be prompted next time you run sdrtrunk, or you can open the " + + "User Preferences dialog from the View menu and manually start the calibration process."); + instructionsLabel.setPrefWidth(300); + instructionsLabel.setWrapText(true); + + CheckBox doNotShowAgainCheckBox = new CheckBox("Do not show again"); + doNotShowAgainCheckBox.setOnAction(event -> + { + mUserPreferences.getVectorCalibrationPreference().setHideCalibrationDialog(doNotShowAgainCheckBox.isSelected()); + }); + + VBox.setVgrow(instructionsLabel, Priority.ALWAYS); + VBox contentBox = new VBox(); + contentBox.setPadding(new Insets(10)); + contentBox.setSpacing(20); + contentBox.getChildren().addAll(instructionsLabel, doNotShowAgainCheckBox); + + getDialogPane().setContent(contentBox); + + ButtonType calibrateButtonType = new ButtonType("Calibrate", ButtonBar.ButtonData.OK_DONE); + ButtonType cancelButtonType = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + getDialogPane().getButtonTypes().addAll(calibrateButtonType, cancelButtonType); + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/calibration/VectorCalibrationPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/calibration/VectorCalibrationPreferenceEditor.java new file mode 100644 index 00000000..1c909598 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/calibration/VectorCalibrationPreferenceEditor.java @@ -0,0 +1,347 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.gui.preference.calibration; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.gui.preference.CalibrateRequest; +import io.github.dsheirer.log.TextAreaLogAppender; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.calibration.VectorCalibrationPreference; +import io.github.dsheirer.util.ThreadPool; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import javafx.application.Platform; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextArea; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.controlsfx.control.ToggleSwitch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Preference settings for duplicate call audio handling + */ +public class VectorCalibrationPreferenceEditor extends HBox +{ + private final static Logger mLog = LoggerFactory.getLogger(VectorCalibrationPreferenceEditor.class); + private VectorCalibrationPreference mPreference; + private GridPane mEditorPane; + private ToggleSwitch mHideDialogSwitch; + private ToggleSwitch mVectorEnabled; + private Label mCalibrationsPendingValue; + private Button mResetAllButton; + private Button mCalibrateButton; + private ProgressBar mProgressBar; + private Label mCalibratingLabel; + private TextArea mConsoleTextArea; + private TextAreaLogAppender mTextAreaLogAppender; + + /** + * Constructs an instance + */ + public VectorCalibrationPreferenceEditor(UserPreferences userPreferences) + { + MyEventBus.getGlobalEventBus().register(this); + mPreference = userPreferences.getVectorCalibrationPreference(); + setMaxHeight(Double.MAX_VALUE); + setMaxWidth(Double.MAX_VALUE); + + VBox vbox = new VBox(); + vbox.setMaxHeight(Double.MAX_VALUE); + vbox.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(vbox, Priority.ALWAYS); + + VBox.setVgrow(getEditorPane(), Priority.NEVER); + vbox.getChildren().add(getEditorPane()); + + ScrollPane scrollPane = new ScrollPane(getConsoleTextArea()); + scrollPane.setFitToHeight(true); + scrollPane.setFitToWidth(true); + + VBox.setVgrow(scrollPane, Priority.ALWAYS); + vbox.getChildren().add(scrollPane); + getChildren().add(vbox); + } + + @Subscribe + public void process(CalibrateRequest request) + { + getCalibrateButton().fire(); + } + + private GridPane getEditorPane() + { + if(mEditorPane == null) + { + int row = 0; + mEditorPane = new GridPane(); + mEditorPane.setPadding(new Insets(10)); + mEditorPane.setHgap(10); + mEditorPane.setVgap(10); + + Label titleLabel = new Label("CPU Vector SIMD Preferences"); + GridPane.setConstraints(titleLabel, 0, row++, 2, 1); + mEditorPane.getChildren().add(titleLabel); + + GridPane.setConstraints(getVectorEnabledToggleSwitch(), 0, row); + GridPane.setHalignment(getVectorEnabledToggleSwitch(), HPos.RIGHT); + mEditorPane.getChildren().add(getVectorEnabledToggleSwitch()); + + Label enableSimdLabel = new Label("Enable SIMD Vector Operations"); + GridPane.setConstraints(enableSimdLabel, 1, row++); + mEditorPane.getChildren().add((enableSimdLabel)); + + GridPane.setConstraints(getHideDialogSwitch(), 0, row); + GridPane.setHalignment(getHideDialogSwitch(), HPos.RIGHT); + mEditorPane.getChildren().add(getHideDialogSwitch()); + + Label hideDialogLabel = new Label("Don't Show Calibration Dialog When New Calibrations Are Available"); + GridPane.setConstraints(hideDialogLabel, 1, row++); + mEditorPane.getChildren().add((hideDialogLabel)); + + GridPane.setConstraints(getCalibrationsPendingValue(), 0, row); + mEditorPane.getChildren().add(getCalibrationsPendingValue()); + + Label calibrationsPendingLabel = new Label("Calibrations To Perform"); + GridPane.setHalignment(getCalibrationsPendingValue(), HPos.RIGHT); + GridPane.setConstraints(calibrationsPendingLabel, 1, row++); + mEditorPane.getChildren().add(calibrationsPendingLabel); + updateControls(); + + GridPane.setConstraints(getCalibratingLabel(), 0, row); + mEditorPane.getChildren().add(getCalibratingLabel()); + + GridPane.setConstraints(getProgressBar(), 1, row++); + mEditorPane.getChildren().add(getProgressBar()); + + HBox buttonsBox = new HBox(); + buttonsBox.setSpacing(10); + buttonsBox.getChildren().addAll(getCalibrateButton(), getResetAllButton()); + GridPane.setHalignment(buttonsBox, HPos.CENTER); + GridPane.setConstraints(buttonsBox, 0, row, 2, 1); + mEditorPane.getChildren().add(buttonsBox); + } + + return mEditorPane; + } + + private void enableConsoleLogging() + { + LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Calibration.class); + ((ch.qos.logback.classic.Logger)logger).setLevel(Level.INFO); + ((ch.qos.logback.classic.Logger)logger).addAppender(getTextAreaLogAppender()); + getTextAreaLogAppender().start(); + } + + private void disableConsoleLogging() + { + LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Calibration.class); + ((ch.qos.logback.classic.Logger)logger).detachAppender(getTextAreaLogAppender()); + getTextAreaLogAppender().stop(); + } + + private TextAreaLogAppender getTextAreaLogAppender() + { + if(mTextAreaLogAppender == null) + { + mTextAreaLogAppender = new TextAreaLogAppender(getConsoleTextArea(), "My Console Logger"); + } + + return mTextAreaLogAppender; + } + + private TextArea getConsoleTextArea() + { + if(mConsoleTextArea == null) + { + mConsoleTextArea = new TextArea(); + mConsoleTextArea.setDisable(true); + mConsoleTextArea.setMaxHeight(Double.MAX_VALUE); + mConsoleTextArea.setMaxWidth(Double.MAX_VALUE); + } + + return mConsoleTextArea; + } + + private ProgressBar getProgressBar() + { + if(mProgressBar == null) + { + mProgressBar = new ProgressBar(); + mProgressBar.setVisible(false); + } + + return mProgressBar; + } + + private Button getResetAllButton() + { + if(mResetAllButton == null) + { + mResetAllButton = new Button("Reset All Calibrations"); + mResetAllButton.setTooltip(new Tooltip("Reset all calibrations to an Uncalibrated state")); + mResetAllButton.setOnAction(event -> + { + CalibrationManager.getInstance().reset(); + updateControls(); + }); + } + + return mResetAllButton; + } + + private Button getCalibrateButton() + { + if(mCalibrateButton == null) + { + mCalibrateButton = new Button("Calibrate"); + mCalibrateButton.setTooltip(new Tooltip("Calibrate the items that need calibrated")); + mCalibrateButton.setOnAction(event -> + { + getCalibrateButton().setDisable(true); + getResetAllButton().setDisable(true); + getConsoleTextArea().clear(); + getConsoleTextArea().appendText("Calibrating. Each calibration lasts 20 - 40 seconds."); + enableConsoleLogging(); + getCalibratingLabel().setVisible(true); + getProgressBar().setVisible(true); + getProgressBar().setProgress(0.0); + + ThreadPool.CACHED.submit(() -> + { + try + { + CalibrationManager manager = CalibrationManager.getInstance(); + List calibrations = manager.getUncalibrated(); + int counter = 0; + for(Calibration calibration: calibrations) + { + counter++; + + final String message = "\n\nCalibration [" + counter + "/" + calibrations.size() + "] - " + calibration.getType(); + Platform.runLater(() -> getConsoleTextArea().appendText(message)); + + try + { + calibration.calibrate(); + + final double progress = (double)counter / (double)calibrations.size(); + Platform.runLater(() -> getProgressBar().setProgress(progress)); + } + catch(CalibrationException ce) + { + mLog.error("Calibration error for " + calibration.getType(), ce); + } + } + } + catch(Throwable t) + { + mLog.error("Error while performing calibrations", t); + } + + Platform.runLater(() -> + { + getConsoleTextArea().appendText("\nCalibration Complete!\nNote: restart the application to use these new settings."); + getCalibratingLabel().setVisible(false); + getProgressBar().setVisible(false); + updateControls(); + disableConsoleLogging(); + }); + }); + }); + } + + return mCalibrateButton; + } + + private Label getCalibratingLabel() + { + if(mCalibratingLabel == null) + { + mCalibratingLabel = new Label("Calibrating:"); + mCalibratingLabel.setVisible(false); + } + + return mCalibratingLabel; + } + + private void updateControls() + { + int pending = CalibrationManager.getInstance().getUncalibrated().size(); + int total = CalibrationManager.getInstance().getCalibrationTypes().size(); + getCalibrationsPendingValue().setText(String.valueOf(pending)); + getCalibrateButton().setDisable(pending == 0); + getResetAllButton().setDisable(pending == total); + } + + private Label getCalibrationsPendingValue() + { + if(mCalibrationsPendingValue == null) + { + mCalibrationsPendingValue = new Label(" "); + } + + return mCalibrationsPendingValue; + } + + private ToggleSwitch getVectorEnabledToggleSwitch() + { + if(mVectorEnabled == null) + { + mVectorEnabled = new ToggleSwitch(); + mVectorEnabled.setTooltip(new Tooltip("Allow sdrtrunk to use optimized vector operations when supported by your CPU")); + mVectorEnabled.setSelected(mPreference.isVectorEnabled()); + mVectorEnabled.selectedProperty() + .addListener((observable, oldValue, enabled) -> mPreference.setVectorEnabled(enabled)); + } + + return mVectorEnabled; + } + + private ToggleSwitch getHideDialogSwitch() + { + if(mHideDialogSwitch == null) + { + mHideDialogSwitch = new ToggleSwitch(); + mHideDialogSwitch.setTooltip(new Tooltip("Don't show dialog when new calibrations are available")); + mHideDialogSwitch.setSelected(mPreference.isHideCalibrationDialog()); + mHideDialogSwitch.selectedProperty().addListener((observable, oldValue, hide) -> mPreference.setHideCalibrationDialog(hide)); + } + + return mHideDialogSwitch; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java index 36f45629..2fae07c8 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.gui.preference.decoder; @@ -168,7 +167,7 @@ public class JmbeLibraryPreferenceEditor extends VBox */ private void checkForUpdatedLibrary() { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { try { Version current = mUserPreferences.getJmbeLibraryPreference().getCurrentVersion(); diff --git a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java b/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java index 868277b1..e64bad3b 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java +++ b/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -19,7 +19,8 @@ package io.github.dsheirer.gui.preference.playback; -import io.github.dsheirer.dsp.mixer.Oscillator; +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.ScalarRealOscillator; /** * Utility for generating tones for audio clips @@ -38,9 +39,9 @@ public class ToneUtil */ public static float[] getTone(ToneFrequency toneFrequency, ToneVolume toneVolume, int sampleCount) { - Oscillator oscillator = new Oscillator(toneFrequency.getValue(), 8000.0); + IRealOscillator oscillator = new ScalarRealOscillator(toneFrequency.getValue(), 8000.0); - float[] samples = oscillator.generateReal(sampleCount); + float[] samples = oscillator.generate(sampleCount); double gain = MAX_TONE_VOLUME * ((double)toneVolume.getValue() / 10.0); diff --git a/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java b/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java index 994731c4..fca4ffc4 100644 --- a/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java +++ b/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -125,7 +125,7 @@ public class JmbeCreator if(asset != null) { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { Path tempDirectory = null; try { diff --git a/src/main/java/io/github/dsheirer/log/TextAreaLogAppender.java b/src/main/java/io/github/dsheirer/log/TextAreaLogAppender.java new file mode 100644 index 00000000..65e58a96 --- /dev/null +++ b/src/main/java/io/github/dsheirer/log/TextAreaLogAppender.java @@ -0,0 +1,50 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.log; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import javafx.application.Platform; +import javafx.scene.control.TextArea; + +/** + * Logback Log Appender that continuously writes to a JavaFX text area + * + * Note: this appender doesn't trim/truncate the text in the text area. + */ +public class TextAreaLogAppender extends AppenderBase +{ + private TextArea mTextArea; + + public TextAreaLogAppender(TextArea textArea, String name) + { + mTextArea = textArea; + setName(name); + } + + @Override + protected void append(ILoggingEvent eventObject) + { + Platform.runLater(() -> + { + mTextArea.appendText("\n" + eventObject.getMessage()); + }); + } +} diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index 3ad78310..21a2534c 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -54,18 +54,15 @@ import io.github.dsheirer.module.decode.event.IDecodeEventProvider; import io.github.dsheirer.module.decode.traffic.TrafficChannelManager; import io.github.dsheirer.module.log.EventLogger; import io.github.dsheirer.record.binary.BinaryRecorder; -import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder; +import io.github.dsheirer.record.wave.ComplexSamplesWaveRecorder; 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.IReusableByteBufferListener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferListener; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; +import io.github.dsheirer.sample.real.IRealBufferListener; +import io.github.dsheirer.sample.real.IRealBufferProvider; import io.github.dsheirer.source.ComplexSource; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; @@ -78,6 +75,7 @@ import io.github.dsheirer.source.heartbeat.IHeartbeatProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -105,9 +103,9 @@ public class ProcessingChain implements Listener { private final static Logger mLog = LoggerFactory.getLogger(ProcessingChain.class); - private ReusableBufferBroadcaster mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster(); - private ReusableBufferBroadcaster mBasebandComplexBufferBroadcaster = new ReusableBufferBroadcaster(); - private ReusableBufferBroadcaster mDemodulatedBitstreamBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mDemodulatedAudioBufferBroadcaster = new Broadcaster(); + private Broadcaster mBasebandComplexSamplesBroadcaster = new Broadcaster<>(); + private Broadcaster mDemodulatedBitstreamBufferBroadcaster = new Broadcaster(); private Broadcaster mAudioSegmentBroadcaster = new AudioSegmentBroadcaster<>(); private Broadcaster mDecodeEventBroadcaster = new Broadcaster<>(); private Broadcaster mChannelEventBroadcaster = new Broadcaster<>(); @@ -226,7 +224,7 @@ public class ProcessingChain implements Listener mAudioSegmentBroadcaster.dispose(); mDecodeEventBroadcaster.dispose(); mChannelEventBroadcaster.dispose(); - mBasebandComplexBufferBroadcaster.dispose(); + mBasebandComplexSamplesBroadcaster.dispose(); mDemodulatedBitstreamBufferBroadcaster.dispose(); mMessageBroadcaster.dispose(); mSquelchStateEventBroadcaster.dispose(); @@ -391,19 +389,19 @@ public class ProcessingChain implements Listener mMessageBroadcaster.addListener(((IMessageListener)module).getMessageListener()); } - if(module instanceof IReusableBufferListener) + if(module instanceof IRealBufferListener) { - mDemodulatedAudioBufferBroadcaster.addListener(((IReusableBufferListener)module).getReusableBufferListener()); + mDemodulatedAudioBufferBroadcaster.addListener(((IRealBufferListener)module).getBufferListener()); } - if(module instanceof IReusableByteBufferListener) + if(module instanceof IByteBufferListener) { - mDemodulatedBitstreamBufferBroadcaster.addListener(((IReusableByteBufferListener)module).getReusableByteBufferListener()); + mDemodulatedBitstreamBufferBroadcaster.addListener(((IByteBufferListener)module).getByteBufferListener()); } - if(module instanceof IReusableComplexBufferListener) + if(module instanceof IComplexSamplesListener) { - mBasebandComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener()); + mBasebandComplexSamplesBroadcaster.addListener(((IComplexSamplesListener)module).getComplexSamplesListener()); } if(module instanceof ISourceEventListener) @@ -458,19 +456,19 @@ public class ProcessingChain implements Listener mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener()); } - if(module instanceof IReusableBufferListener) + if(module instanceof IRealBufferListener) { - mDemodulatedAudioBufferBroadcaster.removeListener(((IReusableBufferListener)module).getReusableBufferListener()); + mDemodulatedAudioBufferBroadcaster.removeListener(((IRealBufferListener)module).getBufferListener()); } - if(module instanceof IReusableByteBufferListener) + if(module instanceof IByteBufferListener) { - mDemodulatedBitstreamBufferBroadcaster.removeListener(((IReusableByteBufferListener)module).getReusableByteBufferListener()); + mDemodulatedBitstreamBufferBroadcaster.removeListener(((IByteBufferListener)module).getByteBufferListener()); } - if(module instanceof IReusableComplexBufferListener) + if(module instanceof IComplexSamplesListener) { - mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener()); + mBasebandComplexSamplesBroadcaster.removeListener(((IComplexSamplesListener)module).getComplexSamplesListener()); } if(module instanceof ISourceEventListener) @@ -535,14 +533,14 @@ public class ProcessingChain implements Listener ((IMessageProvider)module).setMessageListener(mMessageBroadcaster); } - if(module instanceof IReusableByteBufferProvider) + if(module instanceof IByteBufferProvider) { - ((IReusableByteBufferProvider)module).setBufferListener(mDemodulatedBitstreamBufferBroadcaster); + ((IByteBufferProvider)module).setBufferListener(mDemodulatedBitstreamBufferBroadcaster); } - if(module instanceof IReusableBufferProvider) + if(module instanceof IRealBufferProvider) { - ((IReusableBufferProvider)module).setBufferListener(mDemodulatedAudioBufferBroadcaster); + ((IRealBufferProvider)module).setBufferListener(mDemodulatedAudioBufferBroadcaster); } if(module instanceof ISourceEventProvider) @@ -572,9 +570,9 @@ public class ProcessingChain implements Listener ((IAudioSegmentProvider)module).setAudioSegmentListener(null); } - if(module instanceof IReusableByteBufferProvider) + if(module instanceof IByteBufferProvider) { - ((IReusableByteBufferProvider)module).removeBufferListener(mDemodulatedBitstreamBufferBroadcaster); + ((IByteBufferProvider)module).removeBufferListener(mDemodulatedBitstreamBufferBroadcaster); } if(module instanceof IDecodeEventProvider) @@ -607,9 +605,9 @@ public class ProcessingChain implements Listener ((IMessageProvider)module).setMessageListener(null); } - if(module instanceof IReusableBufferProvider) + if(module instanceof IRealBufferProvider) { - ((IReusableBufferProvider)module).setBufferListener(null); + ((IRealBufferProvider)module).setBufferListener(null); } if(module instanceof ISourceEventProvider) @@ -648,7 +646,7 @@ public class ProcessingChain implements Listener switch(mSource.getSampleType()) { case COMPLEX: - ((ComplexSource)mSource).setListener(mBasebandComplexBufferBroadcaster); + ((ComplexSource)mSource).setListener(mBasebandComplexSamplesBroadcaster); break; case REAL: ((RealSource)mSource).setListener(mDemodulatedAudioBufferBroadcaster); @@ -696,10 +694,10 @@ public class ProcessingChain implements Listener switch(mSource.getSampleType()) { case COMPLEX: - ((ComplexSource)mSource).removeListener(mBasebandComplexBufferBroadcaster); + ((ComplexSource)mSource).setListener(null); break; case REAL: - ((RealSource)mSource).removeListener(mDemodulatedAudioBufferBroadcaster); + ((RealSource)mSource).setListener(null); break; default: throw new IllegalArgumentException("Unrecognized source sample type - cannot start processing " + @@ -753,7 +751,7 @@ public class ProcessingChain implements Listener for(Module module : mModules) { - if(module instanceof ComplexBufferWaveRecorder) + if(module instanceof ComplexSamplesWaveRecorder) { recordingModules.add(module); } @@ -882,7 +880,7 @@ public class ProcessingChain implements Listener /** * Adds listener to receive demodulated audio buffers from an modules that produce demodulated audio. */ - public void addDemodulatedAudioListener(Listener listener) + public void addDemodulatedAudioListener(Listener listener) { mDemodulatedAudioBufferBroadcaster.addListener(listener); } @@ -890,7 +888,7 @@ public class ProcessingChain implements Listener /** * Removes the listener from receiving demodulated audio buffers. */ - public void removeDemodulatedAudioListener(Listener listener) + public void removeDemodulatedAudioListener(Listener listener) { mDemodulatedAudioBufferBroadcaster.removeListener(listener); } diff --git a/src/main/java/io/github/dsheirer/module/decode/afsk/AbstractAFSKDecoder.java b/src/main/java/io/github/dsheirer/module/decode/afsk/AbstractAFSKDecoder.java index ffa4f6e1..d913d652 100644 --- a/src/main/java/io/github/dsheirer/module/decode/afsk/AbstractAFSKDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/afsk/AbstractAFSKDecoder.java @@ -1,25 +1,27 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.module.decode.afsk; import io.github.dsheirer.dsp.afsk.AFSK1200Decoder; import io.github.dsheirer.module.decode.Decoder; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; /** * Abstract class for Audio Frequency Shift Keying (AFSK) 1200-baud decoder based decoder modules. @@ -28,7 +30,7 @@ import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; * * Sub-class implementations should invoke getDecoder().setListener() to receive decoded symbol stream. */ -public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener +public abstract class AbstractAFSKDecoder extends Decoder implements IRealBufferListener, Listener { private AFSK1200Decoder mAFSK1200Decoder; @@ -48,13 +50,13 @@ public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBu } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - mAFSK1200Decoder.receive(reusableFloatBuffer); + mAFSK1200Decoder.receive(realBuffer); } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { return this; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java index e225b0d5..32c3bf6e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java @@ -21,8 +21,9 @@ package io.github.dsheirer.module.decode.dmr; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; -import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2; -import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.CostasLoop; @@ -35,16 +36,16 @@ import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.FeedbackDecoder; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; @@ -52,7 +53,7 @@ import java.util.Map; * DMR decoder module. */ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableByteBufferProvider + IComplexSamplesListener, Listener, IByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(DMRDecoder.class); protected static final float SAMPLE_COUNTER_GAIN = 0.4f; @@ -61,15 +62,17 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, private Broadcaster mDibitBroadcaster = new Broadcaster<>(); private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300); private DMRMessageProcessor mMessageProcessor; - private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); + protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); private Map mBasebandFilters = new HashMap<>(); - private ComplexFIRFilter2 mBasebandFilter; + protected IRealFilter mIBasebandFilter; + protected IRealFilter mQBasebandFilter; + protected InterpolatingSampleBuffer mInterpolatingSampleBuffer; protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator; protected CostasLoop mCostasLoop; protected FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor; protected DMRMessageFramer mMessageFramer; - private PowerMonitor mPowerMonitor = new PowerMonitor(); + protected PowerMonitor mPowerMonitor = new PowerMonitor(); /** * Constructs an instance @@ -101,7 +104,7 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, /** * Sets the sample rate and configures internal decoder components. - * @param sampleRate + * @param sampleRate of the channel to decode */ public void setSampleRate(double sampleRate) { @@ -113,7 +116,8 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, mPowerMonitor.setSampleRate((int)sampleRate); mSampleRate = sampleRate; - mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter()); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300); mFrequencyCorrectionSyncMonitor = new FrequencyCorrectionSyncMonitor(mCostasLoop, this); @@ -138,35 +142,21 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, /** * Primary method for processing incoming complex sample buffers - * @param reusableComplexBuffer containing channelized complex samples + * @param samples containing channelized complex samples */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - //User accounting of the incoming buffer is handled by the filter - ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); //Process buffer for power measurements - mPowerMonitor.process(basebandFiltered); + mPowerMonitor.process(i, q); - //User accounting of the incoming buffer is handled by the gain filter - ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); - - mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); - - //User accounting of the filtered buffer is handled by the demodulator - mQPSKDemodulator.receive(gainApplied); - } - - /** - * Filters the complex buffer and returns a new reusable complex buffer with the filtered contents. - * @param reusableComplexBuffer to filter - * @return filtered complex buffer - */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) - { - //User accounting of the incoming buffer is handled by the filter - return mBasebandFilter.filter(reusableComplexBuffer); + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -251,7 +241,7 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mByteBufferAssembler.setBufferListener(listener); } @@ -260,7 +250,7 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mByteBufferAssembler.removeBufferListener(listener); } @@ -318,21 +308,14 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, @Override public Listener getSourceEventListener() { - return new Listener() - { - @Override - public void receive(SourceEvent sourceEvent) - { - process(sourceEvent); - } - }; + return sourceEvent -> process(sourceEvent); } /** * Listener interface to receive reusable complex buffers */ @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return DMRDecoder.this; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java index 40849285..1c7d1433 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,7 +14,7 @@ * * 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.dmr; @@ -23,8 +22,8 @@ import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; /** * Instrumented version of DMR decoder @@ -35,7 +34,7 @@ public class DMRDecoderInstrumented extends DMRDecoder private Listener mPLLFrequencyListener; private Listener mSamplesPerSymbolListener; private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; + private Listener mFilteredSymbolListener; private Listener mSymbolDecisionDataListener; /** @@ -48,19 +47,27 @@ public class DMRDecoderInstrumented extends DMRDecoder } /** - * Overrides the filter method so that we can capture the filtered samples for instrumentation + * Primary method for processing incoming complex sample buffers + * @param complexSamples containing channelized complex samples */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) + @Override + public void receive(ComplexSamples samples) { - ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); if(mFilteredSymbolListener != null) { - filtered.incrementUserCount(); - mFilteredSymbolListener.receive(filtered); + mFilteredSymbolListener.receive(new ComplexSamples(i, q)); } - return filtered; + //Process buffer for power measurements + mPowerMonitor.process(i, q); + + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -104,7 +111,7 @@ public class DMRDecoderInstrumented extends DMRDecoder ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener); } - public void setFilteredBufferListener(Listener listener) + public void setFilteredBufferListener(Listener listener) { mFilteredSymbolListener = listener; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java index 0f9f6b65..ba7eb87c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -38,11 +38,11 @@ import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.record.binary.BinaryReader; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; @@ -173,20 +173,15 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene * * @param buffer to process into a stream of dibits for processing. */ - public void receive(ReusableByteBuffer buffer) + public void receive(ByteBuffer buffer) { - //Updates current timestamp to the timestamp from the incoming buffer - setCurrentTime(buffer.getTimestamp()); - - for(byte value : buffer.getBytes()) + for(byte value : buffer.array()) { for(int x = 0; x <= 3; x++) { receive(Dibit.parse(value, x)); } } - - buffer.decrementUserCount(); } /** @@ -469,7 +464,7 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene { while(reader.hasNext()) { - ReusableByteBuffer buffer = reader.next(); + ByteBuffer buffer = reader.next(); messageFramer.receive(buffer); } } @@ -517,7 +512,7 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene { while(reader.hasNext()) { - ReusableByteBuffer buffer = reader.next(); + ByteBuffer buffer = reader.next(); messageFramer.receive(buffer); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java index f05dea5c..81e434d0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.module.decode.fleetsync2; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -44,9 +46,9 @@ public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder implements } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java index c92aeee6..bbee0e5d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.module.decode.lj1200; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -44,9 +46,9 @@ public class LJ1200DecoderInstrumented extends LJ1200Decoder implements IInstrum } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } 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 2a546c92..182dcd8e 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,18 +1,21 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.module.decode.ltrnet; import io.github.dsheirer.bits.MessageFramer; @@ -22,10 +25,9 @@ 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.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; -public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener +public class LTRNetDecoder extends Decoder implements IRealBufferListener, Listener { public static final int LTR_NET_MESSAGE_LENGTH = 40; protected LTRDecoder mLTRDecoder; @@ -72,13 +74,13 @@ public class LTRNetDecoder extends Decoder implements IReusableBufferListener, L } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { return mLTRDecoder; } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] reusableFloatBuffer) { mLTRDecoder.receive(reusableFloatBuffer); } 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 index 2e0055d9..10a78c65 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java @@ -1,22 +1,24 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.module.decode.ltrnet; import io.github.dsheirer.dsp.fsk.LTRDecoderInstrumented; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; public class LTRNetDecoderInstrumented extends LTRNetDecoder @@ -34,9 +36,9 @@ public class LTRNetDecoderInstrumented extends LTRNetDecoder } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); 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 f4851940..b54e2cf9 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,18 +1,21 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.module.decode.ltrstandard; import io.github.dsheirer.alias.AliasList; @@ -23,10 +26,9 @@ 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.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; -public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener +public class LTRStandardDecoder extends Decoder implements IRealBufferListener, Listener { public static final int LTR_STANDARD_MESSAGE_LENGTH = 40; @@ -65,15 +67,15 @@ public class LTRStandardDecoder extends Decoder implements IReusableBufferListen } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { return mLTRDecoder; } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - mLTRDecoder.receive(reusableFloatBuffer); + mLTRDecoder.receive(realBuffer); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java index 422c1745..3feab946 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,13 +14,12 @@ * * 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.mdc1200; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -48,9 +46,9 @@ public class MDCDecoderInstrumented extends MDCDecoder implements IInstrumentedA } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java index b9364e9f..b58a62a1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,7 +14,7 @@ * * 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.mpt1327; @@ -26,15 +25,16 @@ import io.github.dsheirer.dsp.symbol.BinaryToByteBufferAssembler; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.afsk.AbstractAFSKDecoder; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; + /** * Instrumented version of the MPT1327 decoder. Exposes properties for instrumented manual decoding of a signal. */ -public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbolProcessor, IReusableByteBufferProvider +public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbolProcessor, IByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(MPT1327Decoder.class); @@ -114,13 +114,13 @@ public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbol } @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mBinaryToByteBufferAssembler.setBufferListener(listener); } @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mBinaryToByteBufferAssembler.removeBufferListener(listener); } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java index fa76b186..364bca80 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,13 +14,12 @@ * * 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.mpt1327; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -48,9 +46,9 @@ public class MPT1327DecoderInstrumented extends MPT1327Decoder implements IInstr } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java b/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java index 41316a41..4d789244 100644 --- a/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java @@ -22,21 +22,21 @@ import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.IDecoderStateEventProvider; import io.github.dsheirer.channel.state.State; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; import io.github.dsheirer.dsp.filter.decimate.DecimationFilterFactory; -import io.github.dsheirer.dsp.filter.decimate.IComplexDecimationFilter; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; 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.IRealFilter; import io.github.dsheirer.dsp.filter.resample.RealResampler; -import io.github.dsheirer.dsp.fm.SquelchingFMDemodulator; +import io.github.dsheirer.dsp.fm.FmDemodulatorFactory; +import io.github.dsheirer.dsp.fm.ISquelchingFmDemodulator; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.PrimaryDecoder; 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.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; +import io.github.dsheirer.sample.real.IRealBufferProvider; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; @@ -47,22 +47,23 @@ import org.slf4j.LoggerFactory; * Decoder module with integrated narrowband FM (12.5 or 25.0 kHz channel) demodulator */ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableBufferProvider, + IComplexSamplesListener, Listener, IRealBufferProvider, IDecoderStateEventProvider { private final static Logger mLog = LoggerFactory.getLogger(NBFMDecoder.class); private static final double DEMODULATED_AUDIO_SAMPLE_RATE = 8000.0; - private static final double POWER_SQUELCH_ALPHA_DECAY = 0.0004; - private static final double POWER_SQUELCH_THRESHOLD_DB = -78.0; + private static final float POWER_SQUELCH_ALPHA_DECAY = 0.0004f; + private static final float POWER_SQUELCH_THRESHOLD_DB = -78.0f; private static final int POWER_SQUELCH_RAMP = 4; - private ComplexFIRFilter2 mIQFilter; - private IComplexDecimationFilter mDecimationFilter; - private SquelchingFMDemodulator mDemodulator = new SquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, - POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + private IRealFilter mIBasebandFilter; + private IRealFilter mQBasebandFilter; + private IRealDecimationFilter mIDecimationFilter; + private IRealDecimationFilter mQDecimationFilter; + private ISquelchingFmDemodulator mDemodulator; private RealResampler mResampler; private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor(); - private Listener mResampledReusableBufferListener; + private Listener mResampledBufferListener; private Listener mDecoderStateEventListener; private double mChannelBandwidth; private double mOutputSampleRate = DEMODULATED_AUDIO_SAMPLE_RATE; @@ -76,6 +77,8 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, { super( config ); mChannelBandwidth = config.getBandwidth().getValue(); + mDemodulator = FmDemodulatorFactory.getSquelchingFmDemodulator(POWER_SQUELCH_ALPHA_DECAY, + POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); mDemodulator.setSquelchThreshold(config.getSquelchThreshold()); } @@ -86,7 +89,7 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, } @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return this; } @@ -114,31 +117,33 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, } @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { - mResampledReusableBufferListener = listener; + mResampledBufferListener = listener; } @Override public void removeBufferListener() { - mResampledReusableBufferListener = null; + mResampledBufferListener = null; } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - if(mIQFilter == null) + if(mIDecimationFilter == null || mQDecimationFilter == null) { - reusableComplexBuffer.decrementUserCount(); throw new IllegalStateException("NBFM demodulator module must receive a sample rate change source " + "event before it can process complex sample buffers"); } - ReusableComplexBuffer decimatedBuffer = mDecimationFilter.decimate(reusableComplexBuffer); - ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(decimatedBuffer); + float[] decimatedI = mIDecimationFilter.decimateReal(samples.i()); + float[] decimatedQ = mQDecimationFilter.decimateReal(samples.q()); - ReusableFloatBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer); + float[] filteredI = mIBasebandFilter.filter(decimatedI); + float[] filteredQ = mQBasebandFilter.filter(decimatedQ); + + float[] demodulated = mDemodulator.demodulate(filteredI, filteredQ); if(mResampler != null) { @@ -153,12 +158,11 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, //Either send the demodulated buffer to the resampler for distro, or decrement the user count if(mSquelch) { - demodulatedBuffer.incrementUserCount(); notifyIdle(); } else { - mResampler.resample(demodulatedBuffer); + mResampler.resample(demodulated); notifyCallContinuation(); } @@ -171,7 +175,6 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, } else { - demodulatedBuffer.decrementUserCount(); notifyIdle(); } } @@ -266,10 +269,10 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, { if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE) { - if(mIQFilter != null) + if(mIBasebandFilter != null) { - mIQFilter.dispose(); - mIQFilter = null; + mIBasebandFilter = null; + mQBasebandFilter = null; } double sampleRate = sourceEvent.getValue().doubleValue(); @@ -292,7 +295,8 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, decimatedSampleRate /= decimationRate; } - mDecimationFilter = DecimationFilterFactory.getComplexDecimationFilter(decimationRate); + mIDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimationRate); + mQDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimationRate); if((decimatedSampleRate < (2.0 * mChannelBandwidth))) { @@ -304,7 +308,7 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, int passBandStop = (int)(mChannelBandwidth * .8); int stopBandStart = (int)mChannelBandwidth; - float[] filterTaps = null; + float[] coefficients = null; FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() .sampleRate(decimatedSampleRate * 2) @@ -320,7 +324,7 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, try { - filterTaps = FilterFactory.getTaps(specification); + coefficients = FilterFactory.getTaps(specification); } catch(FilterDesignException fde) { @@ -329,28 +333,25 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, "] - will proceed using sinc (low-pass) filter"); } - if(filterTaps == null) + if(coefficients == null) { mLog.info("Unable to use remez filter designer for sample rate [" + decimatedSampleRate + "] pass band stop [" + passBandStop + "] and stop band start [" + stopBandStart + "] - will proceed using simple low pass filter design"); - filterTaps = FilterFactory.getLowPass(decimatedSampleRate, passBandStop, stopBandStart, 60, - Window.WindowType.HAMMING, true); + coefficients = FilterFactory.getLowPass(decimatedSampleRate, passBandStop, stopBandStart, 60, + WindowType.HAMMING, true); } - mIQFilter = new ComplexFIRFilter2(filterTaps); + mIBasebandFilter = FilterFactory.getRealFilter(coefficients); + mQBasebandFilter = FilterFactory.getRealFilter(coefficients); - mResampler = new RealResampler(decimatedSampleRate, mOutputSampleRate, 2000, 1000); + mResampler = new RealResampler(decimatedSampleRate, mOutputSampleRate, 4192, 512); - mResampler.setListener(reusableFloatBuffer -> + mResampler.setListener(resampled -> { - if(mResampledReusableBufferListener != null) + if(mResampledBufferListener != null) { - mResampledReusableBufferListener.receive(reusableFloatBuffer); - } - else - { - reusableFloatBuffer.decrementUserCount(); + mResampledBufferListener.receive(resampled); } }); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/C4FMSymbolFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/C4FMSymbolFilter.java deleted file mode 100644 index a3e46de5..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/C4FMSymbolFilter.java +++ /dev/null @@ -1,471 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 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.p25.phase1; - -import io.github.dsheirer.dsp.gain.DirectGainControl; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.sample.real.RealSampleListener; -import io.github.dsheirer.sample.real.RealSampleProvider; -import io.github.dsheirer.source.ISourceEventListener; -import io.github.dsheirer.source.SourceEvent; -import org.apache.commons.math3.util.FastMath; - -public class C4FMSymbolFilter implements Listener, ISourceEventListener, RealSampleProvider -{ - private static final float TAPS[][] = - { - {0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f}, // 0/128 - {-1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f}, // 1/128 - {-3.09412e-04f, 1.70888e-03f, -5.55134e-03f, 1.58840e-02f, 9.96891e-01f, -1.07209e-02f, 2.47942e-03f, -3.96391e-04f}, // 2/128 - {-4.64053e-04f, 2.56486e-03f, -8.34364e-03f, 2.39714e-02f, 9.95074e-01f, -1.59305e-02f, 3.69852e-03f, -5.92100e-04f}, // 3/128 - {-6.18544e-04f, 3.42130e-03f, -1.11453e-02f, 3.21531e-02f, 9.93082e-01f, -2.10389e-02f, 4.90322e-03f, -7.86031e-04f}, // 4/128 - {-7.72802e-04f, 4.27773e-03f, -1.39548e-02f, 4.04274e-02f, 9.90917e-01f, -2.60456e-02f, 6.09305e-03f, -9.78093e-04f}, // 5/128 - {-9.26747e-04f, 5.13372e-03f, -1.67710e-02f, 4.87921e-02f, 9.88580e-01f, -3.09503e-02f, 7.26755e-03f, -1.16820e-03f}, // 6/128 - {-1.08030e-03f, 5.98883e-03f, -1.95925e-02f, 5.72454e-02f, 9.86071e-01f, -3.57525e-02f, 8.42626e-03f, -1.35627e-03f}, // 7/128 - {-1.23337e-03f, 6.84261e-03f, -2.24178e-02f, 6.57852e-02f, 9.83392e-01f, -4.04519e-02f, 9.56876e-03f, -1.54221e-03f}, // 8/128 - {-1.38589e-03f, 7.69462e-03f, -2.52457e-02f, 7.44095e-02f, 9.80543e-01f, -4.50483e-02f, 1.06946e-02f, -1.72594e-03f}, // 9/128 - {-1.53777e-03f, 8.54441e-03f, -2.80746e-02f, 8.31162e-02f, 9.77526e-01f, -4.95412e-02f, 1.18034e-02f, -1.90738e-03f}, // 10/128 - {-1.68894e-03f, 9.39154e-03f, -3.09033e-02f, 9.19033e-02f, 9.74342e-01f, -5.39305e-02f, 1.28947e-02f, -2.08645e-03f}, // 11/128 - {-1.83931e-03f, 1.02356e-02f, -3.37303e-02f, 1.00769e-01f, 9.70992e-01f, -5.82159e-02f, 1.39681e-02f, -2.26307e-03f}, // 12/128 - {-1.98880e-03f, 1.10760e-02f, -3.65541e-02f, 1.09710e-01f, 9.67477e-01f, -6.23972e-02f, 1.50233e-02f, -2.43718e-03f}, // 13/128 - {-2.13733e-03f, 1.19125e-02f, -3.93735e-02f, 1.18725e-01f, 9.63798e-01f, -6.64743e-02f, 1.60599e-02f, -2.60868e-03f}, // 14/128 - {-2.28483e-03f, 1.27445e-02f, -4.21869e-02f, 1.27812e-01f, 9.59958e-01f, -7.04471e-02f, 1.70776e-02f, -2.77751e-03f}, // 15/128 - {-2.43121e-03f, 1.35716e-02f, -4.49929e-02f, 1.36968e-01f, 9.55956e-01f, -7.43154e-02f, 1.80759e-02f, -2.94361e-03f}, // 16/128 - {-2.57640e-03f, 1.43934e-02f, -4.77900e-02f, 1.46192e-01f, 9.51795e-01f, -7.80792e-02f, 1.90545e-02f, -3.10689e-03f}, // 17/128 - {-2.72032e-03f, 1.52095e-02f, -5.05770e-02f, 1.55480e-01f, 9.47477e-01f, -8.17385e-02f, 2.00132e-02f, -3.26730e-03f}, // 18/128 - {-2.86289e-03f, 1.60193e-02f, -5.33522e-02f, 1.64831e-01f, 9.43001e-01f, -8.52933e-02f, 2.09516e-02f, -3.42477e-03f}, // 19/128 - {-3.00403e-03f, 1.68225e-02f, -5.61142e-02f, 1.74242e-01f, 9.38371e-01f, -8.87435e-02f, 2.18695e-02f, -3.57923e-03f}, // 20/128 - {-3.14367e-03f, 1.76185e-02f, -5.88617e-02f, 1.83711e-01f, 9.33586e-01f, -9.20893e-02f, 2.27664e-02f, -3.73062e-03f}, // 21/128 - {-3.28174e-03f, 1.84071e-02f, -6.15931e-02f, 1.93236e-01f, 9.28650e-01f, -9.53307e-02f, 2.36423e-02f, -3.87888e-03f}, // 22/128 - {-3.41815e-03f, 1.91877e-02f, -6.43069e-02f, 2.02814e-01f, 9.23564e-01f, -9.84679e-02f, 2.44967e-02f, -4.02397e-03f}, // 23/128 - {-3.55283e-03f, 1.99599e-02f, -6.70018e-02f, 2.12443e-01f, 9.18329e-01f, -1.01501e-01f, 2.53295e-02f, -4.16581e-03f}, // 24/128 - {-3.68570e-03f, 2.07233e-02f, -6.96762e-02f, 2.22120e-01f, 9.12947e-01f, -1.04430e-01f, 2.61404e-02f, -4.30435e-03f}, // 25/128 - {-3.81671e-03f, 2.14774e-02f, -7.23286e-02f, 2.31843e-01f, 9.07420e-01f, -1.07256e-01f, 2.69293e-02f, -4.43955e-03f}, // 26/128 - {-3.94576e-03f, 2.22218e-02f, -7.49577e-02f, 2.41609e-01f, 9.01749e-01f, -1.09978e-01f, 2.76957e-02f, -4.57135e-03f}, // 27/128 - {-4.07279e-03f, 2.29562e-02f, -7.75620e-02f, 2.51417e-01f, 8.95936e-01f, -1.12597e-01f, 2.84397e-02f, -4.69970e-03f}, // 28/128 - {-4.19774e-03f, 2.36801e-02f, -8.01399e-02f, 2.61263e-01f, 8.89984e-01f, -1.15113e-01f, 2.91609e-02f, -4.82456e-03f}, // 29/128 - {-4.32052e-03f, 2.43930e-02f, -8.26900e-02f, 2.71144e-01f, 8.83893e-01f, -1.17526e-01f, 2.98593e-02f, -4.94589e-03f}, // 30/128 - {-4.44107e-03f, 2.50946e-02f, -8.52109e-02f, 2.81060e-01f, 8.77666e-01f, -1.19837e-01f, 3.05345e-02f, -5.06363e-03f}, // 31/128 - {-4.55932e-03f, 2.57844e-02f, -8.77011e-02f, 2.91006e-01f, 8.71305e-01f, -1.22047e-01f, 3.11866e-02f, -5.17776e-03f}, // 32/128 - {-4.67520e-03f, 2.64621e-02f, -9.01591e-02f, 3.00980e-01f, 8.64812e-01f, -1.24154e-01f, 3.18153e-02f, -5.28823e-03f}, // 33/128 - {-4.78866e-03f, 2.71272e-02f, -9.25834e-02f, 3.10980e-01f, 8.58189e-01f, -1.26161e-01f, 3.24205e-02f, -5.39500e-03f}, // 34/128 - {-4.89961e-03f, 2.77794e-02f, -9.49727e-02f, 3.21004e-01f, 8.51437e-01f, -1.28068e-01f, 3.30021e-02f, -5.49804e-03f}, // 35/128 - {-5.00800e-03f, 2.84182e-02f, -9.73254e-02f, 3.31048e-01f, 8.44559e-01f, -1.29874e-01f, 3.35600e-02f, -5.59731e-03f}, // 36/128 - {-5.11376e-03f, 2.90433e-02f, -9.96402e-02f, 3.41109e-01f, 8.37557e-01f, -1.31581e-01f, 3.40940e-02f, -5.69280e-03f}, // 37/128 - {-5.21683e-03f, 2.96543e-02f, -1.01915e-01f, 3.51186e-01f, 8.30432e-01f, -1.33189e-01f, 3.46042e-02f, -5.78446e-03f}, // 38/128 - {-5.31716e-03f, 3.02507e-02f, -1.04150e-01f, 3.61276e-01f, 8.23188e-01f, -1.34699e-01f, 3.50903e-02f, -5.87227e-03f}, // 39/128 - {-5.41467e-03f, 3.08323e-02f, -1.06342e-01f, 3.71376e-01f, 8.15826e-01f, -1.36111e-01f, 3.55525e-02f, -5.95620e-03f}, // 40/128 - {-5.50931e-03f, 3.13987e-02f, -1.08490e-01f, 3.81484e-01f, 8.08348e-01f, -1.37426e-01f, 3.59905e-02f, -6.03624e-03f}, // 41/128 - {-5.60103e-03f, 3.19495e-02f, -1.10593e-01f, 3.91596e-01f, 8.00757e-01f, -1.38644e-01f, 3.64044e-02f, -6.11236e-03f}, // 42/128 - {-5.68976e-03f, 3.24843e-02f, -1.12650e-01f, 4.01710e-01f, 7.93055e-01f, -1.39767e-01f, 3.67941e-02f, -6.18454e-03f}, // 43/128 - {-5.77544e-03f, 3.30027e-02f, -1.14659e-01f, 4.11823e-01f, 7.85244e-01f, -1.40794e-01f, 3.71596e-02f, -6.25277e-03f}, // 44/128 - {-5.85804e-03f, 3.35046e-02f, -1.16618e-01f, 4.21934e-01f, 7.77327e-01f, -1.41727e-01f, 3.75010e-02f, -6.31703e-03f}, // 45/128 - {-5.93749e-03f, 3.39894e-02f, -1.18526e-01f, 4.32038e-01f, 7.69305e-01f, -1.42566e-01f, 3.78182e-02f, -6.37730e-03f}, // 46/128 - {-6.01374e-03f, 3.44568e-02f, -1.20382e-01f, 4.42134e-01f, 7.61181e-01f, -1.43313e-01f, 3.81111e-02f, -6.43358e-03f}, // 47/128 - {-6.08674e-03f, 3.49066e-02f, -1.22185e-01f, 4.52218e-01f, 7.52958e-01f, -1.43968e-01f, 3.83800e-02f, -6.48585e-03f}, // 48/128 - {-6.15644e-03f, 3.53384e-02f, -1.23933e-01f, 4.62289e-01f, 7.44637e-01f, -1.44531e-01f, 3.86247e-02f, -6.53412e-03f}, // 49/128 - {-6.22280e-03f, 3.57519e-02f, -1.25624e-01f, 4.72342e-01f, 7.36222e-01f, -1.45004e-01f, 3.88454e-02f, -6.57836e-03f}, // 50/128 - {-6.28577e-03f, 3.61468e-02f, -1.27258e-01f, 4.82377e-01f, 7.27714e-01f, -1.45387e-01f, 3.90420e-02f, -6.61859e-03f}, // 51/128 - {-6.34530e-03f, 3.65227e-02f, -1.28832e-01f, 4.92389e-01f, 7.19116e-01f, -1.45682e-01f, 3.92147e-02f, -6.65479e-03f}, // 52/128 - {-6.40135e-03f, 3.68795e-02f, -1.30347e-01f, 5.02377e-01f, 7.10431e-01f, -1.45889e-01f, 3.93636e-02f, -6.68698e-03f}, // 53/128 - {-6.45388e-03f, 3.72167e-02f, -1.31800e-01f, 5.12337e-01f, 7.01661e-01f, -1.46009e-01f, 3.94886e-02f, -6.71514e-03f}, // 54/128 - {-6.50285e-03f, 3.75341e-02f, -1.33190e-01f, 5.22267e-01f, 6.92808e-01f, -1.46043e-01f, 3.95900e-02f, -6.73929e-03f}, // 55/128 - {-6.54823e-03f, 3.78315e-02f, -1.34515e-01f, 5.32164e-01f, 6.83875e-01f, -1.45993e-01f, 3.96678e-02f, -6.75943e-03f}, // 56/128 - {-6.58996e-03f, 3.81085e-02f, -1.35775e-01f, 5.42025e-01f, 6.74865e-01f, -1.45859e-01f, 3.97222e-02f, -6.77557e-03f}, // 57/128 - {-6.62802e-03f, 3.83650e-02f, -1.36969e-01f, 5.51849e-01f, 6.65779e-01f, -1.45641e-01f, 3.97532e-02f, -6.78771e-03f}, // 58/128 - {-6.66238e-03f, 3.86006e-02f, -1.38094e-01f, 5.61631e-01f, 6.56621e-01f, -1.45343e-01f, 3.97610e-02f, -6.79588e-03f}, // 59/128 - {-6.69300e-03f, 3.88151e-02f, -1.39150e-01f, 5.71370e-01f, 6.47394e-01f, -1.44963e-01f, 3.97458e-02f, -6.80007e-03f}, // 60/128 - {-6.71985e-03f, 3.90083e-02f, -1.40136e-01f, 5.81063e-01f, 6.38099e-01f, -1.44503e-01f, 3.97077e-02f, -6.80032e-03f}, // 61/128 - {-6.74291e-03f, 3.91800e-02f, -1.41050e-01f, 5.90706e-01f, 6.28739e-01f, -1.43965e-01f, 3.96469e-02f, -6.79662e-03f}, // 62/128 - {-6.76214e-03f, 3.93299e-02f, -1.41891e-01f, 6.00298e-01f, 6.19318e-01f, -1.43350e-01f, 3.95635e-02f, -6.78902e-03f}, // 63/128 - {-6.77751e-03f, 3.94578e-02f, -1.42658e-01f, 6.09836e-01f, 6.09836e-01f, -1.42658e-01f, 3.94578e-02f, -6.77751e-03f}, // 64/128 - {-6.78902e-03f, 3.95635e-02f, -1.43350e-01f, 6.19318e-01f, 6.00298e-01f, -1.41891e-01f, 3.93299e-02f, -6.76214e-03f}, // 65/128 - {-6.79662e-03f, 3.96469e-02f, -1.43965e-01f, 6.28739e-01f, 5.90706e-01f, -1.41050e-01f, 3.91800e-02f, -6.74291e-03f}, // 66/128 - {-6.80032e-03f, 3.97077e-02f, -1.44503e-01f, 6.38099e-01f, 5.81063e-01f, -1.40136e-01f, 3.90083e-02f, -6.71985e-03f}, // 67/128 - {-6.80007e-03f, 3.97458e-02f, -1.44963e-01f, 6.47394e-01f, 5.71370e-01f, -1.39150e-01f, 3.88151e-02f, -6.69300e-03f}, // 68/128 - {-6.79588e-03f, 3.97610e-02f, -1.45343e-01f, 6.56621e-01f, 5.61631e-01f, -1.38094e-01f, 3.86006e-02f, -6.66238e-03f}, // 69/128 - {-6.78771e-03f, 3.97532e-02f, -1.45641e-01f, 6.65779e-01f, 5.51849e-01f, -1.36969e-01f, 3.83650e-02f, -6.62802e-03f}, // 70/128 - {-6.77557e-03f, 3.97222e-02f, -1.45859e-01f, 6.74865e-01f, 5.42025e-01f, -1.35775e-01f, 3.81085e-02f, -6.58996e-03f}, // 71/128 - {-6.75943e-03f, 3.96678e-02f, -1.45993e-01f, 6.83875e-01f, 5.32164e-01f, -1.34515e-01f, 3.78315e-02f, -6.54823e-03f}, // 72/128 - {-6.73929e-03f, 3.95900e-02f, -1.46043e-01f, 6.92808e-01f, 5.22267e-01f, -1.33190e-01f, 3.75341e-02f, -6.50285e-03f}, // 73/128 - {-6.71514e-03f, 3.94886e-02f, -1.46009e-01f, 7.01661e-01f, 5.12337e-01f, -1.31800e-01f, 3.72167e-02f, -6.45388e-03f}, // 74/128 - {-6.68698e-03f, 3.93636e-02f, -1.45889e-01f, 7.10431e-01f, 5.02377e-01f, -1.30347e-01f, 3.68795e-02f, -6.40135e-03f}, // 75/128 - {-6.65479e-03f, 3.92147e-02f, -1.45682e-01f, 7.19116e-01f, 4.92389e-01f, -1.28832e-01f, 3.65227e-02f, -6.34530e-03f}, // 76/128 - {-6.61859e-03f, 3.90420e-02f, -1.45387e-01f, 7.27714e-01f, 4.82377e-01f, -1.27258e-01f, 3.61468e-02f, -6.28577e-03f}, // 77/128 - {-6.57836e-03f, 3.88454e-02f, -1.45004e-01f, 7.36222e-01f, 4.72342e-01f, -1.25624e-01f, 3.57519e-02f, -6.22280e-03f}, // 78/128 - {-6.53412e-03f, 3.86247e-02f, -1.44531e-01f, 7.44637e-01f, 4.62289e-01f, -1.23933e-01f, 3.53384e-02f, -6.15644e-03f}, // 79/128 - {-6.48585e-03f, 3.83800e-02f, -1.43968e-01f, 7.52958e-01f, 4.52218e-01f, -1.22185e-01f, 3.49066e-02f, -6.08674e-03f}, // 80/128 - {-6.43358e-03f, 3.81111e-02f, -1.43313e-01f, 7.61181e-01f, 4.42134e-01f, -1.20382e-01f, 3.44568e-02f, -6.01374e-03f}, // 81/128 - {-6.37730e-03f, 3.78182e-02f, -1.42566e-01f, 7.69305e-01f, 4.32038e-01f, -1.18526e-01f, 3.39894e-02f, -5.93749e-03f}, // 82/128 - {-6.31703e-03f, 3.75010e-02f, -1.41727e-01f, 7.77327e-01f, 4.21934e-01f, -1.16618e-01f, 3.35046e-02f, -5.85804e-03f}, // 83/128 - {-6.25277e-03f, 3.71596e-02f, -1.40794e-01f, 7.85244e-01f, 4.11823e-01f, -1.14659e-01f, 3.30027e-02f, -5.77544e-03f}, // 84/128 - {-6.18454e-03f, 3.67941e-02f, -1.39767e-01f, 7.93055e-01f, 4.01710e-01f, -1.12650e-01f, 3.24843e-02f, -5.68976e-03f}, // 85/128 - {-6.11236e-03f, 3.64044e-02f, -1.38644e-01f, 8.00757e-01f, 3.91596e-01f, -1.10593e-01f, 3.19495e-02f, -5.60103e-03f}, // 86/128 - {-6.03624e-03f, 3.59905e-02f, -1.37426e-01f, 8.08348e-01f, 3.81484e-01f, -1.08490e-01f, 3.13987e-02f, -5.50931e-03f}, // 87/128 - {-5.95620e-03f, 3.55525e-02f, -1.36111e-01f, 8.15826e-01f, 3.71376e-01f, -1.06342e-01f, 3.08323e-02f, -5.41467e-03f}, // 88/128 - {-5.87227e-03f, 3.50903e-02f, -1.34699e-01f, 8.23188e-01f, 3.61276e-01f, -1.04150e-01f, 3.02507e-02f, -5.31716e-03f}, // 89/128 - {-5.78446e-03f, 3.46042e-02f, -1.33189e-01f, 8.30432e-01f, 3.51186e-01f, -1.01915e-01f, 2.96543e-02f, -5.21683e-03f}, // 90/128 - {-5.69280e-03f, 3.40940e-02f, -1.31581e-01f, 8.37557e-01f, 3.41109e-01f, -9.96402e-02f, 2.90433e-02f, -5.11376e-03f}, // 91/128 - {-5.59731e-03f, 3.35600e-02f, -1.29874e-01f, 8.44559e-01f, 3.31048e-01f, -9.73254e-02f, 2.84182e-02f, -5.00800e-03f}, // 92/128 - {-5.49804e-03f, 3.30021e-02f, -1.28068e-01f, 8.51437e-01f, 3.21004e-01f, -9.49727e-02f, 2.77794e-02f, -4.89961e-03f}, // 93/128 - {-5.39500e-03f, 3.24205e-02f, -1.26161e-01f, 8.58189e-01f, 3.10980e-01f, -9.25834e-02f, 2.71272e-02f, -4.78866e-03f}, // 94/128 - {-5.28823e-03f, 3.18153e-02f, -1.24154e-01f, 8.64812e-01f, 3.00980e-01f, -9.01591e-02f, 2.64621e-02f, -4.67520e-03f}, // 95/128 - {-5.17776e-03f, 3.11866e-02f, -1.22047e-01f, 8.71305e-01f, 2.91006e-01f, -8.77011e-02f, 2.57844e-02f, -4.55932e-03f}, // 96/128 - {-5.06363e-03f, 3.05345e-02f, -1.19837e-01f, 8.77666e-01f, 2.81060e-01f, -8.52109e-02f, 2.50946e-02f, -4.44107e-03f}, // 97/128 - {-4.94589e-03f, 2.98593e-02f, -1.17526e-01f, 8.83893e-01f, 2.71144e-01f, -8.26900e-02f, 2.43930e-02f, -4.32052e-03f}, // 98/128 - {-4.82456e-03f, 2.91609e-02f, -1.15113e-01f, 8.89984e-01f, 2.61263e-01f, -8.01399e-02f, 2.36801e-02f, -4.19774e-03f}, // 99/128 - {-4.69970e-03f, 2.84397e-02f, -1.12597e-01f, 8.95936e-01f, 2.51417e-01f, -7.75620e-02f, 2.29562e-02f, -4.07279e-03f}, // 100/128 - {-4.57135e-03f, 2.76957e-02f, -1.09978e-01f, 9.01749e-01f, 2.41609e-01f, -7.49577e-02f, 2.22218e-02f, -3.94576e-03f}, // 101/128 - {-4.43955e-03f, 2.69293e-02f, -1.07256e-01f, 9.07420e-01f, 2.31843e-01f, -7.23286e-02f, 2.14774e-02f, -3.81671e-03f}, // 102/128 - {-4.30435e-03f, 2.61404e-02f, -1.04430e-01f, 9.12947e-01f, 2.22120e-01f, -6.96762e-02f, 2.07233e-02f, -3.68570e-03f}, // 103/128 - {-4.16581e-03f, 2.53295e-02f, -1.01501e-01f, 9.18329e-01f, 2.12443e-01f, -6.70018e-02f, 1.99599e-02f, -3.55283e-03f}, // 104/128 - {-4.02397e-03f, 2.44967e-02f, -9.84679e-02f, 9.23564e-01f, 2.02814e-01f, -6.43069e-02f, 1.91877e-02f, -3.41815e-03f}, // 105/128 - {-3.87888e-03f, 2.36423e-02f, -9.53307e-02f, 9.28650e-01f, 1.93236e-01f, -6.15931e-02f, 1.84071e-02f, -3.28174e-03f}, // 106/128 - {-3.73062e-03f, 2.27664e-02f, -9.20893e-02f, 9.33586e-01f, 1.83711e-01f, -5.88617e-02f, 1.76185e-02f, -3.14367e-03f}, // 107/128 - {-3.57923e-03f, 2.18695e-02f, -8.87435e-02f, 9.38371e-01f, 1.74242e-01f, -5.61142e-02f, 1.68225e-02f, -3.00403e-03f}, // 108/128 - {-3.42477e-03f, 2.09516e-02f, -8.52933e-02f, 9.43001e-01f, 1.64831e-01f, -5.33522e-02f, 1.60193e-02f, -2.86289e-03f}, // 109/128 - {-3.26730e-03f, 2.00132e-02f, -8.17385e-02f, 9.47477e-01f, 1.55480e-01f, -5.05770e-02f, 1.52095e-02f, -2.72032e-03f}, // 110/128 - {-3.10689e-03f, 1.90545e-02f, -7.80792e-02f, 9.51795e-01f, 1.46192e-01f, -4.77900e-02f, 1.43934e-02f, -2.57640e-03f}, // 111/128 - {-2.94361e-03f, 1.80759e-02f, -7.43154e-02f, 9.55956e-01f, 1.36968e-01f, -4.49929e-02f, 1.35716e-02f, -2.43121e-03f}, // 112/128 - {-2.77751e-03f, 1.70776e-02f, -7.04471e-02f, 9.59958e-01f, 1.27812e-01f, -4.21869e-02f, 1.27445e-02f, -2.28483e-03f}, // 113/128 - {-2.60868e-03f, 1.60599e-02f, -6.64743e-02f, 9.63798e-01f, 1.18725e-01f, -3.93735e-02f, 1.19125e-02f, -2.13733e-03f}, // 114/128 - {-2.43718e-03f, 1.50233e-02f, -6.23972e-02f, 9.67477e-01f, 1.09710e-01f, -3.65541e-02f, 1.10760e-02f, -1.98880e-03f}, // 115/128 - {-2.26307e-03f, 1.39681e-02f, -5.82159e-02f, 9.70992e-01f, 1.00769e-01f, -3.37303e-02f, 1.02356e-02f, -1.83931e-03f}, // 116/128 - {-2.08645e-03f, 1.28947e-02f, -5.39305e-02f, 9.74342e-01f, 9.19033e-02f, -3.09033e-02f, 9.39154e-03f, -1.68894e-03f}, // 117/128 - {-1.90738e-03f, 1.18034e-02f, -4.95412e-02f, 9.77526e-01f, 8.31162e-02f, -2.80746e-02f, 8.54441e-03f, -1.53777e-03f}, // 118/128 - {-1.72594e-03f, 1.06946e-02f, -4.50483e-02f, 9.80543e-01f, 7.44095e-02f, -2.52457e-02f, 7.69462e-03f, -1.38589e-03f}, // 119/128 - {-1.54221e-03f, 9.56876e-03f, -4.04519e-02f, 9.83392e-01f, 6.57852e-02f, -2.24178e-02f, 6.84261e-03f, -1.23337e-03f}, // 120/128 - {-1.35627e-03f, 8.42626e-03f, -3.57525e-02f, 9.86071e-01f, 5.72454e-02f, -1.95925e-02f, 5.98883e-03f, -1.08030e-03f}, // 121/128 - {-1.16820e-03f, 7.26755e-03f, -3.09503e-02f, 9.88580e-01f, 4.87921e-02f, -1.67710e-02f, 5.13372e-03f, -9.26747e-04f}, // 122/128 - {-9.78093e-04f, 6.09305e-03f, -2.60456e-02f, 9.90917e-01f, 4.04274e-02f, -1.39548e-02f, 4.27773e-03f, -7.72802e-04f}, // 123/128 - {-7.86031e-04f, 4.90322e-03f, -2.10389e-02f, 9.93082e-01f, 3.21531e-02f, -1.11453e-02f, 3.42130e-03f, -6.18544e-04f}, // 124/128 - {-5.92100e-04f, 3.69852e-03f, -1.59305e-02f, 9.95074e-01f, 2.39714e-02f, -8.34364e-03f, 2.56486e-03f, -4.64053e-04f}, // 125/128 - {-3.96391e-04f, 2.47942e-03f, -1.07209e-02f, 9.96891e-01f, 1.58840e-02f, -5.55134e-03f, 1.70888e-03f, -3.09412e-04f}, // 126/128 - {-1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f}, // 127/128 - {0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f}, // 128/128 - }; - - private static final int NUMBER_FILTER_TAPS = 8; - private static final int NUMBER_FILTER_STEPS = 128; - - private static final int SAMPLE_RATE = 48000; - private static final int SYMBOL_RATE = 4800; - - /* PLLGain loop gain constant */ - private static final double K_SYMBOL_SPREAD = 0.0100; - - /* Constraints on symbol spreading */ - private static final float SYMBOL_SPREAD_MAX = 2.4f; // upper range limit: +20% - private static final float SYMBOL_SPREAD_MIN = 1.6f; // lower range limit: -20% - - /* Symbol clock tracking loop gain */ - private static final double K_SYMBOL_TIMING = 0.025; - - /* Coarse and fine frequency tracking constants */ - private static final double K_COARSE_FREQUENCY = 0.00125; - private static final double K_FINE_FREQUENCY = 0.125; - - /* Frequency correction broadcast threshold */ -// private static final double COARSE_FREQUENCY_DEADBAND = 1.66; - private static final double COARSE_FREQUENCY_THRESHOLD = 1.20; - - /* 2.0 symbol spread gives -3, -1, 1, 3 */ - private float mSymbolSpread = 2.0f; - private float mSymbolClock = 0.0f; - private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE; - - private float mFineFrequencyCorrection = 0.0f; - private float mCoarseFrequencyCorrection = 0.0f; - - private float mHistory[] = new float[NUMBER_FILTER_TAPS]; - private int mHistoryLast = 0; - - private RealSampleListener mListener; - - private DirectGainControl mGainController = new DirectGainControl(15.0f, 0.1f, 35.0f, 0.3f); - - private int mFrequencyAdjustmentRequested = 0; - private int mFrequencyCorrection = 0; - private int mFrequencyCorrectionMaximum = 3000; - private boolean mResetFrequencyTracker = false; - private FrequencyCorrectionProcessor mFrequencyCorrectionProcessor; - private Listener mFrequencyChangeListener; - - /** - * C4FM Symbol Filter - * - * Sample Gain values - the gain of incoming sample values will critically - * impact the performance of this filter. Gain should be adjusted to - * optimize the value of mSymbolSpread toward a value of 2.0. If the value - * is less than 2.0, then increase gain. If the value is over 2.0, then - * decrease gain. - * - * When preceded by the RealAutomaticGainFilter class, an optimal gain - * setting for this filter is 15.4 and that yields a symbol spread that - * is centered on 2.0, ranging 1.92 to 2.08. - */ - public C4FMSymbolFilter(int frequencyCorrectionMaximum) - { - mFrequencyCorrectionMaximum = frequencyCorrectionMaximum; - } - - public void dispose() - { - mGainController = null; -// mFrequencyCorrectionControl = null; - mListener = null; - } - - @Override - public void receive(ReusableFloatBuffer buffer) - { - for(float sample : buffer.getSamples()) - { - receive(sample); - } - - /* If a frequency correction was requested during the processing of this - * buffer, we'll apply the change and it will be reflected in the next - * arriving buffer. Reset the lock on frequency correction and reset - * the internal frequency correction tracker */ - if(mFrequencyAdjustmentRequested != 0) - { - int correction = mFrequencyCorrection + mFrequencyAdjustmentRequested; - - if(correction > mFrequencyCorrectionMaximum) - { - correction = mFrequencyCorrectionMaximum; - } - else if(correction < -mFrequencyCorrectionMaximum) - { - correction = -mFrequencyCorrectionMaximum; - } - - broadcast(SourceEvent.channelFrequencyCorrectionRequest(correction)); - - mFrequencyAdjustmentRequested = 0; - } - } - - public void receive(float sample) - { - sample = mGainController.correct(sample); - - if(mResetFrequencyTracker) - { - mCoarseFrequencyCorrection = 0.0f; - mFineFrequencyCorrection = 0.0f; - - broadcast(SourceEvent.channelFrequencyCorrectionRequest(0l)); - - mResetFrequencyTracker = false; - } - - mSymbolClock += mSymbolTime; - - mHistory[mHistoryLast++] = sample; - - mHistoryLast %= NUMBER_FILTER_TAPS; - - if(mSymbolClock > 1.0f) - { - mSymbolClock -= 1.0f; - - int imu = (int) FastMath.floor(0.5 + - ((float)NUMBER_FILTER_STEPS * (mSymbolClock / mSymbolTime))); - - if(imu >= NUMBER_FILTER_STEPS) - { - imu = NUMBER_FILTER_STEPS - 1; - } - - int imu_p1 = imu + 1; - - int j = mHistoryLast; - - double interp = 0.0; - double interp_p1 = 0.0; - - for(int i = 0; i < NUMBER_FILTER_TAPS; i++) - { - interp += TAPS[imu][i] * mHistory[j]; - interp_p1 += TAPS[imu_p1][i] * mHistory[j]; - - j = (j + 1) % NUMBER_FILTER_TAPS; - } - - /* Output symbol will be interpolated value corrected for symbol - * spread and frequency offset */ - interp -= mFineFrequencyCorrection; - interp_p1 -= mFineFrequencyCorrection; - - /* Correct output for symbol deviation (spread) */ - float output = (float)(2.0 * interp / mSymbolSpread); - - /* Detect received symbol error: basically use a hard decision and - * subtract off expected position nominal symbol level which will be - * +/- 0.5 * symbol spread and +/- 1.5 symbol spread. Remember that - * nominal symbol spread will be 2.0 */ - double symbolError; - - if(interp < -mSymbolSpread) - { - /* symbol is -3: Expected at -1.5 * symbol spread */ - symbolError = interp + (1.5 * mSymbolSpread); - mSymbolSpread -= (symbolError * 0.5 * K_SYMBOL_SPREAD); - } - else if(interp < 0.0) - { - /* symbol is -1: Expected at -0.5 * symbol_spread */ - symbolError = interp + (0.5 * mSymbolSpread); - mSymbolSpread -= (symbolError * K_SYMBOL_SPREAD); - } - else if(interp < mSymbolSpread) - { - /* symbol is +1: Expected at +0.5 * symbol_spread */ - symbolError = interp - (0.5 * mSymbolSpread); - mSymbolSpread += (symbolError * K_SYMBOL_SPREAD); - } - else - { - /* symbol is +3: Expected at +1.5 * symbol_spread */ - symbolError = interp - (1.5 * mSymbolSpread); - mSymbolSpread += (symbolError * 0.5 * K_SYMBOL_SPREAD); - } - - /* Symbol clock tracking loop adjustment */ - if(interp_p1 < interp) - { - mSymbolClock += symbolError * K_SYMBOL_TIMING; - } - else - { - mSymbolClock -= symbolError * K_SYMBOL_TIMING; - } - - if(mSymbolSpread < SYMBOL_SPREAD_MIN) - { - mGainController.increase(); - - mSymbolSpread = SYMBOL_SPREAD_MIN; - } - else if(mSymbolSpread > SYMBOL_SPREAD_MAX) - { - mGainController.decrease(); - - mSymbolSpread = SYMBOL_SPREAD_MAX; - } - - mCoarseFrequencyCorrection += ((mFineFrequencyCorrection - - mCoarseFrequencyCorrection) * K_COARSE_FREQUENCY); - - mFineFrequencyCorrection += (symbolError * K_FINE_FREQUENCY); - - /* Queue a frequency adjustment (once per buffer) as needed */ - if(FastMath.abs(mCoarseFrequencyCorrection) > COARSE_FREQUENCY_THRESHOLD) - { - mFrequencyAdjustmentRequested = - 500 * (mCoarseFrequencyCorrection > 0 ? 1 : -1); - } - - /* dispatch the interpolated value to the listener */ - if(mListener != null) - { - mListener.receive(output); - } - } - } - - @Override - public void setListener(RealSampleListener listener) - { - mListener = listener; - } - - @Override - public void removeListener(RealSampleListener listener) - { - mListener = null; - } - - /** - * Broadcasts a frequency change event to the registered listener - * - * @param event - */ - public void broadcast(SourceEvent event) - { - if(mFrequencyChangeListener != null) - { - mFrequencyChangeListener.receive(event); - } - } - - /** - * Sets the listener for frequency change events. - * - * @param listener - */ - public void setFrequencyChangeListener(Listener listener) - { - mFrequencyChangeListener = listener; - } - - @Override - public Listener getSourceEventListener() - { - if(mFrequencyCorrectionProcessor == null) - { - mFrequencyCorrectionProcessor = new FrequencyCorrectionProcessor(); - } - - return mFrequencyCorrectionProcessor; - } - - /** - * Receives notifications that the channel frequency correction has been - * applied and updates internal tracking value. - */ - public class FrequencyCorrectionProcessor implements Listener - { - @Override - public void receive(SourceEvent event) - { - switch(event.getEvent()) - { - case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE: - mFrequencyCorrection = event.getValue().intValue(); - - //Reset internal frequency tracking - mCoarseFrequencyCorrection = 0.0f; - mFineFrequencyCorrection = 0.0f; - break; - case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE: - case NOTIFICATION_SAMPLE_RATE_CHANGE: - mResetFrequencyTracker = true; - break; - default: - break; - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java index d68298f2..24885908 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java @@ -25,16 +25,17 @@ import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.FeedbackDecoder; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; +import java.nio.ByteBuffer; + public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableByteBufferProvider + IComplexSamplesListener, Listener, IByteBufferProvider { private double mSampleRate; private Broadcaster mDibitBroadcaster = new Broadcaster<>(); @@ -78,7 +79,7 @@ public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEve * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mByteBufferAssembler.setBufferListener(listener); } @@ -87,7 +88,7 @@ public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEve * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mByteBufferAssembler.removeBufferListener(listener); } @@ -160,7 +161,7 @@ public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEve * Listener interface to receive reusable complex buffers */ @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return P25P1Decoder.this; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FM.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FM.java index d38ad8e9..de5d4af9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FM.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FM.java @@ -24,16 +24,16 @@ package io.github.dsheirer.module.decode.p25.phase1; import io.github.dsheirer.dsp.filter.FilterFactory; 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.gain.ComplexFeedForwardGainControl; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor; import io.github.dsheirer.dsp.psk.pll.PLLBandwidth; -import io.github.dsheirer.dsp.squelch.PowerMonitor; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,9 +51,10 @@ public class P25P1DecoderC4FM extends P25P1Decoder protected CostasLoop mCostasLoop; protected FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor; protected P25P1MessageFramer mMessageFramer; - private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); + protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); private Map mBasebandFilters = new HashMap<>(); - private ComplexFIRFilter2 mBasebandFilter; + protected IRealFilter mIBasebandFilter; + protected IRealFilter mQBasebandFilter; /** * P25 Phase 1 - standard C4FM modulation decoder. Uses Differential QPSK decoding with a Costas PLL and a @@ -68,7 +69,8 @@ public class P25P1DecoderC4FM extends P25P1Decoder public void setSampleRate(double sampleRate) { super.setSampleRate(sampleRate); - mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter()); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300); @@ -95,35 +97,21 @@ public class P25P1DecoderC4FM extends P25P1Decoder /** * Primary method for processing incoming complex sample buffers - * @param reusableComplexBuffer containing channelized complex samples + * @param samples containing channelized complex samples */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - //User accounting of the incoming buffer is handled by the filter - ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); //Process the buffer for power meter measurements (before gain is applied) - mPowerMonitor.process(basebandFiltered); + mPowerMonitor.process(i, q); - //User accounting of the incoming buffer is handled by the gain filter - ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); - - mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); - - //User accounting of the filtered buffer is handled by the demodulator - mQPSKDemodulator.receive(gainApplied); - } - - /** - * Filters the complex buffer and returns a new reusable complex buffer with the filtered contents. - * @param reusableComplexBuffer to filter - * @return filtered complex buffer - */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) - { - //User accounting of the incoming buffer is handled by the filter - return mBasebandFilter.filter(reusableComplexBuffer); + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FMInstrumented.java index 8b7438c9..6101314b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FMInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FMInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,7 +14,7 @@ * * 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.p25.phase1; @@ -23,8 +22,8 @@ import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; public class P25P1DecoderC4FMInstrumented extends P25P1DecoderC4FM { @@ -32,7 +31,7 @@ public class P25P1DecoderC4FMInstrumented extends P25P1DecoderC4FM private Listener mPLLFrequencyListener; private Listener mSamplesPerSymbolListener; private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; + private Listener mFilteredSymbolListener; private Listener mSymbolDecisionDataListener; /** @@ -44,19 +43,27 @@ public class P25P1DecoderC4FMInstrumented extends P25P1DecoderC4FM } /** - * Overrides the filter method so that we can capture the filtered samples for instrumentation + * Primary method for processing incoming complex sample buffers + * @param samples containing channelized complex samples */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) + @Override + public void receive(ComplexSamples samples) { - ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); if(mFilteredSymbolListener != null) { - filtered.incrementUserCount(); - mFilteredSymbolListener.receive(filtered); + mFilteredSymbolListener.receive(new ComplexSamples(i, q)); } - return filtered; + //Process the buffer for power meter measurements (before gain is applied) + mPowerMonitor.process(i, q); + + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -100,7 +107,7 @@ public class P25P1DecoderC4FMInstrumented extends P25P1DecoderC4FM ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener); } - public void setFilteredBufferListener(Listener listener) + public void setFilteredBufferListener(Listener listener) { mFilteredSymbolListener = listener; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSM.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSM.java index e37b1746..6db8f44f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSM.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSM.java @@ -22,26 +22,21 @@ package io.github.dsheirer.module.decode.p25.phase1; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window.WindowType; -import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2; -import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor; import io.github.dsheirer.dsp.psk.pll.PLLBandwidth; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.protocol.Protocol; -import io.github.dsheirer.record.binary.BinaryRecorder; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.SourceEvent; -import io.github.dsheirer.source.wave.ComplexWaveSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -52,8 +47,9 @@ public class P25P1DecoderLSM extends P25P1Decoder protected static final float SAMPLE_COUNTER_GAIN = 0.3f; private Map mBasebandFilters = new HashMap<>(); - private ComplexFIRFilter2 mBasebandFilter; - private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); + protected IRealFilter mIBasebandFilter; + protected IRealFilter mQBasebandFilter; + protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); protected DQPSKGardnerDemodulator mQPSKDemodulator; protected P25P1MessageFramer mMessageFramer; protected CostasLoop mCostasLoop; @@ -79,7 +75,8 @@ public class P25P1DecoderLSM extends P25P1Decoder { super.setSampleRate(sampleRate); - mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter()); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_200); @@ -108,37 +105,19 @@ public class P25P1DecoderLSM extends P25P1Decoder * Primary method for receiving incoming channel samples */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + //The filter will decrement the user count when finished - ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); //Process the buffer for power measurements - mPowerMonitor.process(basebandFiltered); + mPowerMonitor.process(i, q); - //AGC will decrement the user count when finished - ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); - - mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); - - //Decoder will decrement the user count when finished - mQPSKDemodulator.receive(gainApplied); - } - - /** - * Filters the complex buffer and returns a new reusable complex buffer with the filtered contents. - * - * @param reusableComplexBuffer to filter - * @return filtered complex buffer - */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) - { - //No additional filtering of the channel is currently needed, since the polyphase channelizer - //provides the filtering. - return reusableComplexBuffer; - - //User accounting of the incoming buffer is handled by the filter -// return mBasebandFilter.filter(reusableComplexBuffer); + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -194,44 +173,4 @@ public class P25P1DecoderLSM extends P25P1Decoder return filter; } - - public static void main(String[] args) - { - String path = "/media/denny/500G1EXT4/RadioRecordings/"; -// String input = "DFWAirport_Site_857_3875_baseband_20181213_223236.wav"; -// String output = "DFWAirport_Site_857_3875_baseband_20181213_223236"; - - String input = "P25P2_HCPM_Metrocrest_Dallas_857_7625_phase_2_not_motorola_baseband_20181213_224616_control_channel.wav"; - String output = "P25P2_HCPM_Metrocrest_Dallas_857_7625_phase_2_not_motorola_baseband_20181213_224616_control_channel"; - - - P25P1DecoderLSM decoder = new P25P1DecoderLSM(); - BinaryRecorder recorder = new BinaryRecorder(Path.of(path), output, Protocol.APCO25); - decoder.setBufferListener(recorder.getReusableByteBufferListener()); - recorder.start(); - - File file = new File(path + input); - - boolean running = true; - - try(ComplexWaveSource source = new ComplexWaveSource(file)) - { - decoder.setSampleRate(50000.0); - source.setListener(decoder); - source.start(); - - while(running) - { - source.next(200, true); - } - } - catch(IOException e) - { - mLog.error("Error", e); - running = false; - } - - recorder.stop(); - } - } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSMInstrumented.java index 0c4ad986..2cf8b055 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSMInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSMInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,7 +14,7 @@ * * 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.p25.phase1; @@ -23,8 +22,8 @@ import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; public class P25P1DecoderLSMInstrumented extends P25P1DecoderLSM { @@ -32,7 +31,7 @@ public class P25P1DecoderLSMInstrumented extends P25P1DecoderLSM private Listener mPLLFrequencyListener; private Listener mSamplesPerSymbolListener; private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; + private Listener mFilteredSymbolListener; private Listener mSymbolDecisionDataListener; /** @@ -46,19 +45,27 @@ public class P25P1DecoderLSMInstrumented extends P25P1DecoderLSM } /** - * Overrides the filter method so that we can capture the filtered samples for instrumentation + * Primary method for receiving incoming channel samples */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) + @Override + public void receive(ComplexSamples samples) { - ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + //The filter will decrement the user count when finished + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); if(mFilteredSymbolListener != null) { - filtered.incrementUserCount(); - mFilteredSymbolListener.receive(filtered); + mFilteredSymbolListener.receive(new ComplexSamples(i, q)); } - return filtered; + //Process the buffer for power measurements + mPowerMonitor.process(i, q); + + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -101,7 +108,7 @@ public class P25P1DecoderLSMInstrumented extends P25P1DecoderLSM ((DQPSKGardnerDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener); } - public void setFilteredBufferListener(Listener listener) + public void setFilteredBufferListener(Listener listener) { mFilteredSymbolListener = listener; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java index 4fef8d91..25ac11b8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -36,7 +36,6 @@ import io.github.dsheirer.message.StuffBitsMessage; import io.github.dsheirer.message.SyncLossMessage; import io.github.dsheirer.module.ProcessingChain; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.ip.IPacket; import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket; import io.github.dsheirer.module.decode.ip.udp.UDPPacket; @@ -53,17 +52,16 @@ import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.record.AudioRecordingManager; import io.github.dsheirer.record.binary.BinaryReader; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.util.function.Consumer; /** @@ -376,20 +374,15 @@ public class P25P1MessageFramer implements Listener, IP25P1DataUnitDetect * * @param buffer to process into a stream of dibits for processing. */ - public void receive(ReusableByteBuffer buffer) + public void receive(ByteBuffer buffer) { - //Updates current timestamp to the timestamp from the incoming buffer - setCurrentTime(buffer.getTimestamp()); - - for(byte value : buffer.getBytes()) + for(byte value : buffer.array()) { for(int x = 0; x <= 3; x++) { receive(Dibit.parse(value, x)); } } - - buffer.decrementUserCount(); } @Override @@ -545,7 +538,7 @@ public class P25P1MessageFramer implements Listener, IP25P1DataUnitDetect { while(reader.hasNext()) { - ReusableByteBuffer buffer = reader.next(); + ByteBuffer buffer = reader.next(); messageFramer.receive(buffer); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java index 0bafa507..7ee604f8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java @@ -25,19 +25,20 @@ import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.FeedbackDecoder; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; +import java.nio.ByteBuffer; + /** * Base P25 Phase 2 Decoder */ public abstract class P25P2Decoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableByteBufferProvider + IComplexSamplesListener, Listener, IByteBufferProvider { private double mSampleRate; private Broadcaster mDibitBroadcaster = new Broadcaster<>(); @@ -80,7 +81,7 @@ public abstract class P25P2Decoder extends FeedbackDecoder implements ISourceEve * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mByteBufferAssembler.setBufferListener(listener); } @@ -89,7 +90,7 @@ public abstract class P25P2Decoder extends FeedbackDecoder implements ISourceEve * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mByteBufferAssembler.removeBufferListener(listener); } @@ -163,7 +164,7 @@ public abstract class P25P2Decoder extends FeedbackDecoder implements ISourceEve * Listener interface to receive reusable complex buffers */ @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return P25P2Decoder.this; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java index 3dfc5f33..1cf97217 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java @@ -24,32 +24,25 @@ package io.github.dsheirer.module.decode.p25.phase2; import io.github.dsheirer.dsp.filter.FilterFactory; 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.gain.ComplexFeedForwardGainControl; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor; import io.github.dsheirer.dsp.psk.pll.PLLBandwidth; -import io.github.dsheirer.dsp.squelch.PowerMonitor; import io.github.dsheirer.identifier.Form; import io.github.dsheirer.identifier.IdentifierUpdateListener; import io.github.dsheirer.identifier.IdentifierUpdateNotification; -import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.message.SyncLossMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; -import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.SourceEvent; -import io.github.dsheirer.source.wave.ComplexWaveSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -64,9 +57,10 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate protected DQPSKGardnerDemodulator mQPSKDemodulator; protected CostasLoop mCostasLoop; protected P25P2MessageFramer mMessageFramer; - private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); + protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); private Map mBasebandFilters = new HashMap<>(); - private ComplexFIRFilter2 mBasebandFilter; + protected IRealFilter mIBasebandFilter; + protected IRealFilter mQBasebandFilter; private DecodeConfigP25Phase2 mDecodeConfigP25Phase2; private FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor; @@ -81,7 +75,8 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate { super.setSampleRate(sampleRate); - mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter()); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300); @@ -113,35 +108,21 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate /** * Primary method for processing incoming complex sample buffers - * @param reusableComplexBuffer containing channelized complex samples + * @param samples containing channelized complex samples */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - //User accounting of the incoming buffer is handled by the filter - ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); //Process the buffer for power measurements - mPowerMonitor.process(basebandFiltered); + mPowerMonitor.process(i, q); - //User accounting of the incoming buffer is handled by the gain filter - ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); - - mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); - - //User accounting of the filtered buffer is handled by the demodulator - mQPSKDemodulator.receive(gainApplied); - } - - /** - * Filters the complex buffer and returns a new reusable complex buffer with the filtered contents. - * @param reusableComplexBuffer to filter - * @return filtered complex buffer - */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) - { - //User accounting of the incoming buffer is handled by the filter - return mBasebandFilter.filter(reusableComplexBuffer); + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -224,83 +205,6 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate } } - public static class MessageErrorListener implements Listener - { - private int mMessageCount = 0; - private int mBitErrorCount = 0; - - @Override - public void receive(IMessage iMessage) - { - if(!(iMessage instanceof SyncLossMessage) && !(iMessage instanceof EncryptionSynchronizationSequence)) - { - mMessageCount++; - } - - if(iMessage instanceof MacMessage) - { - mBitErrorCount += ((MacMessage)iMessage).getBitErrorCount(); - } - - mLog.debug("[" + mMessageCount + " | " + mBitErrorCount + "] " + iMessage.toString()); - } - } - - public static void main(String[] args) - { -// String path = "/media/denny/500G1EXT4/RadioRecordings/APCO25P2/CNYICC/"; -// String input = "CNYICC_Rome_CNYICC_154_250_baseband_20190322_180331_good.wav"; -// String output = "CNYICC_ROME_154_250_8"; - - String path = "/home/denny/SDRTrunk/recordings/"; - String input = "CNYICC_Site_P25P2_151.37_baseband_20200221_110306.wav"; - String output = "P25P2_test"; - - ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 686, 677); //CNYICC - DecodeConfigP25Phase2 decodeConfigP25Phase2 = new DecodeConfigP25Phase2(); - decodeConfigP25Phase2.setScrambleParameters(scrambleParameters); - - P25P2DecoderHDQPSK decoder = new P25P2DecoderHDQPSK(decodeConfigP25Phase2); - decoder.setMessageListener(new MessageErrorListener()); - -// decoder.setMessageListener(new Listener() -// { -// @Override -// public void receive(IMessage iMessage) -// { -// mLog.debug(iMessage.toString()); -// } -// }); - decoder.start(); - -// BinaryRecorder recorder = new BinaryRecorder(Path.of(path), output, Protocol.APCO25_PHASE2); -// decoder.setBufferListener(recorder.getReusableByteBufferListener()); -// recorder.start(); - - File file = new File(path + input); - - boolean running = true; - - try(ComplexWaveSource source = new ComplexWaveSource(file)) - { - decoder.setSampleRate(50000.0); - source.setListener(decoder); - source.start(); - - while(running) - { - source.next(200, true); - } - } - catch(IOException e) - { - mLog.error("Error", e); - running = false; - } - -// recorder.stop(); - } - @Override public Listener getIdentifierUpdateListener() { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java index f5228020..d6f53e92 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.p25.phase2; @@ -25,8 +22,8 @@ import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK { @@ -34,7 +31,7 @@ public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK private Listener mPLLFrequencyListener; private Listener mSamplesPerSymbolListener; private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; + private Listener mFilteredSymbolListener; private Listener mSymbolDecisionDataListener; /** @@ -55,19 +52,27 @@ public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK } /** - * Overrides the filter method so that we can capture the filtered samples for instrumentation + * Primary method for processing incoming complex sample buffers + * @param samples containing channelized complex samples */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) + @Override + public void receive(ComplexSamples samples) { - ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); if(mFilteredSymbolListener != null) { - filtered.incrementUserCount(); - mFilteredSymbolListener.receive(filtered); + mFilteredSymbolListener.receive(samples); } - return filtered; + //Process the buffer for power measurements + mPowerMonitor.process(i, q); + + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -119,7 +124,7 @@ public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK getDemodulator().setPLLFrequencyListener(listener); } - public void setFilteredBufferListener(Listener listener) + public void setFilteredBufferListener(Listener listener) { mFilteredSymbolListener = listener; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java index 8a1dbdf9..fc771dab 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -38,12 +38,12 @@ import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.AudioRecordingManager; import io.github.dsheirer.record.binary.BinaryReader; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; @@ -190,20 +190,15 @@ public class P25P2MessageFramer implements Listener * * @param buffer to process into a stream of dibits for processing. */ - public void receive(ReusableByteBuffer buffer) + public void receive(ByteBuffer buffer) { - //TODO: set timestamp in super frame detector - setCurrentTime(buffer.getTimestamp()); - - for(byte value : buffer.getBytes()) + for(byte value : buffer.array()) { for(int x = 0; x <= 3; x++) { receive(Dibit.parse(value, x)); } } - - buffer.decrementUserCount(); } public static void main(String[] args) @@ -298,7 +293,7 @@ public class P25P2MessageFramer implements Listener { while(reader.hasNext()) { - ReusableByteBuffer buffer = reader.next(); + ByteBuffer buffer = reader.next(); messageFramer.receive(buffer); } } 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 d9336b5a..6fd0a2bf 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 @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,7 +14,7 @@ * * 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.passport; @@ -26,10 +25,9 @@ import io.github.dsheirer.module.decode.Decoder; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; -public class PassportDecoder extends Decoder implements IReusableBufferListener, Listener +public class PassportDecoder extends Decoder implements IRealBufferListener, Listener { public static final int PASSPORT_MESSAGE_LENGTH = 68; @@ -55,15 +53,15 @@ public class PassportDecoder extends Decoder implements IReusableBufferListener, } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { return mLTRDecoder; } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - mLTRDecoder.receive(reusableFloatBuffer); + mLTRDecoder.receive(realBuffer); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java index a9cfd979..0c1d10ee 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,13 +14,12 @@ * * 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.tait; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -48,9 +46,9 @@ public class Tait1200DecoderInstrumented extends Tait1200Decoder implements IIns } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } 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 560a9c66..cd400b5c 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 @@ -1,36 +1,36 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.module.demodulate.am; 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.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.fir.real.IRealFilter; import io.github.dsheirer.dsp.filter.resample.RealResampler; import io.github.dsheirer.dsp.gain.AutomaticGainControl; +import io.github.dsheirer.dsp.window.WindowType; 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.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; +import io.github.dsheirer.sample.real.IRealBufferProvider; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; @@ -39,23 +39,24 @@ import org.slf4j.LoggerFactory; /** * 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 +public class AMDemodulatorModule extends Module + implements ISourceEventListener, IComplexSamplesListener, IRealBufferProvider, Listener { private final static Logger mLog = LoggerFactory.getLogger(AMDemodulatorModule.class); - private static float[] mLowPassFilterTaps; - private ComplexFIRFilter2 mIQFilter; + private IRealFilter mIBasebandFilter; + private IRealFilter mQBasebandFilter; + private AMDemodulator mDemodulator; - private RealFIRFilter2 mLowPassFilter; + private IRealFilter mLowPassFilter; private AutomaticGainControl mAGC = new AutomaticGainControl(); private double mChannelBandwidth; private double mOutputSampleRate; private RealResampler mResampler; - private Listener mResampledReusableBufferListener; + private Listener mResampledReusableBufferListener; private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor(); /** @@ -71,101 +72,81 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener, mDemodulator = new AMDemodulator(500.0f); - if(mLowPassFilterTaps == null) + float[] lowPassCoefficients = 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(); + + try { - 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(); - - 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); - } + lowPassCoefficients = 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"); } - mLowPassFilter = new RealFIRFilter2(mLowPassFilterTaps); + if(lowPassCoefficients == null) + { + lowPassCoefficients = FilterFactory.getLowPass(mOutputSampleRate, 3000, 3500, 60, WindowType.HAMMING, true); + } + + mLowPassFilter = FilterFactory.getRealFilter(lowPassCoefficients); } - @Override - public void receive(ReusableComplexBuffer basebandBuffer) + @Override public void receive(ComplexSamples samples) { - ReusableComplexBuffer filteredBuffer = mIQFilter.filter(basebandBuffer); - ReusableFloatBuffer demodulated = mDemodulator.demodulate(filteredBuffer); + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); + float[] demodulated = mDemodulator.demodulate(i, q); mResampler.resample(demodulated); } /** * Processes resampled audio buffers produced by the resampler. + * * @param resampledAudio buffer to process. */ - private void processResampledAudio(ReusableFloatBuffer resampledAudio) + private void processResampledAudio(float[] resampledAudio) { if(mResampledReusableBufferListener != null) { - ReusableFloatBuffer filteredAudio = mLowPassFilter.filter(resampledAudio); - ReusableFloatBuffer gainApplied = mAGC.process(filteredAudio); - mResampledReusableBufferListener.receive(gainApplied); - } - else - { - resampledAudio.decrementUserCount(); + float[] filtered = mLowPassFilter.filter(resampledAudio); + float[] amplified = mAGC.process(filtered); + mResampledReusableBufferListener.receive(amplified); } } - @Override - public Listener getReusableComplexBufferListener() + @Override public Listener getComplexSamplesListener() { return this; } - @Override - public void reset() + @Override public void reset() { } - @Override - public void start() + @Override public void start() { } - @Override - public void stop() + @Override public void stop() { } - @Override - public void setBufferListener(Listener listener) + @Override public void setBufferListener(Listener listener) { mResampledReusableBufferListener = listener; } - @Override - public void removeBufferListener() + @Override public void removeBufferListener() { mResampledReusableBufferListener = null; } - @Override - public Listener getSourceEventListener() + @Override public Listener getSourceEventListener() { return mSourceEventProcessor; } @@ -175,73 +156,58 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener, */ public class SourceEventProcessor implements Listener { - @Override - public void receive(SourceEvent sourceEvent) + @Override public void receive(SourceEvent sourceEvent) { if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE) { - if(mIQFilter != null) + if(mIBasebandFilter != null || mQBasebandFilter != null) { - mIQFilter.dispose(); - mIQFilter = null; + mIBasebandFilter = null; + mQBasebandFilter = null; } double sampleRate = sourceEvent.getValue().doubleValue(); 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")); + 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; + float[] coefficients = 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(); + 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); + coefficients = 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"); + mLog.error("Couldn't design AM demodulator remez filter for sample rate [" + sampleRate + + "] pass frequency [" + passBandStop + "] and stop frequency [" + stopBandStart + + "] - using sinc filter"); } - if(filterTaps == null) + if(coefficients == null) { - filterTaps = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, - Window.WindowType.HAMMING, true); + coefficients = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, + WindowType.HAMMING, true); } - mIQFilter = new ComplexFIRFilter2(filterTaps); - - mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000); - - mResampler.setListener(new Listener() - { - @Override - public void receive(ReusableFloatBuffer resampledAudioBuffer) - { - processResampledAudio(resampledAudioBuffer); - } - }); + mIBasebandFilter = FilterFactory.getRealFilter(coefficients); + mQBasebandFilter = FilterFactory.getRealFilter(coefficients); + mResampler = new RealResampler(sampleRate, mOutputSampleRate, 8192, 512); + mResampler.setListener(resampledBuffer -> processResampledAudio(resampledBuffer)); } } } 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 a51e44ee..0ba78a80 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 @@ -1,34 +1,36 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.module.demodulate.fm; 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.ComplexFIRFilter2; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; import io.github.dsheirer.dsp.filter.resample.RealResampler; -import io.github.dsheirer.dsp.fm.FMDemodulator; +import io.github.dsheirer.dsp.fm.ScalarFMDemodulator; import io.github.dsheirer.dsp.squelch.PowerMonitor; +import io.github.dsheirer.dsp.window.WindowType; 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.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; +import io.github.dsheirer.sample.real.IRealBufferProvider; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; @@ -41,16 +43,17 @@ import org.slf4j.LoggerFactory; * Note: no filtering is applied to the demodulated audio. */ public class FMDemodulatorModule extends Module implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableBufferProvider + IComplexSamplesListener, Listener, IRealBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatorModule.class); - private ComplexFIRFilter2 mIQFilter; - private FMDemodulator mDemodulator = new FMDemodulator(); + private IRealFilter mIBasebandFilter; + private IRealFilter mQBasebandFilter; + private ScalarFMDemodulator mDemodulator = new ScalarFMDemodulator(); private PowerMonitor mPowerMonitor = new PowerMonitor(); private RealResampler mResampler; private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor(); - private Listener mResampledReusableBufferListener; + private Listener mResampledBufferListener; private double mChannelBandwidth; private double mOutputSampleRate; @@ -68,7 +71,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, } @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return this; } @@ -82,12 +85,6 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, @Override public void dispose() { - if(mIQFilter != null) - { - mIQFilter.dispose(); - mIQFilter = null; - } - mDemodulator.dispose(); mDemodulator = null; } @@ -109,38 +106,35 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, } @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { - mResampledReusableBufferListener = listener; + mResampledBufferListener = listener; } @Override public void removeBufferListener() { - mResampledReusableBufferListener = null; + mResampledBufferListener = null; } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - if(mIQFilter == null) + if(mIBasebandFilter == null || mQBasebandFilter == null) { - 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); - mPowerMonitor.process(basebandFilteredBuffer); - ReusableFloatBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer); + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); + + mPowerMonitor.process(i, q); + float[] demodulated = mDemodulator.demodulate(i, q); if(mResampler != null) { - mResampler.resample(demodulatedBuffer); - } - else - { - demodulatedBuffer.decrementUserCount(); + mResampler.resample(demodulated); } } @@ -166,12 +160,6 @@ 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))) @@ -187,7 +175,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, int passBandStop = (int)cutoff - 500; int stopBandStart = (int)cutoff + 500; - float[] filterTaps = null; + float[] coefficients = null; FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() .sampleRate(sampleRate) @@ -203,7 +191,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, try { - filterTaps = FilterFactory.getTaps(specification); + coefficients = FilterFactory.getTaps(specification); } catch(FilterDesignException fde) { @@ -212,29 +200,20 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, "] - using sinc filter"); } - if(filterTaps == null) + if(coefficients == null) { - filterTaps = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, - Window.WindowType.HAMMING, true); + coefficients = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, + WindowType.HAMMING, true); } - mIQFilter = new ComplexFIRFilter2(filterTaps); + mIBasebandFilter = FilterFactory.getRealFilter(coefficients); + mQBasebandFilter = FilterFactory.getRealFilter(coefficients); + mResampler = new RealResampler(sampleRate, mOutputSampleRate, 8192, 512); - mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000); - - mResampler.setListener(new Listener() - { - @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + mResampler.setListener(resampledBuffer -> { + if(mResampledBufferListener != null) { - if(mResampledReusableBufferListener != null) - { - mResampledReusableBufferListener.receive(reusableFloatBuffer); - } - else - { - reusableFloatBuffer.decrementUserCount(); - } + mResampledBufferListener.receive(resampledBuffer); } }); } diff --git a/src/main/java/io/github/dsheirer/preference/PreferenceType.java b/src/main/java/io/github/dsheirer/preference/PreferenceType.java index 0e00518f..d9309dea 100644 --- a/src/main/java/io/github/dsheirer/preference/PreferenceType.java +++ b/src/main/java/io/github/dsheirer/preference/PreferenceType.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -24,6 +24,7 @@ package io.github.dsheirer.preference; */ public enum PreferenceType { + CALIBRATION, DECODE_EVENT, DIRECTORY, DUPLICATE_CALL_DETECTION, diff --git a/src/main/java/io/github/dsheirer/preference/UserPreferences.java b/src/main/java/io/github/dsheirer/preference/UserPreferences.java index b301c98e..bc247a07 100644 --- a/src/main/java/io/github/dsheirer/preference/UserPreferences.java +++ b/src/main/java/io/github/dsheirer/preference/UserPreferences.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -20,6 +20,7 @@ package io.github.dsheirer.preference; import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.preference.calibration.VectorCalibrationPreference; import io.github.dsheirer.preference.decoder.JmbeLibraryPreference; import io.github.dsheirer.preference.directory.DirectoryPreference; import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference; @@ -53,6 +54,7 @@ import io.github.dsheirer.sample.Listener; */ public class UserPreferences implements Listener { + private VectorCalibrationPreference mVectorCalibrationPreference; private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference; private DecodeEventPreference mDecodeEventPreference; private DirectoryPreference mDirectoryPreference; @@ -76,6 +78,11 @@ public class UserPreferences implements Listener loadPreferenceTypes(); } + public VectorCalibrationPreference getVectorCalibrationPreference() + { + return mVectorCalibrationPreference; + } + /** * Java FX window management preferences */ @@ -186,6 +193,7 @@ public class UserPreferences implements Listener */ private void loadPreferenceTypes() { + mVectorCalibrationPreference = new VectorCalibrationPreference(this::receive); mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this::receive); mDecodeEventPreference = new DecodeEventPreference(this::receive); mDirectoryPreference = new DirectoryPreference(this::receive); diff --git a/src/main/java/io/github/dsheirer/preference/calibration/VectorCalibrationPreference.java b/src/main/java/io/github/dsheirer/preference/calibration/VectorCalibrationPreference.java new file mode 100644 index 00000000..83a0ce38 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/calibration/VectorCalibrationPreference.java @@ -0,0 +1,103 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.preference.calibration; + +import io.github.dsheirer.preference.Preference; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.prefs.Preferences; + +/** + * User preferences for CPU calibration for scalar vs vector SIMD implementations + */ +public class VectorCalibrationPreference extends Preference +{ + private static final String PREFERENCE_KEY_HIDE_CALIBRATION_DIALOG = "hide.calibration.dialog"; + private static final String PREFERENCE_KEY_VECTOR_ENABLED = "vector.enabled"; + + private final static Logger mLog = LoggerFactory.getLogger(VectorCalibrationPreference.class); + private Preferences mPreferences = Preferences.userNodeForPackage(VectorCalibrationPreference.class); + private Boolean mVectorEnabled; + private Boolean mHideCalibrationDialog; + + /** + * Constructs an instance + * @param updateListener to receive notifications that a preference has been updated + */ + public VectorCalibrationPreference(Listener updateListener) + { + super(updateListener); + } + + @Override + public PreferenceType getPreferenceType() + { + return PreferenceType.CALIBRATION; + } + + /** + * Indicates if the calibration dialog should be shown. + */ + public boolean isHideCalibrationDialog() + { + if(mHideCalibrationDialog == null) + { + mHideCalibrationDialog = mPreferences.getBoolean(PREFERENCE_KEY_HIDE_CALIBRATION_DIALOG, false); + } + + return mHideCalibrationDialog; + } + + /** + * Sets the show calibration dialog + */ + public void setHideCalibrationDialog(boolean hide) + { + mPreferences.putBoolean(PREFERENCE_KEY_HIDE_CALIBRATION_DIALOG, hide); + mHideCalibrationDialog = hide; + notifyPreferenceUpdated(); + } + + /** + * Indicates if Vector (SIMD) operations are enabled. + */ + public boolean isVectorEnabled() + { + if(mVectorEnabled == null) + { + mVectorEnabled = mPreferences.getBoolean(PREFERENCE_KEY_VECTOR_ENABLED, true); + } + + return mVectorEnabled; + } + + /** + * Enables or disables Vector (SIMD) operations + */ + public void setVectorEnabled(boolean enabled) + { + mPreferences.putBoolean(PREFERENCE_KEY_VECTOR_ENABLED, enabled); + mVectorEnabled = enabled; + notifyPreferenceUpdated(); + } +} diff --git a/src/main/java/io/github/dsheirer/record/RecorderFactory.java b/src/main/java/io/github/dsheirer/record/RecorderFactory.java index 9ce170d0..42a69a3b 100644 --- a/src/main/java/io/github/dsheirer/record/RecorderFactory.java +++ b/src/main/java/io/github/dsheirer/record/RecorderFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -26,7 +26,8 @@ import io.github.dsheirer.module.decode.p25.audio.P25P1CallSequenceRecorder; import io.github.dsheirer.module.decode.p25.audio.P25P2CallSequenceRecorder; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.binary.BinaryRecorder; -import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder; +import io.github.dsheirer.record.wave.ComplexSamplesWaveRecorder; +import io.github.dsheirer.record.wave.NativeBufferWaveRecorder; import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; import io.github.dsheirer.util.StringUtils; @@ -158,12 +159,24 @@ public class RecorderFactory /** * Constructs a baseband recorder for use in a processing chain. */ - public static ComplexBufferWaveRecorder getBasebandRecorder(String channelName, UserPreferences userPreferences) + public static ComplexSamplesWaveRecorder getBasebandRecorder(String channelName, UserPreferences userPreferences) { StringBuilder sb = new StringBuilder(); sb.append(getRecordingBasePath(userPreferences)); sb.append(File.separator).append(StringUtils.replaceIllegalCharacters(channelName)).append("_baseband"); - return new ComplexBufferWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString()); + return new ComplexSamplesWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString()); + } + + /** + * Constructs a baseband recorder for use in a processing chain. + */ + public static NativeBufferWaveRecorder getTunerRecorder(String channelName, UserPreferences userPreferences) + { + StringBuilder sb = new StringBuilder(); + sb.append(getRecordingBasePath(userPreferences)); + sb.append(File.separator).append(StringUtils.replaceIllegalCharacters(channelName)).append("_baseband"); + + return new NativeBufferWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString()); } } diff --git a/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java b/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java index e4454b9c..0168ba2f 100644 --- a/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java +++ b/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,12 +14,10 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.record.binary; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,21 +25,18 @@ import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.file.Path; -import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Iterator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public class BinaryReader implements Iterator, AutoCloseable +public class BinaryReader implements Iterator, AutoCloseable { private final static Logger mLog = LoggerFactory.getLogger(BinaryReader.class); - private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("Binary Reader"); private int mBufferSize; private InputStream mInputStream; private Path mPath; - private ReusableByteBuffer mNextBuffer; - private TimestampTracker mTimestampTracker = new TimestampTracker(); + private ByteBuffer mNextBuffer; /** * Constructs a binary reader @@ -55,7 +49,6 @@ public class BinaryReader implements Iterator, AutoCloseable mBufferSize = bufferSize; mPath = path; mInputStream = new BufferedInputStream(new FileInputStream(path.toFile())); - mTimestampTracker.processFileName(path.toString()); getNext(); } @@ -84,9 +77,9 @@ public class BinaryReader implements Iterator, AutoCloseable * @return */ @Override - public ReusableByteBuffer next() + public ByteBuffer next() { - ReusableByteBuffer current = mNextBuffer; + ByteBuffer current = mNextBuffer; getNext(); return current; } @@ -98,118 +91,27 @@ public class BinaryReader implements Iterator, AutoCloseable { try { - mNextBuffer = mBufferQueue.getBuffer(mBufferSize); + byte[] readBytes = new byte[mBufferSize]; + int bytesRead = mInputStream.read(readBytes); - int bytesRead = mInputStream.read(mNextBuffer.getBytes(), 0, mBufferSize); - - mNextBuffer.setTimestamp(mTimestampTracker.getTimestamp()); - mTimestampTracker.updateBytesProcessed(bytesRead); + if(bytesRead < readBytes.length) + { + readBytes = Arrays.copyOf(readBytes, bytesRead); + } if(bytesRead > 0) { - if(bytesRead < mBufferSize) - { - ReusableByteBuffer partialBuffer = mBufferQueue.getBuffer(bytesRead); - System.arraycopy(mNextBuffer.getBytes(), 0, partialBuffer.getBytes(), 0, bytesRead); - partialBuffer.setTimestamp(mNextBuffer.getTimestamp()); - mNextBuffer.decrementUserCount(); - mNextBuffer = partialBuffer; - } + mNextBuffer = ByteBuffer.wrap(readBytes); } else { - mNextBuffer.decrementUserCount(); mNextBuffer = null; } } catch(IOException e) { mLog.error("Error reading binary file [" + mPath.toString() + "]", e); - mNextBuffer.decrementUserCount(); mNextBuffer = null; } } - - public class TimestampTracker - { - private final Pattern TIMESTAMP_BITRATE_PATTERN = Pattern.compile(".*(\\d{8}_\\d{6})_(\\d{4,8})BPS_.*.bits"); - private final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMdd_HHmmss"); - private Double mBitRate = null; - private long mTimestamp; - - public TimestampTracker() - { - } - - /** - * Processes the file name to attempt to extract the file starting timestamp and bit rate. - * @param filename - */ - public void processFileName(String filename) - { - try - { - Matcher m = TIMESTAMP_BITRATE_PATTERN.matcher(filename); - - if(m.find()) - { - long timestamp = SDF.parse(m.group(1)).getTime(); - int bitRate = Integer.parseInt(m.group(2)); - - setTimestamp(timestamp); - setBitRate(bitRate); - return; - } - } - catch(Exception e) - { - mLog.error("Couldn't parse timestamp and bit rate from filename [" + filename + "]"); - } - } - - /** - * Sets the expected bit rate (ie bits per second) for the tracker - */ - public void setBitRate(int bitRate) - { - mBitRate = (double)bitRate; - } - - /** - * Sets the timestamp for the tracker, otherwise current system time is assumed. - */ - public void setTimestamp(long timestamp) - { - mTimestamp = timestamp; - } - - /** - * Gets the currently tracked timestamp when bits per second has been previously specified, otherwise - * returns current system time. - * - * @return current tracked timestamp or current system time. - */ - public long getTimestamp() - { - if(mBitRate != null) - { - return mTimestamp; - } - - return System.currentTimeMillis(); - } - - /** - * Updates the timestamp based on the number of bytes processed and the specified bit rate. - * - * @param bytesProcessed thus far - */ - public void updateBytesProcessed(int bytesProcessed) - { - if(mBitRate != null) - { - mTimestamp += (long)(((double)bytesProcessed * 8.0) / mBitRate * 1000.0); - } - } - } } diff --git a/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java b/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java index b433437f..3a1c8489 100644 --- a/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java +++ b/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,16 +14,15 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.record.binary; -import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor; import io.github.dsheirer.module.Module; import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferListener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferListener; +import io.github.dsheirer.util.Dispatcher; import io.github.dsheirer.util.StringUtils; import io.github.dsheirer.util.TimeStamp; import org.slf4j.Logger; @@ -37,7 +35,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.EnumSet; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -48,14 +45,13 @@ import java.util.concurrent.atomic.AtomicBoolean; * The contents of the file are the raw bytes as demodulated by the decoder with * no header or timestamps, other than the timestamp included in the filename. */ -public class BinaryRecorder extends Module implements IReusableByteBufferListener +public class BinaryRecorder extends Module implements IByteBufferListener { private final static Logger mLog = LoggerFactory.getLogger(BinaryRecorder.class); private static final int MAX_RECORDING_BYTE_SIZE = 524288; //500 kB - private ContinuousReusableBufferProcessor mBufferProcessor = - new ContinuousReusableBufferProcessor<>(500, 50); - + private Dispatcher mBufferProcessor = new Dispatcher<>(500, + "sdrtrunk binary recorder", ByteBuffer.allocate(0)); private AtomicBoolean mRunning = new AtomicBoolean(); private Path mBaseRecordingPath; private String mRecordingIdentifier; @@ -135,7 +131,7 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene } @Override - public Listener getReusableByteBufferListener() + public Listener getByteBufferListener() { return mBufferProcessor; } @@ -148,7 +144,7 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene /** * Binary writer implementation for reusable byte buffers delivered from buffer processor */ - public class BinaryWriter implements Listener> + public class BinaryWriter implements Listener { private Path mCurrentPath; private WritableByteChannel mWritableByteChannel; @@ -160,7 +156,6 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene mCurrentPath = path; mWritableByteChannel = Files.newByteChannel(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE)); -// mLog.info("Binary (bitstream) recording started: " + mCurrentPath.toString()); } } @@ -209,42 +204,36 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene /** * Primary receive method for incoming byte buffers - * @param reusableComplexBuffers to record + * @param byteBuffer to record */ @Override - public void receive(List reusableComplexBuffers) + public void receive(ByteBuffer byteBuffer) { - for(ReusableByteBuffer buffer: reusableComplexBuffers) + if(mWritableByteChannel != null) { - if(mWritableByteChannel != null) + try { - try - { - ByteBuffer byteBuffer = ByteBuffer.wrap(buffer.getSamplesCopy()); - mBytesRecordedCounter += mWritableByteChannel.write(byteBuffer); + mBytesRecordedCounter += mWritableByteChannel.write(byteBuffer); - if(mBytesRecordedCounter > MAX_RECORDING_BYTE_SIZE) - { - cycleRecording(); - } - } - catch(IOException ioe) + if(mBytesRecordedCounter > MAX_RECORDING_BYTE_SIZE) { - mLog.error("Error recording demodulated bits to file [" + - (mCurrentPath != null ? mCurrentPath.toString() : "no file") + "] - stopping recorder"); - - try - { - stop(); - } - catch(IOException ioe2) - { - mLog.error("Error stopping recorder after write error", ioe2.getLocalizedMessage()); - } + cycleRecording(); } } + catch(IOException ioe) + { + mLog.error("Error recording demodulated bits to file [" + + (mCurrentPath != null ? mCurrentPath.toString() : "no file") + "] - stopping recorder"); - buffer.decrementUserCount(); + try + { + stop(); + } + catch(IOException ioe2) + { + mLog.error("Error stopping recorder after write error", ioe2.getLocalizedMessage()); + } + } } } } diff --git a/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java b/src/main/java/io/github/dsheirer/record/wave/ComplexSamplesWaveRecorder.java similarity index 61% rename from src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java rename to src/main/java/io/github/dsheirer/record/wave/ComplexSamplesWaveRecorder.java index 1086f48c..39140ded 100644 --- a/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java +++ b/src/main/java/io/github/dsheirer/record/wave/ComplexSamplesWaveRecorder.java @@ -1,28 +1,31 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.record.wave; -import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.Dispatcher; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; import org.slf4j.Logger; @@ -32,28 +35,26 @@ import javax.sound.sampled.AudioFormat; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * WAVE audio recorder module for recording complex (I&Q) samples to a wave file */ -public class ComplexBufferWaveRecorder extends Module implements IReusableComplexBufferListener, - Listener, ISourceEventListener +public class ComplexSamplesWaveRecorder extends Module implements IComplexSamplesListener, + Listener, ISourceEventListener { - private final static Logger mLog = LoggerFactory.getLogger(ComplexBufferWaveRecorder.class); + private final static Logger mLog = LoggerFactory.getLogger(ComplexSamplesWaveRecorder.class); - private ContinuousReusableBufferProcessor mBufferProcessor = - new ContinuousReusableBufferProcessor<>(500, 50); + private Dispatcher mBufferProcessor = new Dispatcher<>(500, + "sdrtrunk complex wave recorder", new ComplexSamples(new float[0], new float[0])); private AtomicBoolean mRunning = new AtomicBoolean(); - private ReusableBufferWaveWriter mWriter; + private BufferWaveWriter mWriter; private String mFilePrefix; private Path mFile; private AudioFormat mAudioFormat; - public ComplexBufferWaveRecorder(float sampleRate, String filePrefix) + public ComplexSamplesWaveRecorder(float sampleRate, String filePrefix) { mFilePrefix = filePrefix; setSampleRate(sampleRate); @@ -91,7 +92,7 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple sb.append(".wav"); mFile = Paths.get(sb.toString()); - mWriter = new ReusableBufferWaveWriter(mAudioFormat, mFile); + mWriter = new BufferWaveWriter(mAudioFormat, mFile); mBufferProcessor.setListener(mWriter); mBufferProcessor.start(); @@ -117,7 +118,7 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple { //Thread this operation so that it doesn't tie up the calling thread. The wave writer //close method will also rename the file and this can sometimes take a few seconds. - ThreadPool.SCHEDULED.schedule(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() @@ -131,20 +132,20 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple mLog.error("Error closing baseband I/Q recorder", ioe); } } - }, 0, TimeUnit.MILLISECONDS); + }); } } } @Override - public void receive(ReusableComplexBuffer buffer) + public void receive(ComplexSamples buffer) { //Queue the buffer with the buffer processor so that recording occurs on the buffer processor thread mBufferProcessor.receive(buffer); } @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return this; } @@ -173,37 +174,32 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple } /** - * Wave writer implementation for reusable complex buffers delivered from buffer processor + * Wave writer implementation for complex buffers delivered from buffer processor */ - public class ReusableBufferWaveWriter extends WaveWriter implements Listener> + public class BufferWaveWriter extends WaveWriter implements Listener { - public ReusableBufferWaveWriter(AudioFormat format, Path file) throws IOException + public BufferWaveWriter(AudioFormat format, Path file) throws IOException { super(format, file); } @Override - public void receive(List reusableComplexBuffers) + public void receive(ComplexSamples complexSamples) { boolean error = false; - for(ReusableComplexBuffer reusableComplexBuffer: reusableComplexBuffers) + if(!error) { - if(!error) + try { - try - { - mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(reusableComplexBuffer)); - } - catch(IOException ioe) - { - mLog.error("IOException while writing I/Q buffers to wave recorder - stopping recorder", ioe); - error = true; - stop(); - } + mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(complexSamples)); + } + catch(IOException ioe) + { + mLog.error("IOException while writing I/Q buffers to wave recorder - stopping recorder", ioe); + error = true; + stop(); } - - reusableComplexBuffer.decrementUserCount(); } } } diff --git a/src/main/java/io/github/dsheirer/record/wave/NativeBufferWaveRecorder.java b/src/main/java/io/github/dsheirer/record/wave/NativeBufferWaveRecorder.java new file mode 100644 index 00000000..69484f02 --- /dev/null +++ b/src/main/java/io/github/dsheirer/record/wave/NativeBufferWaveRecorder.java @@ -0,0 +1,208 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.buffer.INativeBuffer; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; +import io.github.dsheirer.module.Module; +import io.github.dsheirer.sample.ConversionUtils; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; +import io.github.dsheirer.source.ISourceEventListener; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.Dispatcher; +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.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * WAVE audio recorder module for recording complex (I&Q) samples to a wave file + */ +public class NativeBufferWaveRecorder extends Module implements Listener, ISourceEventListener +{ + private final static Logger mLog = LoggerFactory.getLogger(ComplexSamplesWaveRecorder.class); + + private Dispatcher mBufferProcessor = new Dispatcher<>(500, + "sdrtrunk-native buffer wave recorder", new NativeBufferPoisonPill()); + + private AtomicBoolean mRunning = new AtomicBoolean(); + private INativeBufferWaveWriter mWriter; + private String mFilePrefix; + private Path mFile; + private AudioFormat mAudioFormat; + + public NativeBufferWaveRecorder(float sampleRate, String filePrefix) + { + mFilePrefix = filePrefix; + setSampleRate(sampleRate); + } + + public void setSampleRate(float sampleRate) + { + if(mAudioFormat == null || mAudioFormat.getSampleRate() != sampleRate) + { + mAudioFormat = new AudioFormat(sampleRate, 16, 2, true, false); + + if(mRunning.get()) + { + stop(); + start(); + } + } + } + + public Path getFile() + { + return mFile; + } + + public void start() + { + if(mRunning.compareAndSet(false, true)) + { + try + { + StringBuilder sb = new StringBuilder(); + sb.append(mFilePrefix); + sb.append("_"); + sb.append(TimeStamp.getTimeStamp("_")); + sb.append(".wav"); + mFile = Paths.get(sb.toString()); + + mWriter = new INativeBufferWaveWriter(mAudioFormat, mFile); + + mBufferProcessor.setListener(mWriter); + mBufferProcessor.start(); + } + catch(IOException io) + { + mLog.error("Error starting complex baseband recorder", io); + } + } + } + + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + if(mBufferProcessor != null) + { + mBufferProcessor.stop(); + mBufferProcessor.setListener(null); + } + + if(mWriter != null) + { + //Thread this operation so that it doesn't tie up the calling thread. The wave writer + //close method will also rename the file and this can sometimes take a few seconds. + ThreadPool.CACHED.submit(new Runnable() + { + @Override + public void run() + { + try + { + mWriter.close(); + } + catch(IOException ioe) + { + mLog.error("Error closing baseband I/Q recorder", ioe); + } + } + }); + } + } + } + + @Override + public void receive(INativeBuffer nativeBuffer) + { + //Queue the buffer with the buffer processor so that recording occurs on the buffer processor thread + mBufferProcessor.receive(nativeBuffer); + } + + public Listener getReusableComplexBufferListener() + { + return this; + } + + @Override + public void reset() + { + } + + @Override + public Listener getSourceEventListener() + { + return new Listener() + { + @Override + public void receive(SourceEvent sourceEvent) + { + switch(sourceEvent.getEvent()) + { + case NOTIFICATION_SAMPLE_RATE_CHANGE: + setSampleRate(sourceEvent.getValue().floatValue()); + break; + } + } + }; + } + + /** + * Wave writer implementation for complex samples delivered from buffer processor + */ + public class INativeBufferWaveWriter extends WaveWriter implements Listener + { + public INativeBufferWaveWriter(AudioFormat format, Path file) throws IOException + { + super(format, file); + } + + @Override + public void receive(INativeBuffer nativeBuffer) + { + boolean error = false; + + Iterator iterator = nativeBuffer.iteratorInterleaved(); + + while(iterator.hasNext() & !error) + { + try + { + mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(iterator.next())); + } + catch(IOException ioe) + { + mLog.error("IOException while writing I/Q buffers to wave recorder - stopping recorder", ioe); + error = true; + stop(); + } + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/sample/ConversionUtils.java b/src/main/java/io/github/dsheirer/sample/ConversionUtils.java index 5a48ed61..cc2b91b5 100644 --- a/src/main/java/io/github/dsheirer/sample/ConversionUtils.java +++ b/src/main/java/io/github/dsheirer/sample/ConversionUtils.java @@ -1,6 +1,26 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -52,12 +72,32 @@ public class ConversionUtils return converted; } + /** + * Converts the float samples into a little-endian 16-bit sample byte buffer. + * + * @param samples - float array of sample data + * @return - little-endian 16-bit sample byte buffer + */ + public static ByteBuffer convertToSigned16BitSamples(ComplexSamples samples) + { + ByteBuffer converted = ByteBuffer.allocate(samples.i().length * 4); + converted.order(ByteOrder.LITTLE_ENDIAN); + + for(int x = 0; x < samples.i().length; x++) + { + converted.putShort((short)(samples.i()[x] * Short.MAX_VALUE)); + converted.putShort((short)(samples.q()[x] * Short.MAX_VALUE)); + } + + return converted; + } + /** * Converts the float samples in a complex buffer to a little endian 16-bit * buffer */ - public static ByteBuffer convertToSigned16BitSamples(ReusableFloatBuffer buffer) + public static ByteBuffer convertToSigned16BitSamples(InterleavedComplexSamples complexSamples) { - return convertToSigned16BitSamples(buffer.getSamples()); + return convertToSigned16BitSamples(complexSamples.samples()); } } diff --git a/src/main/java/io/github/dsheirer/sample/Provider.java b/src/main/java/io/github/dsheirer/sample/Provider.java index a81136cc..583d7a5c 100644 --- a/src/main/java/io/github/dsheirer/sample/Provider.java +++ b/src/main/java/io/github/dsheirer/sample/Provider.java @@ -1,17 +1,17 @@ /******************************************************************************* * 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 ******************************************************************************/ @@ -19,7 +19,5 @@ package io.github.dsheirer.sample; public interface Provider { - public void setListener( Listener listener ); - - public void removeListener( Listener listener ); + void setListener(Listener listener); } diff --git a/src/main/java/io/github/dsheirer/sample/SampleUtils.java b/src/main/java/io/github/dsheirer/sample/SampleUtils.java new file mode 100644 index 00000000..255a26bc --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/SampleUtils.java @@ -0,0 +1,145 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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; + +import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; +import org.apache.commons.math3.util.FastMath; + +import java.util.Random; + +public class SampleUtils +{ + public static final double TWO_PI = Math.PI * 2.0; + + /** + * Converts from an interleaved complex sample array to a sample record with the + * I and Q in separate arrays. + * @param samples that are interleaved complex samples + * @return deinterleaved complex samples instance + */ + public static ComplexSamples deinterleave(float[] samples) + { + float[] i = new float[samples.length / 2]; + float[] q = new float[samples.length / 2]; + + int offset; + + for(int x = 0; x < i.length; x++) + { + offset = 2 * x; + i[x] = samples[offset]; + q[x] = samples[offset + 1]; + } + + return new ComplexSamples(i, q); + } + + /** + * Converts a complex samples buffer to an array of interleaved (I, Q, etc.) samples + * @param complexSamples to interleave + * @return interleaved samples array + */ + public static float[] interleave(ComplexSamples complexSamples) + { + float[] i = complexSamples.i(); + float[] q = complexSamples.q();; + + float[] complex = new float[i.length + q.length]; + + for(int x = 0; x < i.length; x++) + { + complex[2 * x] = i[x]; + complex[2 * x + 1] = q[x]; + } + + return complex; + } + + /** + * Generates a buffer of complex samples where each sample's vector length is unity (1.0) + * @param count of buffer samples + * @return complex buffer + */ + public static ComplexSamples generateComplex(int count) + { + Random random = new Random(); + + float[] i = new float[count]; + float[] q = new float[count]; + + for(int x = 0; x < i.length; x++) + { + //Random angle in radians in range of -PI to + PI + double angle = random.nextDouble() * TWO_PI - Math.PI; + + i[x] = (float)Math.cos(angle); + q[x] = (float)Math.sin(angle); + } + + return new ComplexSamples(i, q); + } + + /** + * Generates random complex samples with vector length in range 0.5 to 1.0 (unity). + * @param count of samples + * @return complex sample buffer + */ + public static ComplexSamples generateComplexRandomVectorLength(int count) + { + Random random = new Random(); + ComplexSamples unitySamples = generateComplex(count); + float[] i = unitySamples.i(); + float[] q = unitySamples.q(); + + float gain; + + //Apply random gain reduction to samples in range 0.5 to 1.0 gain + for(int x = 0; x < i.length; x++) + { + gain = 0.5f + random.nextFloat() / 2.0f; + i[x] *= gain; + q[x] *= gain; + } + + return new ComplexSamples(i, q); + } + + public static Complex multiply(Complex a, Complex b) + { + return new Complex((a.inphase() * b.inphase()) - (a.quadrature() * b.quadrature()), + ((a.quadrature() * b.inphase()) + (a.inphase() * b.quadrature()))); + } + + public static Complex minus(Complex a, Complex b) + { + return new Complex(a.inphase() - b.inphase(), a.quadrature() - b.quadrature()); + } + + public static double magnitude(Complex sample) + { + return FastMath.sqrt(magnitudeSquared(sample)); + } + + public static int magnitudeSquared(Complex sample) + { + return (int)((sample.inphase() * sample.inphase()) + (sample.quadrature() * sample.quadrature())); + } +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java deleted file mode 100644 index 9bc4bfe6..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java +++ /dev/null @@ -1,23 +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.sample.adapter; - -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -public abstract class AbstractSampleAdapter -{ - public abstract T convert(byte[] sampleBytes); -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java deleted file mode 100644 index 21846439..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java +++ /dev/null @@ -1,39 +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.sample.adapter; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; - -public abstract class ComplexSampleAdapter extends AbstractSampleAdapter -{ - private ReusableComplexBufferQueue mReusableBufferQueue; - - /** - * Constructs a real sample adapter - * - * @param debugName to use for debug logging - */ - public ComplexSampleAdapter(String debugName) - { - mReusableBufferQueue = new ReusableComplexBufferQueue(debugName); - } - - protected ReusableComplexBuffer getBuffer(int size) - { - return mReusableBufferQueue.getBuffer(size); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java index 2b39e9c2..0efef045 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java @@ -1,21 +1,25 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +29,7 @@ import java.nio.ByteOrder; /** * Converts 16-bit/2-byte little endian byte data into a reusable buffer of complex float sample data */ -public class ComplexShortAdapter extends ComplexSampleAdapter +public class ComplexShortAdapter implements ISampleAdapter { private final static Logger mLog = LoggerFactory.getLogger(ComplexShortAdapter.class); private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; @@ -33,19 +37,15 @@ public class ComplexShortAdapter extends ComplexSampleAdapter /** * Constructs a real sample adapter - * - * @param debugName to use for debug logging */ - public ComplexShortAdapter(String debugName) + public ComplexShortAdapter() { - super(debugName); } @Override - public ReusableComplexBuffer convert(byte[] samples) + public INativeBuffer convert(byte[] samples) { - ReusableComplexBuffer reusableBuffer = getBuffer(samples.length / 2); - float[] convertedSamples = reusableBuffer.getSamples(); + float[] convertedSamples = new float[samples.length / 2]; int pointer = 0; @@ -60,7 +60,9 @@ public class ComplexShortAdapter extends ComplexSampleAdapter pointer++; } - return reusableBuffer; + long now = System.currentTimeMillis(); + + return new FloatNativeBuffer(convertedSamples, now); } /** diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java new file mode 100644 index 00000000..aa1692f7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java @@ -0,0 +1,24 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.adapter; + +public interface ISampleAdapter +{ + T convert(byte[] sampleBytes); +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java index fd1f5180..74ddb4b1 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java @@ -1,21 +1,23 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import io.github.dsheirer.source.mixer.MixerChannel; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; @@ -34,10 +36,8 @@ public class RealChannelShortAdapter extends RealSampleAdapter private MixerChannel mMixerChannel; private ByteBuffer mByteBuffer; - public RealChannelShortAdapter(MixerChannel channel, String debugName) + public RealChannelShortAdapter(MixerChannel channel) { - super(debugName); - /* Only use with LEFT/RIGHT channels */ Validate.isTrue(channel != MixerChannel.MONO); @@ -45,10 +45,9 @@ public class RealChannelShortAdapter extends RealSampleAdapter } @Override - public ReusableFloatBuffer convert(byte[] samples) + public float[] convert(byte[] samples) { - ReusableFloatBuffer reusableFloatBuffer = getBuffer(samples.length / 4); - float[] convertedSamples = reusableFloatBuffer.getSamples(); + float[] convertedSamples = new float[samples.length / 4]; mByteBuffer = ByteBuffer.wrap(samples); @@ -75,7 +74,7 @@ public class RealChannelShortAdapter extends RealSampleAdapter } } - return reusableFloatBuffer; + return convertedSamples; } /** diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java index 70d05bcd..f017e0e3 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java @@ -1,39 +1,29 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -public abstract class RealSampleAdapter extends AbstractSampleAdapter +public abstract class RealSampleAdapter implements ISampleAdapter { - private ReusableBufferQueue mReusableBufferQueue; - /** * Constructs a real sample adapter - * - * @param debugName to use for debug logging */ - public RealSampleAdapter(String debugName) + public RealSampleAdapter() { - mReusableBufferQueue = new ReusableBufferQueue(debugName); - } - - protected ReusableFloatBuffer getBuffer(int size) - { - return mReusableBufferQueue.getBuffer(size); } } diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java index 683c04d1..2c4d8b53 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java @@ -1,22 +1,23 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -30,19 +31,15 @@ public class RealShortAdapter extends RealSampleAdapter /** * Constructs a real sample adapter - * - * @param debugName to use for debug logging */ - public RealShortAdapter(String debugName) + public RealShortAdapter() { - super(debugName); } @Override - public ReusableFloatBuffer convert(byte[] samples) + public float[] convert(byte[] samples) { - ReusableFloatBuffer reusableFloatBuffer = getBuffer(samples.length / 2); - float[] convertedSamples = reusableFloatBuffer.getSamples(); + float[] convertedSamples = new float[samples.length / 2]; int pointer = 0; @@ -57,7 +54,7 @@ public class RealShortAdapter extends RealSampleAdapter pointer++; } - return reusableFloatBuffer; + return convertedSamples; } /** diff --git a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java deleted file mode 100644 index 0a8a5ce5..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java +++ /dev/null @@ -1,177 +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.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.atomic.AtomicInteger; - -public abstract class AbstractReusableBuffer -{ - private final static Logger mLog = LoggerFactory.getLogger(AbstractReusableBuffer.class); - - protected IReusableBufferDisposedListener mBufferDisposedListener; - protected long mTimestamp; - protected AtomicInteger mUserCount = new AtomicInteger(); - private String mDebugName; - - public AbstractReusableBuffer(IReusableBufferDisposedListener bufferDisposedListener, long timestamp) - { - mBufferDisposedListener = bufferDisposedListener; - mTimestamp = timestamp; - } - - public AbstractReusableBuffer(IReusableBufferDisposedListener bufferDisposedListener) - { - mBufferDisposedListener = bufferDisposedListener; - } - - public void dispose() - { - mBufferDisposedListener = null; - } - - public void setDebugName(String debugName) - { - mDebugName = debugName; - } - - public String name() - { - return (mDebugName != null ? mDebugName : "(null)") + " User Count:" + getUserCount(); - } - - public String toString() - { - return name(); - } - - /** - * Reference timestamp for this complex buffer. Timestamp is relative to the first sample in the buffer. - * @return timestamp as milliseconds since epoch - */ - public long getTimestamp() - { - return mTimestamp; - } - - public void setTimestamp(long timestamp) - { - mTimestamp = timestamp; - } - - /** - * Sets the user count to 0 and recycles this buffer - */ - public void clearUserCount() - { - mUserCount.set(0); - recycle(); - } - - /** - * Decrements the user count for this buffer to indicate that the user/receiver of this buffer is finished using - * the buffer. - * - * User count is established prior to sending this buffer to each listener and each listener invokes this method to - * indicate that they have finished processing the buffer so that when the user count reaches zero, this buffer - * can be reused. - * - * This method is thread-safe. - */ - public synchronized void decrementUserCount() - { - if(mUserCount.decrementAndGet() <= 0) - { - recycle(); - } - } - - /** - * Sends this buffer back to the owning buffer queue for reuse - */ - private synchronized void recycle() - { - prepareForRecycle(); - - if(mBufferDisposedListener != null) - { - mBufferDisposedListener.disposed(this); - } - - if(mUserCount.get() < 0) - { - mUserCount.set(0); - throw new IllegalStateException("User count is below zero. This indicates that this buffer's decrement" + - " user count was invoked by more than the expected user count"); - } - } - - /** - * Invoked just prior to notifying the owner that this buffer is ready for prepareForRecycle. This method - * is intended for sub-class implementations to perform any prepareForRecycle cleanup actions. - */ - protected void prepareForRecycle() - { - //No-op in this abstract class - } - - /** - * Increments the user count to indicate that this buffer will be sent to another user. - * - * Users/receivers of this buffer that wish to forward the buffer to additional users should increment the expected - * user count prior to invoking the decrementUserCount() to indicate that the original receiver of the buffer is - * finished to avoid the user count reaching zero and the buffer being flagged as ready for reuse. - * - * This method is thread-safe. - */ - public void incrementUserCount() - { - mUserCount.incrementAndGet(); - } - - /** - * Increments the user count by the specified count to indicate that this buffer will be sent to additional users. - * - * Users/receivers of this buffer that wish to forward the buffer to additional users should increment the expected - * user count prior to invoking the decrementUserCount() to indicate that the original receiver of the buffer is - * finished to avoid the user count reaching zero and the buffer being flagged as ready for reuse. - * - * This method is thread-safe. - * - * @param additionalUserCount to add to the current user count - */ - public void incrementUserCount(int additionalUserCount) - { - if(additionalUserCount >= 0) - { - mUserCount.addAndGet(additionalUserCount); - } - else - { - mLog.debug("Request to add negative user count [" + additionalUserCount + "] ignored"); - } - } - - /** - * Number of users currently registered for this buffer - */ - public int getUserCount() - { - return mUserCount.get(); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java deleted file mode 100644 index dd07dbab..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Queue; -import java.util.concurrent.LinkedTransferQueue; - -public abstract class AbstractReusableBufferQueue - implements IReusableBufferDisposedListener -{ - private final static Logger mLog = LoggerFactory.getLogger(AbstractReusableBufferQueue.class); - - private Queue mReusableBufferQueue = new LinkedTransferQueue<>(); - private int mBufferCount = 0; - private String mDebugName; - - //Set to true to log when reusable buffers are created to monitor proper user count management - protected boolean mBufferCreationLoggingEnabled = false; - - /** - * Base queue for managing reusable buffers. - * - * @param debugName to associate a debug string with this buffer instance - */ - public AbstractReusableBufferQueue(String debugName) - { - mDebugName = debugName; - } - - /** - * Base queue for managing reusable buffers. - */ - public AbstractReusableBufferQueue() - { - } - - /** - * Turns on/off logging of buffer creation - */ - public void setDebugLogging(boolean enabled) - { - mBufferCreationLoggingEnabled = enabled; - } - - /** - * Disposes of any reclaimed buffers to prepare this queue for disposal. - */ - public void dispose() - { - T buffer = mReusableBufferQueue.poll(); - - while(buffer != null) - { - buffer.dispose(); - buffer = mReusableBufferQueue.poll(); - } - - mBufferCount = 0; - } - - /** - * Implements buffer disposed listener interface. Disposed (ie user count = 0) buffers will automatically callback - * to this method to indicate when they are disposed. - * - * @param reusableBuffer that has been disposed - */ - @Override - public void disposed(T reusableBuffer) - { - mReusableBufferQueue.offer(reusableBuffer); - } - - /** - * Get a recycled buffer from the queue - */ - protected T getRecycledBuffer() - { - return mReusableBufferQueue.poll(); - } - - /** - * Increments the count of buffers managed by this queue. Note: this method is NOT thread safe - */ - protected void incrementBufferCount() - { - mBufferCount++; - - if(mBufferCreationLoggingEnabled) - { - mLog.debug("Buffer Created - count:" + mBufferCount + - " debug:" + (mDebugName != null ? mDebugName : "null") + " class:" + this.getClass()); - } - } - - /** - * Current count of buffers created by this queue - */ - protected int getBufferCount() - { - return mBufferCount; - } - - /** - * Debug name for this queue instance - */ - protected String getDebugName() - { - return mDebugName; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferListener.java new file mode 100644 index 00000000..3945b42a --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferListener.java @@ -0,0 +1,28 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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; + +import java.nio.ByteBuffer; + +public interface IByteBufferListener +{ + Listener getByteBufferListener(); +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferProvider.java b/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferProvider.java new file mode 100644 index 00000000..7e28d609 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferProvider.java @@ -0,0 +1,41 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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; + +import java.nio.ByteBuffer; + +public interface IByteBufferProvider +{ + /** + * Adds the listener to receive complex buffer samples + */ + 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(); +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferDisposedListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferDisposedListener.java deleted file mode 100644 index 67cbf293..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferDisposedListener.java +++ /dev/null @@ -1,25 +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.sample.buffer; - -public interface IReusableBufferDisposedListener -{ - /** - * Notifies the listener that the timestamped buffer has been disposed and can now be reused. - * @param t reusableBuffer that has been disposed - */ - void disposed(T t); -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java deleted file mode 100644 index 41795f92..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java +++ /dev/null @@ -1,31 +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.sample.buffer; - -import io.github.dsheirer.sample.Listener; - -public interface IReusableBufferProvider -{ - /** - * Adds the listener to receive complex buffer samples - */ - void setBufferListener(Listener listener); - - /** - * Removes the listener from receiving complex buffer samples - */ - void removeBufferListener(); -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferListener.java deleted file mode 100644 index 2aba9f9a..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferListener.java +++ /dev/null @@ -1,23 +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.sample.buffer; - -import io.github.dsheirer.sample.Listener; - -public interface IReusableByteBufferListener -{ - Listener getReusableByteBufferListener(); -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java deleted file mode 100644 index 80de66d5..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java +++ /dev/null @@ -1,23 +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.sample.buffer; - -import io.github.dsheirer.sample.Listener; - -public interface IReusableComplexBufferListener -{ - 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 deleted file mode 100644 index 14316bb3..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferProvider.java +++ /dev/null @@ -1,36 +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.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/ITimestamped.java b/src/main/java/io/github/dsheirer/sample/buffer/ITimestamped.java new file mode 100644 index 00000000..cff7b6fa --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/buffer/ITimestamped.java @@ -0,0 +1,28 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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; + +/** + * Interface for objects that have timestamps. + */ +public interface ITimestamped +{ + long timestamp(); +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/OverflowableReusableBufferTransferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/OverflowableReusableBufferTransferQueue.java deleted file mode 100644 index 874f1a9b..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/OverflowableReusableBufferTransferQueue.java +++ /dev/null @@ -1,69 +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.sample.buffer; - -import io.github.dsheirer.sample.OverflowableTransferQueue; - -public class OverflowableReusableBufferTransferQueue extends OverflowableTransferQueue -{ - /** - * Concurrent transfer queue that couples a higher-throughput linked transfer queue with an atomic integer for - * monitoring queue size. When the queue size exceeds maximum size (overflow), all inbound elements are ignored - * until the queue size is reduced to or below the reset threshold. - * - * This implementation includes special handling for reusable complex buffers. - * - * @param maximumSize of the queue. Overflow state will occur once queue size exceeds this value. - * @param resetThreshold for resetting overflow state to normal, once queue size is at or below this value. - */ - public OverflowableReusableBufferTransferQueue(int maximumSize, int resetThreshold) - { - super(maximumSize, resetThreshold); - } - - /** - * Overrides the overflow method to decrement the user count on any buffers that are being discarded when the queue - * is in an overflow state. - * - * @param t reusableBuffer that will be discarded - */ - @Override - protected void overflow(T t) - { - t.decrementUserCount(); - } - - /** - * Overrides the buffer clear method to decrement the user count on each buffer that is being cleared from the queue. - */ - @Override - public void clear() - { - synchronized(mQueue) - { - T buffer = mQueue.poll(); - - while(buffer != null) - { - buffer.decrementUserCount(); - buffer = mQueue.poll(); - } - - mCounter.set(0); - mOverflow.set(false); - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferAssembler.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferAssembler.java deleted file mode 100644 index 59fda806..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferAssembler.java +++ /dev/null @@ -1,80 +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.sample.buffer; - -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.real.RealSampleListener; - -/** - * Assembles single float samples into a ReusableFloatBuffer containing an array of floats - */ -public class ReusableBufferAssembler implements RealSampleListener -{ - private int mBufferSize; - private int mBufferPointer; - private ReusableFloatBuffer mCurrentBuffer; - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Real"); - - private Listener mListener; - - public ReusableBufferAssembler(int bufferSize) - { - mBufferSize = bufferSize; - } - - public void dispose() - { - mListener = null; - } - - public void reset() - { - mCurrentBuffer.decrementUserCount(); - mCurrentBuffer = null; - mBufferPointer = 0; - } - - @Override - public void receive(float sample) - { - if(mCurrentBuffer == null) - { - mCurrentBuffer = mReusableBufferQueue.getBuffer(mBufferSize); - mBufferPointer = 0; - } - - mCurrentBuffer.getSamples()[mBufferPointer++] = sample; - - if (mBufferPointer >= mBufferSize) - { - if (mListener != null) - { - mListener.receive(mCurrentBuffer); - } - else - { - mCurrentBuffer.decrementUserCount(); - } - - mCurrentBuffer = null; - } - } - - public void setListener(Listener listener) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java deleted file mode 100644 index fcd36e73..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java +++ /dev/null @@ -1,48 +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.sample.buffer; - -import io.github.dsheirer.sample.Broadcaster; -import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableBufferBroadcaster extends Broadcaster -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableBufferBroadcaster.class); - - /** - * Increments the user count for the reusable complex buffer and then broadcasts the buffer to all registered - * listeners. - * - * The total user count is established and applied to the buffer prior to dispatching. If we were to simply - * increment the user count prior to sending to each consumer, there is a possibility that the consumer could - * immediately decrement the user count and prematurely signal that the buffer is ready for disposal before we - * send the buffer to all consumers. - */ - @Override - public void broadcast(T reusableBuffer) - { - for(Listener listener : mListeners) - { - reusableBuffer.incrementUserCount(); - listener.receive(reusableBuffer); - } - - //Decrement user counter for this broadcaster - 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 deleted file mode 100644 index 7c4373f1..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2020 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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableBufferQueue extends AbstractReusableBufferQueue -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableBufferQueue.class); - - public ReusableBufferQueue(String debugName) - { - super(debugName); - } - - /** - * Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no - * buffers available for reuse. The user count is set to one and must be managed throughout the lifecycle - * of the buffer. - * - * @param size of the samples that will be loaded into the buffer - * @return a reusable buffer - */ - public ReusableFloatBuffer getBuffer(int size) - { - ReusableFloatBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableFloatBuffer(this, new float[size]); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.resize(size); - buffer.incrementUserCount(); - - return buffer; - } - - /** - * Creates or reuses a buffer and loads it with the samples and timestamp and increments user count to one. - * @param samples to load - * @param timestamp to set - * @return loaded buffer with user count set to one - */ - public ReusableFloatBuffer getBuffer(float[] samples, long timestamp) - { - ReusableFloatBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableFloatBuffer(this, new float[samples.length]); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.reloadFrom(samples, timestamp); - buffer.incrementUserCount(); - - return buffer; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBuffer.java deleted file mode 100644 index 8ed0169b..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBuffer.java +++ /dev/null @@ -1,97 +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.sample.buffer; - -import org.apache.commons.lang3.Validate; - -public class ReusableByteBuffer extends AbstractReusableBuffer -{ - private byte[] mSamples; - - /** - * Creates a reusable, timestamped byte buffer using the specified time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param bufferDisposedListener to be notified when all consumers/users are finished using the buffer - * @param bytes of data - * @param timestamp in millis for the buffer - */ - ReusableByteBuffer(IReusableBufferDisposedListener bufferDisposedListener, byte[] bytes, long timestamp) - { - super(bufferDisposedListener, timestamp); - mSamples = bytes; - - Validate.notNull(bufferDisposedListener, "Reusable Byte Buffer Listener cannot be null"); - } - - /** - * Constructs a timestamped byte buffer using the current system time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param bufferDisposedListener to be notified when all consumers are finished using the buffer - * @param bytes of data - */ - public ReusableByteBuffer(IReusableBufferDisposedListener bufferDisposedListener, byte[] bytes) - { - this(bufferDisposedListener, bytes, System.currentTimeMillis()); - } - - /** - * Samples for this buffer - */ - public byte[] getBytes() - { - return mSamples; - } - - /** - * Creates a copy of the samples from this buffer - */ - public byte[] getSamplesCopy() - { - byte[] bytes = getBytes(); - byte[] copy = new byte[bytes.length]; - System.arraycopy(bytes, 0, copy, 0, bytes.length); - return copy; - } - - /** - * Number of bytes contained in this buffer - */ - public int getSampleCount() - { - return getBytes().length; - } - - /** - * Resizes the internal array to the size argument - * - * @param size for the internal array - */ - protected void resize(int size) - { - if(mSamples.length != size) - { - mSamples = new byte[size]; - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBufferQueue.java deleted file mode 100644 index b322097e..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBufferQueue.java +++ /dev/null @@ -1,54 +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.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableByteBufferQueue extends AbstractReusableBufferQueue -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableByteBufferQueue.class); - - public ReusableByteBufferQueue(String debugName) - { - super(debugName); - } - - /** - * Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no - * buffers available for reuse. The user count is set to one and must be managed throughout the lifecycle - * of the buffer. - * - * @param size of the samples that will be loaded into the buffer - * @return a reusable buffer - */ - public ReusableByteBuffer getBuffer(int size) - { - ReusableByteBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableByteBuffer(this, new byte[size]); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.resize(size); - buffer.incrementUserCount(); - - return buffer; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBuffer.java deleted file mode 100644 index 8e401d05..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBuffer.java +++ /dev/null @@ -1,164 +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.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.LinkedList; -import java.util.List; - -public class ReusableChannelResultsBuffer extends AbstractReusableBuffer -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableChannelResultsBuffer.class); - private ReusableComplexBufferQueue mReusableComplexBufferQueue = - new ReusableComplexBufferQueue("ReusableChannelResultsBuffer"); - private LinkedList mEmptyBuffers = new LinkedList<>(); - private LinkedList mFilledBuffers = new LinkedList<>(); - private Integer mLength; - - /** - * Reusable buffer for storing polyphase channelizer results arrays. This buffer is NOT thread safe and is - * designed to be used by only one consumer. The consumer must invoke the prepareForRecycle() method to indicate that this - * buffer can be reused. - * - * @param disposedListener to receive notification when this buffer is ready for prepareForRecycle - */ - ReusableChannelResultsBuffer(IReusableBufferDisposedListener disposedListener) - { - super(disposedListener); - } - - /** - * Recycles this buffer for reuse. - */ - protected void prepareForRecycle() - { - mEmptyBuffers.addAll(mFilledBuffers); - mFilledBuffers.clear(); - mLength = null; - } - - /** - * Gets an empty channel results array correctly sized to the requested length. This method should be used to - * obtain all empty channel results buffers to fill with information and add back to this buffer so that the results - * arrays can be reused. - * - * @param length of the requested array - * @return a float array of the requested length that may contain stale data from previous use. - */ - public float[] getEmptyBuffer(int length) - { - float[] emptyBuffer = mEmptyBuffers.poll(); - - if(emptyBuffer == null || emptyBuffer.length != length) - { - emptyBuffer = new float[length]; - } - - return emptyBuffer; - } - - /** - * Adds the channel results to this buffer. Note: you should only use channel results arrays obtained via the - * getEmptyBuffer() method and filled with data and then added back to this buffer via this method, so that array - * reuse can be accurately controlled. - * - * @param channelResults to add back to this buffer - */ - public void addChannelResults(float[] channelResults) - { - if(mLength == null) - { - mLength = channelResults.length; - } - else if(mLength != channelResults.length) - { - throw new IllegalArgumentException("Channel results length must be the same for all added results"); - } - - mFilledBuffers.offer(channelResults); - } - - /** - * Channel results array contained in this buffer - */ - public List getChannelResults() - { - return mFilledBuffers; - } - - /** - * Extracts a single I/Q interleaved sample buffer from this buffer of channel results arrays - * - * @param iChannelIndex to extract for the inphase sample. The q index is assumed to be one greater than i. - * @return float[] containing interleaved I and Q samples - * @throws IllegalArgumentException if this buffer doesn't contain any samples or if the requested channel is - * not within the channel range of the contained channel results. - */ - public ReusableComplexBuffer getChannel(int iChannelIndex) - { - if(mFilledBuffers.isEmpty()) - { - throw new IllegalArgumentException("Buffer is empty - cannot extract channel [" + iChannelIndex + "]"); - } - - if(!isValidChannelIndex(iChannelIndex)) - { - throw new IllegalArgumentException("Channel [" + iChannelIndex + "] is not valid for the contained channel " + - "results -- max channel is " + mLength); - } - - ReusableComplexBuffer channelBuffer = mReusableComplexBufferQueue.getBuffer(mFilledBuffers.size() * 2); - - float[] samples = channelBuffer.getSamples(); - - int pointer = 0; - - int qChannelIndex = iChannelIndex + 1; - - for(float[] channelResults: mFilledBuffers) - { - try - { - samples[pointer] = channelResults[iChannelIndex]; - pointer++; - samples[pointer] = channelResults[qChannelIndex]; - pointer++; - } - catch(Exception e) - { - mLog.error("Error accessing channel results - iIndex:" + iChannelIndex + - " Results Size: " + mFilledBuffers.size() + - " Samples Length:" + samples.length + - " Pointer:" + pointer, e); - } - } - - return channelBuffer; - } - - /** - * Indicates if the channel argument is valid for the contained channel results. - * - * @param channelIndex number (0 to N) to check - * @return false if the buffer is empty or if the channel is greater than the contained channel results - */ - private boolean isValidChannelIndex(int channelIndex) - { - return (mLength != null) && (channelIndex <= mLength); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBufferQueue.java deleted file mode 100644 index 76c027ed..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBufferQueue.java +++ /dev/null @@ -1,55 +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.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableChannelResultsBufferQueue extends AbstractReusableBufferQueue -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableChannelResultsBufferQueue.class); - - public ReusableChannelResultsBufferQueue(String debugName) - { - super(debugName); - } - - public ReusableChannelResultsBufferQueue() - { - } - - /** - * Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no - * buffers available for reuse. - * - * @return a reusable buffer - */ - public ReusableChannelResultsBuffer getBuffer() - { - ReusableChannelResultsBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableChannelResultsBuffer(this); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.incrementUserCount(); - - return buffer; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java deleted file mode 100644 index 49df3b1e..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java +++ /dev/null @@ -1,72 +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.sample.buffer; - -public class ReusableComplexBuffer extends ReusableFloatBuffer -{ - /** - * Creates a reusable, timestamped complex buffer using the specified time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param disposalListener to be notified when all consumers/users are finished using the buffer - * @param samples of data - * @param timestamp in millis for the buffer - */ - ReusableComplexBuffer(IReusableBufferDisposedListener disposalListener, float[] samples, long timestamp) - { - super(disposalListener, samples, timestamp); - } - - /** - * Constructs a timestamped complex buffer using the current system time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param disposalListener to be notified when all consumers are finished using the buffer - * @param samples of data - */ - protected ReusableComplexBuffer(IReusableBufferDisposedListener disposalListener, float[] samples) - { - this(disposalListener, samples, System.currentTimeMillis()); - } - - /** - * Number of complex samples contained in this buffer - */ - @Override - public int getSampleCount() - { - return getSamples().length / 2; - } - - /** - * Applies the gain value to the samples contained in this buffer - */ - public void applyGain(double gain) - { - float[] samples = getSamples(); - - for(int x = 0; x < samples.length; x++) - { - samples[x] *= gain; - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java deleted file mode 100644 index 789358d8..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * 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.dsp.filter.channelizer.SampleTimestampManager; -import io.github.dsheirer.sample.Listener; -import org.apache.commons.lang3.Validate; - -import java.nio.FloatBuffer; - -/** - * Assembles complex float samples into a ComplexBuffer containing an array of floats. Monitors incoming sample count - * to accurately assign a relative timestamp to each assembled buffer. - */ -public class ReusableComplexBufferAssembler -{ -// private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexBufferAssembler.class); - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ReusableComplexBufferAssembler"); - private SampleTimestampManager mTimestampManager; - private FloatBuffer mBuffer; - private int mBufferSize; - private long mCurrentBufferTimestamp; - private Listener mListener; - - public ReusableComplexBufferAssembler(int bufferSize, double sampleRate) - { - Validate.isTrue(bufferSize % 2 == 0); - mBufferSize = bufferSize; - mBuffer = FloatBuffer.allocate(mBufferSize); - mTimestampManager = new SampleTimestampManager(sampleRate); - } - - /** - * Updates the reference timestamp to use for all subsequent samples that are assembled. This timestamp should - * identify the relative timestamp of the first/next sample that is received by this assembler. - * @param timestamp to use relative to the incoming sample arrays. - */ - public void updateTimestamp(long timestamp) - { - mTimestampManager.setReferenceTimestamp(timestamp); - - if(mBuffer.position() == 0) - { - mCurrentBufferTimestamp = timestamp; - } - } - - public void dispose() - { - mReusableComplexBufferQueue.dispose(); - } - - /** - * Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered - * listener. - * - * @param inphase sample to assemble - * @param quadrature sample to assemble - */ - public void receive(float inphase, float quadrature) - { - if(mBuffer.remaining() >= 2) - { - mBuffer.put(inphase); - mBuffer.put(quadrature); - - mTimestampManager.increment(1); - - if(!mBuffer.hasRemaining()) - { - flush(); - } - } - } - - /** - * Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered - * listener. - * - * @param samples to assemble - */ - public void receive(float[] samples) - { - if(mBuffer.remaining() >= samples.length) - { - mBuffer.put(samples); - mTimestampManager.increment(samples.length / 2); - - if(!mBuffer.hasRemaining()) - { - flush(); - } - } - else - { - int offset = 0; - - while(offset < samples.length) - { - int toCopy = mBuffer.remaining(); - - if((samples.length - offset) < toCopy) - { - toCopy = samples.length - offset; - } - - mBuffer.put(samples, offset, toCopy); - - offset += toCopy; - mTimestampManager.increment(toCopy / 2); - - if(!mBuffer.hasRemaining()) - { - flush(); - } - } - } - } - - /** - * Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered - * listener. - */ - public void receive(ReusableComplexBuffer samplesBuffer) - { - receive(samplesBuffer.getSamples()); - samplesBuffer.decrementUserCount(); - } - - /** - * Flushes the current buffer contents to the registered listener - */ - public void flush() - { - Listener listener = mListener; - - if(listener != null && mBuffer.position() > 0) - { - mBuffer.rewind(); - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(mBufferSize); - reusableComplexBuffer.reloadFrom(mBuffer, mCurrentBufferTimestamp); - listener.receive(reusableComplexBuffer); - } - - mBuffer.rewind(); - mCurrentBufferTimestamp = mTimestampManager.getCurrentTimestamp(); - } - - /** - * Sets the listener to receive the output from this buffer assembler. Note: the listener is responsible for - * managing the user count for any buffers received from this buffer assembler. - */ - public void setListener(Listener listener) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferQueue.java deleted file mode 100644 index 40057c30..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferQueue.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableComplexBufferQueue extends AbstractReusableBufferQueue -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexBufferQueue.class); - - /** - * Queue for managing the creation and reuse of reusable complex sample buffers. - * @param debugName to associate with this queue instance - */ - public ReusableComplexBufferQueue(String debugName) - { - super(debugName); - } - - /** - * Queue for managing the creation and reuse of reusable complex sample buffers. - */ - public ReusableComplexBufferQueue() - { - } - - /** - * Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no - * buffers available for reuse. - * - * @param size of the samples that will be loaded into the buffer - * @return a reusable buffer - */ - public ReusableComplexBuffer getBuffer(int size) - { - ReusableComplexBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableComplexBuffer(this, new float[size]); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.resize(size); - buffer.incrementUserCount(); - - return buffer; - } - - /** - * Returns a buffer loaded with the samples. - * @param samples to load - * @param timestamp to apply - * @return buffer - */ - public ReusableComplexBuffer getBuffer(float[] samples, long timestamp) - { - ReusableComplexBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableComplexBuffer(this, samples); - buffer.setTimestamp(timestamp); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - else - { - buffer.reloadFrom(samples, timestamp); - } - - buffer.incrementUserCount(); - - return buffer; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableFloatBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableFloatBuffer.java deleted file mode 100644 index ba66b323..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableFloatBuffer.java +++ /dev/null @@ -1,140 +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.sample.buffer; - -import org.apache.commons.lang3.Validate; - -import java.nio.FloatBuffer; - -public class ReusableFloatBuffer extends AbstractReusableBuffer -{ - private float[] mSamples; - - /** - * Creates a reusable, timestamped complex buffer using the specified time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param bufferDisposedListener to be notified when all consumers/users are finished using the buffer - * @param samples of data - * @param timestamp in millis for the buffer - */ - ReusableFloatBuffer(IReusableBufferDisposedListener bufferDisposedListener, float[] samples, long timestamp) - { - super(bufferDisposedListener, timestamp); - mSamples = samples; - - Validate.notNull(bufferDisposedListener, "Reusable Buffer Listener cannot be null"); - } - - /** - * Samples for this buffer - */ - public float[] getSamples() - { - return mSamples; - } - - /** - * Creates a copy of the samples from this buffer - */ - public float[] getSamplesCopy() - { - float[] samples = getSamples(); - float[] copy = new float[samples.length]; - System.arraycopy(samples, 0, copy, 0, samples.length); - return copy; - } - - /** - * Number of samples contained in this buffer - */ - public int getSampleCount() - { - return getSamples().length; - } - - /** - * Constructs a timestamped complex buffer using the current system time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param bufferDisposedListener to be notified when all consumers are finished using the buffer - * @param samples of data - */ - public ReusableFloatBuffer(IReusableBufferDisposedListener bufferDisposedListener, float[] samples) - { - this(bufferDisposedListener, samples, System.currentTimeMillis()); - } - - /** - * Reloads this buffer by copying the sample array into this buffer. Internal buffer length will be automatically - * resized to fit the new sample array length. - * - * This is convenient if the source of the sample array originates via a native buffer. - * @param samples to copy into this buffer - * @param timestamp for the samples in millis - */ - public void reloadFrom(float[] samples, long timestamp) - { - if(mUserCount.get() > 0) - { - throw new IllegalStateException("New data cannot be loaded into this reusable buffer while the user count " + - "is above zero - user count:" + mUserCount.get()); - } - - mUserCount.set(0); - - resize(samples.length); - - System.arraycopy(samples, 0, mSamples, 0, samples.length); - - mTimestamp = timestamp; - } - - /** - * Reloads this buffer by filling this buffer from the float buffer (ie copying the buffer contents). Internal - * buffer length will be automatically resized to accommodate the float buffer's capacity/size. - * - * @param floatBuffer to copy into this buffer - * @param timestamp for the samples in millis - */ - public void reloadFrom(FloatBuffer floatBuffer, long timestamp) - { - resize(floatBuffer.capacity()); - floatBuffer.rewind(); - floatBuffer.get(mSamples); - - mTimestamp = timestamp; - } - - /** - * Resizes the internal array to the size argument - * @param size for the internal array - */ - protected void resize(int size) - { - if(mSamples.length != size) - { - mSamples = new float[size]; - } - } - -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleAssembler.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleAssembler.java deleted file mode 100644 index f617b9a8..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleAssembler.java +++ /dev/null @@ -1,72 +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.sample.complex; - -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.Provider; - -import java.util.ArrayList; -import java.util.List; - -public class ComplexSampleAssembler implements Listener, - Provider> -{ - private ArrayList mBuffer; - private int mBufferSize; - private Listener> mListener; - - public ComplexSampleAssembler( int bufferSize ) - { - mBufferSize = bufferSize; - mBuffer = new ArrayList<>(); - } - - public void dispose() - { - mBuffer.clear(); - mListener = null; - } - - @Override - public void receive( Complex sample ) - { - mBuffer.add( sample ); - - if( mBuffer.size() >= mBufferSize ) - { - if( mListener != null ) - { - mListener.receive( mBuffer ); - } - - mBuffer = new ArrayList<>(); - } - } - - @Override - public void setListener( Listener> listener ) - { - mListener = listener; - } - - @Override - public void removeListener( Listener> listener ) - { - mListener = null; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleBroadcaster.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleBroadcaster.java deleted file mode 100644 index 08c8ba09..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleBroadcaster.java +++ /dev/null @@ -1,76 +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.sample.complex; - -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Broadcasts received float samples to multiple listeners - */ -public class ComplexSampleBroadcaster implements ComplexSampleListener -{ - private CopyOnWriteArrayList mListeners = - new CopyOnWriteArrayList(); - - @Override - public void receive( float i, float q ) - { - broadcast( i, q ); - } - - /** - * Clear listeners to prepare for garbage collection - */ - public void dispose() - { - mListeners.clear(); - } - - public boolean hasListeners() - { - return !mListeners.isEmpty(); - } - - public int getListenerCount() - { - return mListeners.size(); - } - - public void addListener( ComplexSampleListener listener ) - { - mListeners.add( listener ); - } - - public void removeListener( ComplexSampleListener listener ) - { - mListeners.remove( listener ); - } - - public void clear() - { - mListeners.clear(); - } - - public void broadcast( float i, float q ) - { - for( ComplexSampleListener listener: mListeners ) - { - listener.receive( i, q ); - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleListListener.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleListListener.java deleted file mode 100644 index 392f36ab..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleListListener.java +++ /dev/null @@ -1,25 +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.sample.complex; - -import java.util.List; - -public interface ComplexSampleListListener -{ - public void receive( List samples ); -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleUtils.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleUtils.java deleted file mode 100644 index 63e5a8ab..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleUtils.java +++ /dev/null @@ -1,51 +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.sample.complex; - - -import org.apache.commons.math3.util.FastMath; - -public class ComplexSampleUtils -{ - - public static Complex multiply( Complex a, - Complex b ) - { - return new Complex( - ( a.inphase() * b.inphase() ) - ( a.quadrature() * b.quadrature() ), - ( ( a.quadrature() * b.inphase() ) + ( a.inphase() * b.quadrature() ) ) ); - } - - public static Complex minus( Complex a, Complex b ) - { - return new Complex( a.inphase() - b.inphase(), - a.quadrature() - b.quadrature() ); - } - - public static double magnitude( Complex sample ) - { - return FastMath.sqrt( magnitudeSquared( sample ) ); - } - - public static int magnitudeSquared( Complex sample ) - { - return (int)( ( sample.inphase() * sample.inphase() ) + - ( sample.quadrature() * sample.quadrature() ) ); - } - -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSamples.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamples.java new file mode 100644 index 00000000..9d6fb0cd --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamples.java @@ -0,0 +1,44 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.complex; + +/** + * Wrapper for a complex sample array where I and Q samples are in separate arrays. + */ +public record ComplexSamples(float[] i, float[] q) +{ + /** + * Converts this non-interleaved complex samples to interleaved. + * @return interleaved samples. + */ + public InterleavedComplexSamples toInterleaved() + { + float[] interleaved = new float[i().length * 2]; + + for(int x = 0; x < i().length; x++) + { + interleaved[2 * x] = i()[x]; + interleaved[2 * x + 1] = q()[x]; + } + + return new InterleavedComplexSamples(interleaved, System.currentTimeMillis()); + } +} + diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesRepackager.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesRepackager.java new file mode 100644 index 00000000..1164e3e7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesRepackager.java @@ -0,0 +1,95 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.complex; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Processes I & Q sample arrays into uniform length complex samples buffers. + */ +public class ComplexSamplesRepackager +{ + private int mSize; + private float[] mIResidual = new float[0]; + private float[] mQResidual = new float[0]; + + /** + * Constructs an instance. + * @param size or length of the output complex samples. + */ + public ComplexSamplesRepackager(int size) + { + mSize = size; + } + + /** + * Combines the I & Q sample arrays with any residual leftover from the previous invocation and + * chunks the arrays up into uniform length complex sample buffers, storing any leftover samples + * as residual to use in the next processing invocation. If the length of the sample arguments + * combined with any residual is not enough to create at least one buffer, those samples will be + * added to the residual and used in the next operation, and no complex sample buffer will be + * returned. + * + * @param i samples array to repackage + * @param q samples array to repackage + * @return zero or more complex samples of uniform length. + */ + public List process(float[] i, float[] q) + { + float[] iCombined = new float[mIResidual.length + i.length]; + float[] qCombined = new float[mQResidual.length + q.length]; + + if(mIResidual.length > 0) + { + System.arraycopy(mIResidual, 0, iCombined, 0, mIResidual.length); + System.arraycopy(mQResidual, 0, qCombined, 0, mQResidual.length); + } + + System.arraycopy(i, 0, iCombined, mIResidual.length, i.length); + System.arraycopy(q, 0, qCombined, mQResidual.length, q.length); + + int processed = 0; + + List samples = new ArrayList<>(); + + while((processed + mSize) < iCombined.length) + { + float[] iChunk = Arrays.copyOfRange(iCombined, processed, processed + mSize); + float[] qChunk = Arrays.copyOfRange(qCombined, processed, processed + mSize); + samples.add(new ComplexSamples(iChunk, qChunk)); + processed += mSize; + } + + if(processed < iCombined.length) + { + mIResidual = Arrays.copyOfRange(iCombined, processed, iCombined.length); + mQResidual = Arrays.copyOfRange(qCombined, processed, qCombined.length); + } + else + { + mIResidual = new float[0]; + mQResidual = new float[0]; + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java b/src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesListener.java similarity index 86% rename from src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java rename to src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesListener.java index 15596a07..2527613d 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java +++ b/src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesListener.java @@ -13,11 +13,11 @@ * If not, see * ******************************************************************************/ -package io.github.dsheirer.sample.buffer; +package io.github.dsheirer.sample.complex; import io.github.dsheirer.sample.Listener; -public interface IReusableBufferListener +public interface IComplexSamplesListener { - Listener getReusableBufferListener(); + Listener getComplexSamplesListener(); } diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferProvider.java b/src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesProvider.java similarity index 84% rename from src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferProvider.java rename to src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesProvider.java index e84db05c..c91aeb62 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferProvider.java +++ b/src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesProvider.java @@ -13,21 +13,21 @@ * If not, see * ******************************************************************************/ -package io.github.dsheirer.sample.buffer; +package io.github.dsheirer.sample.complex; import io.github.dsheirer.sample.Listener; -public interface IReusableByteBufferProvider +public interface IComplexSamplesProvider { /** * Adds the listener to receive complex buffer samples */ - void setBufferListener(Listener listener); + void addBufferListener(Listener listener); /** * Removes the listener from receiving complex buffer samples */ - void removeBufferListener(Listener listener); + void removeBufferListener(Listener listener); /** * Indicates if there are any complex buffer listeners registered diff --git a/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesListener.java b/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesListener.java new file mode 100644 index 00000000..e0fc0d05 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesListener.java @@ -0,0 +1,29 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.complex; + +import io.github.dsheirer.sample.Listener; + +/** + * Interface for listener of interleaved complex samples + */ +public interface IInterleavedComplexSamplesListener +{ + Listener getReusableComplexBufferListener(); +} diff --git a/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesProvider.java b/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesProvider.java new file mode 100644 index 00000000..80e02501 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesProvider.java @@ -0,0 +1,42 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.complex; + +import io.github.dsheirer.sample.Listener; + +/** + * Interface for a provider of interleaved complex samples + */ +public interface IInterleavedComplexSamplesProvider +{ + /** + * Adds the listener to receive interleaved complex samples + */ + void addBufferListener(Listener listener); + + /** + * Removes the listener from receiving interleaved complex 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/complex/InterleavedComplexSamples.java b/src/main/java/io/github/dsheirer/sample/complex/InterleavedComplexSamples.java new file mode 100644 index 00000000..d08ac31b --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/InterleavedComplexSamples.java @@ -0,0 +1,46 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.complex; + +import io.github.dsheirer.sample.buffer.ITimestamped; + +/** + * Complex samples array that incorporates a timestamp for the first complex sample. + */ +public record InterleavedComplexSamples(float[] samples, long timestamp) implements ITimestamped +{ + /** + * Converts this interleaved complex samples to de-interleaved. + * @return de-interleaved samples. + */ + public ComplexSamples toDeinterleaved() + { + float[] i = new float[samples().length / 2]; + float[] q = new float[samples().length / 2]; + + for(int x = 0; x < i.length; x++) + { + i[x] = samples()[x / 2]; + q[x] = samples()[x / 2 + 1]; + } + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ThreadKiller.java b/src/main/java/io/github/dsheirer/sample/complex/ThreadKiller.java deleted file mode 100644 index feea8bd9..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ThreadKiller.java +++ /dev/null @@ -1,33 +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.sample.complex; - -/** - * Poison Pill class to kill a processing thread. Where a thread accepts - * ComplexSample objects, send this class to the thread as a sentinal value - * to kill the processing thread. - */ -public class ThreadKiller extends Complex -{ - private static final long serialVersionUID = 1L; - - public ThreadKiller() - { - super( 0, 0 ); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java b/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java deleted file mode 100644 index b9445ddf..00000000 --- a/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java +++ /dev/null @@ -1,75 +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.sample.decimator; - -import io.github.dsheirer.sample.complex.ComplexSampleListener; - -/** - * Decimator for complex samples - */ -public class ComplexDecimator implements ComplexSampleListener -{ - private ComplexSampleListener mListener; - private int mCounter = 0; - private int mDecimationRate; - - /** - * Constructs a new Decimator object with the specified decimation rate. - */ - public ComplexDecimator(int rate) - { - mDecimationRate = rate; - } - - public void dispose() - { - mListener = null; - } - - /** - * Sets a new decimation rate. Rate can be changed after this object - * is constructed. - */ - public synchronized void setDecimationRate(int rate) - { - mDecimationRate = rate; - } - - /** - * Receives samples allowing only 1 of every (rate) sample to go on - * to the registered listener - */ - @Override - public void receive(float i, float q) - { - mCounter++; - - if(mCounter >= mDecimationRate) - { - mListener.receive(i, q); - - mCounter = 0; - } - } - - /** - * Sets the decimated output listener - */ - public void setListener(ComplexSampleListener listener) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/decimator/Decimator.java b/src/main/java/io/github/dsheirer/sample/decimator/Decimator.java deleted file mode 100644 index 2cddce54..00000000 --- a/src/main/java/io/github/dsheirer/sample/decimator/Decimator.java +++ /dev/null @@ -1,72 +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.sample.decimator; - -import io.github.dsheirer.sample.Listener; - - -public class Decimator implements Listener -{ - private int mDecimationFactor; - private int mDecimationPointer = 0; - private Listener mListener; - - /** - * Constructs the class to consume samples and decimate them by the - * DECIMATIONFACTOR. - * - * @param decimationFactor - number of samples to decimate. Provides one - * sample to registered listeners and discards the next decimationFactor - 1 - * samples. - */ - public Decimator( int decimationFactor ) - { - mDecimationFactor = decimationFactor; - } - - /** - * Receives and broadcasts a sample and then discards the next - * decimationFactor -1 samples - */ - public void receive( T t ) - { - if( mDecimationPointer == 0 ) - { - if( mListener != null ) - { - mListener.receive( t ); - } - } - - mDecimationPointer++; - - //Reset the decimation pointer once it equals the decimation factor - if( mDecimationPointer == mDecimationFactor ) - { - mDecimationPointer = 0; - } - } - - /** - * Adds a sample listener to receive samples - */ - public void setListener( Listener listener ) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/decimator/RealDecimator.java b/src/main/java/io/github/dsheirer/sample/decimator/RealDecimator.java deleted file mode 100644 index c81ee3a5..00000000 --- a/src/main/java/io/github/dsheirer/sample/decimator/RealDecimator.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.dsheirer.sample.decimator; - -import io.github.dsheirer.sample.real.RealSampleListener; - - -public class RealDecimator implements RealSampleListener -{ - private RealSampleListener mListener; - private int mCounter = 0; - private int mDecimationRate; - - /** - * Constructs a new Decimator object with the specified decimation rate. - */ - public RealDecimator( int rate ) - { - mDecimationRate = rate; - } - - public void dispose() - { - mListener = null; - } - - /** - * Sets a new decimation rate. Rate can be changed after this object - * is constructed. - */ - public synchronized void setDecimationRate( int rate ) - { - mDecimationRate = rate; - } - - /** - * Receives samples allowing only 1 of every (rate) sample to go on - * to the registered listener - */ - @Override - public void receive( float sample ) - { - mCounter++; - - if( mCounter >= mDecimationRate ) - { - mListener.receive( sample ); - - mCounter = 0; - } - } - - /** - * Sets the decimated output listener - */ - public void setListener( RealSampleListener listener ) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/real/IRealBufferListener.java b/src/main/java/io/github/dsheirer/sample/real/IRealBufferListener.java new file mode 100644 index 00000000..57c95563 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/real/IRealBufferListener.java @@ -0,0 +1,26 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.real; + +import io.github.dsheirer.sample.Listener; + +public interface IRealBufferListener +{ + Listener getBufferListener(); +} diff --git a/src/main/java/io/github/dsheirer/sample/real/IRealBufferProvider.java b/src/main/java/io/github/dsheirer/sample/real/IRealBufferProvider.java new file mode 100644 index 00000000..aef2b002 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/real/IRealBufferProvider.java @@ -0,0 +1,34 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.real; + +import io.github.dsheirer.sample.Listener; + +public interface IRealBufferProvider +{ + /** + * Adds the listener to receive complex buffer samples + */ + void setBufferListener(Listener listener); + + /** + * Removes the listener from receiving complex buffer samples + */ + void removeBufferListener(); +} diff --git a/src/main/java/io/github/dsheirer/sample/real/RealSampleBroadcaster.java b/src/main/java/io/github/dsheirer/sample/real/RealSampleBroadcaster.java deleted file mode 100644 index b33ba053..00000000 --- a/src/main/java/io/github/dsheirer/sample/real/RealSampleBroadcaster.java +++ /dev/null @@ -1,76 +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.sample.real; - -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Broadcasts a received float sample to multiple listeners - */ -public class RealSampleBroadcaster implements RealSampleListener -{ - private CopyOnWriteArrayList mListeners = - new CopyOnWriteArrayList(); - - @Override - public void receive( float sample ) - { - broadcast( sample ); - } - - /** - * Clear listeners to prepare for garbage collection - */ - public void dispose() - { - mListeners.clear(); - } - - public boolean hasListeners() - { - return !mListeners.isEmpty(); - } - - public int getListenerCount() - { - return mListeners.size(); - } - - public void addListener( RealSampleListener listener ) - { - mListeners.add( listener ); - } - - public void removeListener( RealSampleListener listener ) - { - mListeners.remove( listener ); - } - - public void clear() - { - mListeners.clear(); - } - - public void broadcast( float sample ) - { - for( RealSampleListener listener: mListeners ) - { - listener.receive( sample ); - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/real/RealSampleProvider.java b/src/main/java/io/github/dsheirer/sample/real/RealSampleProvider.java deleted file mode 100644 index 71cc4086..00000000 --- a/src/main/java/io/github/dsheirer/sample/real/RealSampleProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.github.dsheirer.sample.real; - -public interface RealSampleProvider -{ - public void setListener( RealSampleListener listener ); - - public void removeListener( RealSampleListener listener ); -} diff --git a/src/main/java/io/github/dsheirer/source/ComplexSource.java b/src/main/java/io/github/dsheirer/source/ComplexSource.java index 098641d5..30afee74 100644 --- a/src/main/java/io/github/dsheirer/source/ComplexSource.java +++ b/src/main/java/io/github/dsheirer/source/ComplexSource.java @@ -17,9 +17,9 @@ package io.github.dsheirer.source; import io.github.dsheirer.sample.Provider; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; -public abstract class ComplexSource extends Source implements Provider +public abstract class ComplexSource extends Source implements Provider { @Override public SampleType getSampleType() diff --git a/src/main/java/io/github/dsheirer/source/RealSource.java b/src/main/java/io/github/dsheirer/source/RealSource.java index a84ad915..47f0d57c 100644 --- a/src/main/java/io/github/dsheirer/source/RealSource.java +++ b/src/main/java/io/github/dsheirer/source/RealSource.java @@ -1,11 +1,29 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.source; import io.github.dsheirer.sample.Provider; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -public abstract class RealSource extends Source implements Provider +public abstract class RealSource extends Source implements Provider { @Override public SampleType getSampleType() diff --git a/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java b/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java index 6ceab8ad..b1425004 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java +++ b/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java @@ -1,23 +1,26 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.mixer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.ComplexSampleAdapter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.adapter.ISampleAdapter; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +31,7 @@ import javax.sound.sampled.TargetDataLine; public class ComplexMixer { private final static Logger mLog = LoggerFactory.getLogger(ComplexMixer.class); - private MixerReader mMixerReader; + private MixerReader mMixerReader; private String mName; /** @@ -38,12 +41,11 @@ public class ComplexMixer * @param targetDataLine - mixer or sound card to be used * @param format - audio format * @param name - token name to use for this mixer - * @param complexSampleAdapter - adapter to convert byte array data read from the mixer into ReusableComplexBuffer. + * @param sampleAdapter - adapter to convert byte array data read from the mixer into ReusableComplexBuffer. */ - public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name, - ComplexSampleAdapter complexSampleAdapter) + public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name, ISampleAdapter sampleAdapter) { - mMixerReader = new MixerReader<>(format, targetDataLine, complexSampleAdapter); + mMixerReader = new MixerReader(format, targetDataLine, sampleAdapter); mName = name; } @@ -74,7 +76,7 @@ public class ComplexMixer mMixerReader.stop(); } - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mMixerReader.setBufferListener(listener); } diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java index a68012e7..2379ede3 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java +++ b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.mixer; import io.github.dsheirer.audio.AudioFormats; @@ -84,7 +87,7 @@ public class MixerManager if(dataLine != null) { return new RealMixerSource(dataLine, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO, - new RealShortAdapter("RealMixerSource - Mono")); + new RealShortAdapter()); } } catch(LineUnavailableException e) @@ -108,7 +111,7 @@ public class MixerManager if(dataLine != null) { return new RealMixerSource(dataLine, AudioFormats.PCM_SIGNED_8KHZ_16BITS_STEREO, - new RealChannelShortAdapter(mixerConfig.getChannel(), "RealMixerSource-Stereo")); + new RealChannelShortAdapter(mixerConfig.getChannel())); } } catch(LineUnavailableException e) diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java b/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java index 351d9a4c..3c560828 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java +++ b/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.mixer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.AbstractSampleAdapter; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.adapter.ISampleAdapter; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.heartbeat.HeartbeatManager; import io.github.dsheirer.util.ThreadPool; @@ -37,7 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * the samples to an array of floats using the specified adapter. Dispatches float arrays to the registered * buffer listener. */ -public class MixerReader implements Runnable +public class MixerReader implements Runnable { private final static Logger mLog = LoggerFactory.getLogger(MixerReader.class); @@ -48,14 +50,14 @@ public class MixerReader implements Runnable private AtomicBoolean mRunning = new AtomicBoolean(); private ScheduledFuture mScheduledFuture; private int mBytesRead; - private AbstractSampleAdapter mSampleAdapter; - private Listener mReusableBufferListener; + private ISampleAdapter mSampleAdapter; + private Listener mListener; private Listener mSourceEventListener; private AudioFormat mAudioFormat; private HeartbeatManager mHeartbeatManager; public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, - AbstractSampleAdapter abstractSampleAdapter, HeartbeatManager heartbeatManager) + ISampleAdapter abstractSampleAdapter, HeartbeatManager heartbeatManager) { mTargetDataLine = targetDataLine; mAudioFormat = audioFormat; @@ -66,7 +68,7 @@ public class MixerReader implements Runnable mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize(); } - public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, AbstractSampleAdapter abstractSampleAdapter) + public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, ISampleAdapter abstractSampleAdapter) { this(audioFormat, targetDataLine, abstractSampleAdapter, new HeartbeatManager()); } @@ -210,9 +212,9 @@ public class MixerReader implements Runnable //Sample adapter automatically sets initial listener count to one. T reusableBuffer = mSampleAdapter.convert(mBuffer); - if(reusableBuffer != null && mReusableBufferListener != null) + if(reusableBuffer != null && mListener != null) { - mReusableBufferListener.receive(reusableBuffer); + mListener.receive(reusableBuffer); } } else if(mBytesRead > 0) @@ -220,9 +222,9 @@ public class MixerReader implements Runnable //Sample adapter automatically sets initial listener count to one. T reusableBuffer = mSampleAdapter.convert(Arrays.copyOf(mBuffer, mBytesRead)); - if(reusableBuffer != null && mReusableBufferListener != null) + if(reusableBuffer != null && mListener != null) { - mReusableBufferListener.receive(reusableBuffer); + mListener.receive(reusableBuffer); } } } @@ -247,7 +249,7 @@ public class MixerReader implements Runnable */ public void setBufferListener(Listener listener) { - mReusableBufferListener = listener; + mListener = listener; } /** @@ -255,7 +257,7 @@ public class MixerReader implements Runnable */ public void removeBufferListener() { - mReusableBufferListener = null; + mListener = null; } /** @@ -296,7 +298,7 @@ public class MixerReader implements Runnable { stop(); mHeartbeatManager = null; - mReusableBufferListener = null; + mListener = null; mSourceEventListener = null; } } 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 6232cc39..85edcd7a 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java +++ b/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.mixer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.AbstractSampleAdapter; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.adapter.ISampleAdapter; import io.github.dsheirer.source.RealSource; import io.github.dsheirer.source.SourceEvent; @@ -40,7 +42,7 @@ public class RealMixerSource extends RealSource * @param sampleAdapter - adapter to convert byte array data read from the * mixer into float array data. */ - public RealMixerSource(TargetDataLine targetDataLine, AudioFormat format, AbstractSampleAdapter sampleAdapter) + public RealMixerSource(TargetDataLine targetDataLine, AudioFormat format, ISampleAdapter sampleAdapter) { mMixerReader = new MixerReader(format, targetDataLine, sampleAdapter, getHeartbeatManager()); } @@ -101,7 +103,7 @@ public class RealMixerSource extends RealSource /** * Sets the listener to receive sample data in reusable buffers. */ - public void setListener(Listener listener) + public void setListener(Listener listener) { mMixerReader.setBufferListener(listener); } @@ -110,7 +112,7 @@ public class RealMixerSource extends RealSource * Removes the listener from receiving sample data. * @param listener */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mMixerReader.removeBufferListener(); } 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 e5de2e64..12a29117 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2022 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 @@ -18,13 +18,13 @@ */ package io.github.dsheirer.source.tuner; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.INativeBufferProvider; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderFactory; -import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder; +import io.github.dsheirer.record.wave.NativeBufferWaveRecorder; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -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; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -40,16 +40,16 @@ import org.slf4j.LoggerFactory; import java.util.SortedSet; public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener, - IReusableComplexBufferProvider, Listener, ITunerErrorListener + INativeBufferProvider, Listener, ITunerErrorListener { private final static Logger mLog = LoggerFactory.getLogger(TunerController.class); - protected ReusableBufferBroadcaster mReusableBufferBroadcaster = new ReusableBufferBroadcaster(); + protected Broadcaster mNativeBufferBroadcaster = new Broadcaster(); protected FrequencyController mFrequencyController; private int mMiddleUnusableHalfBandwidth; private double mUsableBandwidthPercentage; private Listener mSourceEventListener; private int mMeasuredFrequencyError; - private ComplexBufferWaveRecorder mRecorder; + private NativeBufferWaveRecorder mRecorder; private ITunerErrorListener mTunerErrorListener; /** @@ -137,13 +137,13 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, case REQUEST_START_SAMPLE_STREAM: if(sourceEvent.getSource() instanceof Listener) { - addBufferListener((Listener)sourceEvent.getSource()); + addBufferListener((Listener)sourceEvent.getSource()); } break; case REQUEST_STOP_SAMPLE_STREAM: if(sourceEvent.getSource() instanceof Listener) { - removeBufferListener((Listener)sourceEvent.getSource()); + removeBufferListener((Listener)sourceEvent.getSource()); } break; default: @@ -357,18 +357,18 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, * Adds the listener to receive complex buffer samples */ @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { - mReusableBufferBroadcaster.addListener(listener); + mNativeBufferBroadcaster.addListener(listener); } /** * Removes the listener from receiving complex buffer samples */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { - mReusableBufferBroadcaster.removeListener(listener); + mNativeBufferBroadcaster.removeListener(listener); } /** @@ -377,24 +377,24 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, @Override public boolean hasBufferListeners() { - return mReusableBufferBroadcaster.hasListeners(); + return mNativeBufferBroadcaster.hasListeners(); } /** * Broadcasts the buffer to any registered listeners */ - protected void broadcast(ReusableComplexBuffer reusableComplexBuffer) + protected void broadcast(INativeBuffer complexSamples) { - mReusableBufferBroadcaster.broadcast(reusableComplexBuffer); + mNativeBufferBroadcaster.broadcast(complexSamples); } /** * Implements the Listener interface to receive and distribute complex buffers from subclass implementations */ @Override - public void receive(ReusableComplexBuffer complexBuffer) + public void receive(INativeBuffer nativeBuffer) { - broadcast(complexBuffer); + broadcast(nativeBuffer); } /** @@ -480,13 +480,13 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, /** * Records the complex I/Q buffers produced by the tuner - * @param recorderManager to obtain a baseband recorder + * @param userPreferences to obtain a baseband recorder */ public void startRecorder(UserPreferences userPreferences) { if(!isRecording()) { - mRecorder = RecorderFactory.getBasebandRecorder("TUNER_" + getFrequency(), userPreferences); + mRecorder = RecorderFactory.getTunerRecorder("TUNER_" + getFrequency(), userPreferences); mRecorder.setSampleRate((float)getSampleRate()); mRecorder.start(); addBufferListener(mRecorder); 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 deleted file mode 100644 index eb40a07a..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java +++ /dev/null @@ -1,160 +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.source.tuner.airspy; - -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; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; - -public class AirspySampleConverter extends NativeBufferConverter -{ - private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f; - - private DCRemovalFilter mDCFilter = new DCRemovalFilter(0.01f); - private HilbertTransform mHilbertTransform = new HilbertTransform(); - private boolean mSamplePacking = false; - private FloatBuffer mFloatBuffer; - private float[] mConvertedSamples; - - /** - * Adapter to translate byte buffers received from the airspy tuner into - * float buffers for processing. - */ - public AirspySampleConverter() - { - } - - @Override - protected FloatBuffer convertSamples(ByteBuffer buffer, int length) - { - float[] samples = convert(buffer); - - if(mFloatBuffer == null || mFloatBuffer.capacity() != samples.length) - { - mFloatBuffer = FloatBuffer.allocate(samples.length); - } - - mFloatBuffer.rewind(); - mFloatBuffer.put(samples); - - return mFloatBuffer; - } - - /** - * Sample packing places two 12-bit samples into 3 bytes when enabled or - * places two 12-bit samples into 4 bytes when disabled. - * - * @param enabled - */ - public void setSamplePacking(boolean enabled) - { - mSamplePacking = enabled; - } - - private float[] convert(ByteBuffer samples) - { - if(mSamplePacking) - { - convertPacked(samples.order(ByteOrder.LITTLE_ENDIAN)); - } - else - { - convertUnpacked(samples); - } - - mDCFilter.filter(mConvertedSamples); - - return mHilbertTransform.filter(mConvertedSamples); - } - - /** - * Converts the byte array containing unsigned 12-bit short values into - * signed float values in the range -1 to 1; - * - * @param buffer - native byte buffer containing unsigned 16-bit values - */ - private void convertUnpacked(ByteBuffer buffer) - { - buffer.rewind(); - - if(mConvertedSamples == null || mConvertedSamples.length != buffer.capacity() / 2) - { - mConvertedSamples = new float[buffer.capacity() / 2]; - } - - int pointer = 0; - - - while(buffer.remaining() >= 2) - { - byte lsb = buffer.get(); - byte msb = buffer.get(); - mConvertedSamples[pointer++] = scale((lsb & 0xFF) | (msb << 8)); - } - } - - /** - * Converts every 3 bytes containing a pair of 12-bit unsigned values into - * a pair of float values in the range -1 to 1; - * - * @param buffer - native byte buffer containing packet 12-bit unsigned samples - */ - private void convertPacked(ByteBuffer buffer) - { - buffer.rewind(); - - int sampleCount = buffer.capacity() / 3 * 2; - - if(mConvertedSamples == null || mConvertedSamples.length != sampleCount) - { - mConvertedSamples = new float[sampleCount]; - } - - int pointer = 0; - - byte b1; - byte b2; - byte b3; - int first; - int second; - - while(buffer.remaining() >= 3) - { - b1 = buffer.get(); - b2 = buffer.get(); - b3 = buffer.get(); - - first = ((b1 << 4) & 0xFF0) | ((b2 >> 4) & 0xF); - mConvertedSamples[pointer++] = scale(first); - - second = ((b2 << 8) & 0xF00) | (b3 & 0xFF); - mConvertedSamples[pointer++] = scale(second); - } - } - - /** - * Converts unsigned 12-bit values to signed 12-bit values and then scales - * the signed value to a signed float value in range: -1.0 : +1.0 - */ - public static float scale(int value) - { - return (float) ((value & 0xFFF) - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT; - } -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java index c10bfff4..cea290f5 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java @@ -1,27 +1,25 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.airspy; +import io.github.dsheirer.buffer.airspy.AirspyNativeBufferFactory; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; @@ -76,7 +74,7 @@ public class AirspyTunerController extends USBTunerController private Device mDevice; private DeviceHandle mDeviceHandle; - private AirspySampleConverter mSampleAdapter = new AirspySampleConverter(); + private AirspyNativeBufferFactory mNativeBufferFactory = new AirspyNativeBufferFactory(); private AirspyDeviceInformation mDeviceInfo; private List mSampleRates = new ArrayList<>(); private int mSampleRate = 0; @@ -166,7 +164,7 @@ public class AirspyTunerController extends USBTunerController String deviceName = "Airspy " + getDeviceInfo().getSerialNumber(); - mUSBTransferProcessor = new USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter, + mUSBTransferProcessor = new USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferFactory, USB_TRANSFER_BUFFER_SIZE, this); } @@ -489,7 +487,7 @@ public class AirspyTunerController extends USBTunerController /* If we didn't throw an exception above, then update the sample adapter * to process samples accordingly */ - mSampleAdapter.setSamplePacking(enabled); + mNativeBufferFactory.setSamplePacking(enabled); } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java index 9253200b..3861b143 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java @@ -1,33 +1,36 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.channel; -import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; +import io.github.dsheirer.dsp.filter.cic.PrimeCicDecimationFilter; import io.github.dsheirer.dsp.filter.design.FilterDesignException; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; -import io.github.dsheirer.sample.IOverflowListener; +import io.github.dsheirer.dsp.mixer.ComplexMixer; +import io.github.dsheirer.dsp.mixer.ComplexMixerFactory; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.ComplexSamplesRepackager; import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.Dispatcher; -import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -35,24 +38,24 @@ import java.util.List; * CIC decimation filter that requires the decimation rate to be an integer multiple. Sample buffer processing * occurs on a scheduled runnable thread. */ -public class CICTunerChannelSource extends TunerChannelSource implements Listener +public class CICTunerChannelSource extends TunerChannelSource implements Listener { -// private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class); - //Maximum number of filled buffers for the blocking queue private static final int BUFFER_MAX_CAPACITY = 300; //Threshold for resetting buffer overflow condition private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100; - private OverflowableReusableBufferTransferQueue mBuffer; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource"); - private IOscillator mFrequencyCorrectionMixer; - private ComplexPrimeCICDecimate mDecimationFilter; - private List mSampleBuffers = new ArrayList<>(); + //Samples repackager is set to 2048 (a power of 2) to align with channel processing chain requirements. + private final ComplexSamplesRepackager mSamplesRepackager = new ComplexSamplesRepackager(1024); + private Dispatcher mBufferDispatcher; + private ComplexMixer mFrequencyCorrectionMixer; + private PrimeCicDecimationFilter mIDecimationFilter; + private PrimeCicDecimationFilter mQDecimationFilter; private double mChannelSampleRate; private long mChannelFrequencyCorrection = 0; private long mTunerFrequency; + private Listener mListener; /** * Constructs a frequency translating and CIC decimating channel source. @@ -71,29 +74,35 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene int decimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate()); - mDecimationFilter = new ComplexPrimeCICDecimate(sampleRate, decimation, channelSpecification.getPassFrequency(), - channelSpecification.getStopFrequency()); + mIDecimationFilter = new PrimeCicDecimationFilter(sampleRate, decimation, channelSpecification.getPassFrequency(), + channelSpecification.getStopFrequency()); + mQDecimationFilter = new PrimeCicDecimationFilter(sampleRate, decimation, channelSpecification.getPassFrequency(), + channelSpecification.getStopFrequency()); - mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD); + mBufferDispatcher = new Dispatcher(BUFFER_MAX_CAPACITY, "sdrtrunk cic channel " + + tunerChannel.getFrequency(), new NativeBufferPoisonPill()); + mBufferDispatcher.setListener(new NativeBufferProcessor()); //Setup the frequency mixer to the current source frequency mChannelSampleRate = sampleRate / (double)decimation; mTunerFrequency = tunerChannel.getFrequency(); long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency(); - mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate); + mFrequencyCorrectionMixer = ComplexMixerFactory.getMixer(frequencyOffset, sampleRate); } - /** - * Registers an overflow listener to be notified in the internal buffer is in an - * overflow state. - * - * @param listener to receiver updates to the source overflow state - */ @Override - public void setOverflowListener(IOverflowListener listener) + public void start() { - mBuffer.setOverflowListener(listener); + super.start(); + mBufferDispatcher.start(); + } + + @Override + public void stop() + { + super.stop(); + mBufferDispatcher.stop(); } @Override @@ -106,9 +115,9 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene * Primary interface for receiving incoming complex sample buffers to be frequency translated and decimated. */ @Override - public void receive(ReusableComplexBuffer buffer) + public void receive(T complexSamples) { - mBuffer.offer(buffer); + mBufferDispatcher.receive(complexSamples); } /** @@ -191,21 +200,14 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene /** * Sets the listener to receive the complex buffer sample output from this channel * - * @param complexBufferListener to receive complex buffers + * @param listener to receive complex buffers */ @Override - public void setListener(Listener complexBufferListener) + public void setListener(Listener listener) { - mDecimationFilter.setListener(complexBufferListener); + mListener = listener; } - @Override - public void removeListener(Listener listener) - { - mDecimationFilter.removeListener(); - } - - @Override public double getSampleRate() { @@ -213,37 +215,33 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene } /** - * Primary processing method that is invoked on a recurring basis to process any queued complex buffers. - * - * Mixes the target frequency to baseband and then passes the buffer to the CIC decimation filter + * Processes native buffers received from the dispatcher and sends to the registered listener */ - protected void processSamples() + public class NativeBufferProcessor implements Listener { - mBuffer.drainTo(mSampleBuffers); - - for(ReusableComplexBuffer complexBuffer : mSampleBuffers) + @Override + public void receive(T nativeBuffer) { - float[] samples = complexBuffer.getSamples(); + getHeartbeatManager().broadcast(); - ReusableComplexBuffer translatedComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); - float[] translatedSamples = translatedComplexBuffer.getSamples(); - - /* Perform frequency translation */ - for(int x = 0; x < samples.length; x += 2) + if(mListener != null) { - mFrequencyCorrectionMixer.rotate(); + Iterator iterator = nativeBuffer.iterator(); - translatedSamples[x] = Complex.multiplyInphase(samples[x], samples[x + 1], - mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); + while(iterator.hasNext()) + { + ComplexSamples basebanded = mFrequencyCorrectionMixer.mix(iterator.next()); + float[] i = mIDecimationFilter.decimate(basebanded.i()); + float[] q = mQDecimationFilter.decimate(basebanded.q()); - translatedSamples[x + 1] = Complex.multiplyQuadrature(samples[x], samples[x + 1], - mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); + List repackaged = mSamplesRepackager.process(i, q); + + for(ComplexSamples samples: repackaged) + { + mListener.receive(samples); + } + } } - - mDecimationFilter.receive(translatedComplexBuffer); - complexBuffer.decrementUserCount(); } - - mSampleBuffers.clear(); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java new file mode 100644 index 00000000..f3ab04fe --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java @@ -0,0 +1,244 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.source.tuner.channel; + +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; +import io.github.dsheirer.dsp.filter.decimate.DecimationFilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; +import io.github.dsheirer.dsp.mixer.ComplexMixer; +import io.github.dsheirer.dsp.mixer.ComplexMixerFactory; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.Dispatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; + +/** + * Complex sample source that provides a frequency-translated and decimated sample buffer stream. Uses a + * CIC decimation filter that requires the decimation rate to be an integer multiple. Sample buffer processing + * occurs on a scheduled runnable thread. + */ +public class HalfBandTunerChannelSource extends TunerChannelSource implements Listener +{ + private static final Logger mLog = LoggerFactory.getLogger(HalfBandTunerChannelSource.class); + + //Maximum number of filled buffers for the blocking queue + private static final int BUFFER_MAX_CAPACITY = 600; + + private Dispatcher mBufferDispatcher; + private ComplexMixer mFrequencyCorrectionMixer; + private IRealDecimationFilter mIDecimationFilter; + private IRealDecimationFilter mQDecimationFilter; + private double mChannelSampleRate; + private long mChannelFrequencyCorrection = 0; + private long mTunerFrequency; + private Listener mListener; + + /** + * Constructs a frequency translating and CIC decimating channel source. + * + * @param producerSourceEventListener to receive sample stream start/stop requests + * @param tunerChannel that details the desired channel frequency and bandwidth + * @param sampleRate of the incoming sample stream + * @param channelSpecification for the requested channel. + * @throws FilterDesignException if a final cleanup filter cannot be designed using the remez filter + * designer and the filter parameters. + */ + public HalfBandTunerChannelSource(Listener producerSourceEventListener, TunerChannel tunerChannel, + double sampleRate, ChannelSpecification channelSpecification) throws FilterDesignException + { + super(producerSourceEventListener, tunerChannel); + + int desiredDecimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate()); + int decimation = DecimationFilterFactory.getDecimationRate(desiredDecimation); + + mIDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimation); + mQDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimation); + + mBufferDispatcher = new Dispatcher(BUFFER_MAX_CAPACITY, "sdrtrunk heterodyne channel " + + tunerChannel.getFrequency(), new NativeBufferPoisonPill()); + mBufferDispatcher.setListener(new NativeBufferProcessor()); + + //Setup the frequency mixer to the current source frequency + mChannelSampleRate = sampleRate / (double)decimation; + mTunerFrequency = tunerChannel.getFrequency(); + long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency(); + mFrequencyCorrectionMixer = ComplexMixerFactory.getMixer(frequencyOffset, sampleRate); + } + + @Override + public void start() + { + super.start(); + mBufferDispatcher.start(); + } + + @Override + public void stop() + { + super.stop(); + mBufferDispatcher.stop(); + } + + @Override + public void dispose() + { + } + + /** + * Primary interface for receiving incoming complex sample buffers to be frequency translated and decimated. + */ + @Override + public void receive(T complexSamples) + { + mBufferDispatcher.receive(complexSamples); + } + + /** + * Sets/updates the center frequency for the sample streaming being sent from the producer. + * + * @param frequency in hertz + */ + @Override + public void setFrequency(long frequency) + { + mTunerFrequency = frequency; + updateMixerFrequencyOffset(); + } + + /** + * Current frequency correction being applied to the channel sample stream + * + * @return correction in hertz + */ + public long getFrequencyCorrection() + { + return mChannelFrequencyCorrection; + } + + /** + * Changes the frequency correction value and broadcasts the change to the registered downstream listener. + * + * @param correction current frequency correction value. + */ + public void setFrequencyCorrection(long correction) + { + mChannelFrequencyCorrection = correction; + updateMixerFrequencyOffset(); + broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection)); + } + + /** + * Calculates the local mixer frequency offset from the tuned frequency, + * channel's requested frequency, and channel frequency correction. + */ + private void updateMixerFrequencyOffset() + { + long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection; + mFrequencyCorrectionMixer.setFrequency(offset); + } + + /** + * Sets the sample rate of the incoming sample stream from the producer + * + * @param sampleRate in hertz + */ + @Override + protected void setSampleRate(double sampleRate) + { + //Not implemented. Sample rate changes are not permitted once sample stream starts + } + + /** + * Sets the frequency correction for the outbound sample stream to the consumer + * + * @param correction in hertz + */ + @Override + protected void setChannelFrequencyCorrection(long correction) + { + mChannelFrequencyCorrection = correction; + broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction)); + updateMixerFrequencyOffset(); + } + + /** + * Frequency correction value currently being applied to the outbound sample stream + */ + @Override + public long getChannelFrequencyCorrection() + { + return mChannelFrequencyCorrection; + } + + /** + * Sets the listener to receive the complex buffer sample output from this channel + * + * @param listener to receive complex buffers + */ + @Override + public void setListener(Listener listener) + { + if(listener == null) + { + //Wait for the native buffer processor thread to die before we nullify the listener + stop(); + } + + mListener = listener; + } + + @Override + public double getSampleRate() + { + return mChannelSampleRate; + } + + /** + * Processes native buffers received from the dispatcher and sends to the registered listener + */ + public class NativeBufferProcessor implements Listener + { + @Override + public void receive(T nativeBuffer) + { + getHeartbeatManager().broadcast(); + + Listener listener = mListener; + + if(listener != null) + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + ComplexSamples basebanded = mFrequencyCorrectionMixer.mix(iterator.next()); + float[] i = mIDecimationFilter.decimateReal(basebanded.i()); + float[] q = mQDecimationFilter.decimateReal(basebanded.q()); + listener.receive(new ComplexSamples(i, q)); + } + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java index e81ac76f..bf84742d 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,14 +14,14 @@ * * 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.channel; import com.google.common.eventbus.Subscribe; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceException; @@ -53,7 +52,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource private List mLockedFrequencies = new ArrayList<>(); private int mFrequencyListPointer = 0; private ChannelSpecification mChannelSpecification; - private Listener mReusableComplexBufferListener; + private Listener mComplexSamplesListener; private Listener mHeartbeatListener; private String mPreferredTuner; private AtomicBoolean mChangingChannels = new AtomicBoolean(); @@ -93,7 +92,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource { //Shutdown the existing tuner channel source mTunerChannelSource.stop(); - mTunerChannelSource.removeListener(mReusableComplexBufferListener); + mTunerChannelSource.setListener(null); mTunerChannelSource.removeSourceEventListener(); mTunerChannelSource.removeHeartbeatListener(mHeartbeatListener); mTunerChannelSource.dispose(); @@ -122,7 +121,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource { mTunerChannelSource = (TunerChannelSource)source; mTunerChannelSource.setSourceEventListener(mConsumerSourceEventAdapter); - mTunerChannelSource.setListener(mReusableComplexBufferListener); + mTunerChannelSource.setListener(mComplexSamplesListener); mTunerChannelSource.addHeartbeatListener(mHeartbeatListener); mTunerChannelSource.start(); mTunerChannel = nextChannel; @@ -295,36 +294,16 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource } @Override - protected void processSamples() + public void setListener(Listener complexSamplesListener) { - if(mTunerChannelSource != null) - { - mTunerChannelSource.processSamples(); - } - } - - @Override - public void setListener(Listener complexBufferListener) - { - mReusableComplexBufferListener = complexBufferListener; + mComplexSamplesListener = complexSamplesListener; if(mTunerChannelSource != null) { - mTunerChannelSource.setListener(complexBufferListener); + mTunerChannelSource.setListener(complexSamplesListener); } } - @Override - public void removeListener(Listener listener) - { - if(mTunerChannelSource != null) - { - mTunerChannelSource.removeListener(listener); - } - - mReusableComplexBufferListener = null; - } - @Override public double getSampleRate() { diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java index ae1c0898..064a2b12 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java @@ -1,46 +1,48 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2019 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.channel; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.tuner.TunerController; +import io.github.dsheirer.util.Dispatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; +import java.util.Iterator; /** * Pass-through channel source that simply passes complex sample buffers from the tuner controller * directly to the registered listener */ public class PassThroughChannelSource extends TunerChannelSource implements ISourceEventListener, - Listener + Listener { private final static Logger mLog = LoggerFactory.getLogger(PassThroughChannelSource.class); private TunerController mTunerController; - private OverflowableReusableBufferTransferQueue mBufferQueue = - new OverflowableReusableBufferTransferQueue<>(500, 100); - private List mBuffersToProcess = new ArrayList<>(); - private Listener mComplexBufferListener; + private Dispatcher mBufferDispatcher; + private Listener mComplexSamplesListener; /** * Constructs an instance @@ -54,6 +56,23 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou { super(listener, tunerChannel); mTunerController = tunerController; + mBufferDispatcher = new Dispatcher<>(500, "sdrtrunk pass-through channel " + + tunerChannel.getFrequency(), new NativeBufferPoisonPill()); + mBufferDispatcher.setListener(new BufferProcessor()); + } + + @Override + public void start() + { + super.start(); + mBufferDispatcher.start(); + } + + @Override + public void stop() + { + super.stop(); + mBufferDispatcher.stop(); } @Override @@ -81,35 +100,9 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou } @Override - public void setListener(Listener complexBufferListener) + public void setListener(Listener complexSamplesListener) { - mComplexBufferListener = complexBufferListener; - } - - @Override - public void removeListener(Listener listener) - { - mComplexBufferListener = null; - } - - @Override - protected void processSamples() - { - mBufferQueue.drainTo(mBuffersToProcess); - - for(ReusableComplexBuffer buffer: mBuffersToProcess) - { - if(mComplexBufferListener != null) - { - mComplexBufferListener.receive(buffer); - } - else - { - buffer.decrementUserCount(); - } - } - - mBuffersToProcess.clear(); + mComplexSamplesListener = complexSamplesListener; } @Override @@ -119,8 +112,27 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer buffer) { - mBufferQueue.offer(reusableComplexBuffer); + mBufferDispatcher.receive(buffer); + } + + public class BufferProcessor implements Listener + { + @Override + public void receive(INativeBuffer nativeBuffer) + { + getHeartbeatManager().broadcast(); + + if(mComplexSamplesListener != null) + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + mComplexSamplesListener.receive(iterator.next()); + } + } + } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java index ad16d3ed..7d8dadae 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java @@ -1,40 +1,33 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.channel; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.ComplexSource; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceEventListenerToProcessorAdapter; import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - public abstract class TunerChannelSource extends ComplexSource implements ISourceEventProcessor { private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class); @@ -43,7 +36,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc protected TunerChannel mTunerChannel; private Listener mProducerSourceEventListener; private Listener mConsumerSourceEventListener; - private ScheduledIntervalProcessor mScheduledIntervalProcessor = new ScheduledIntervalProcessor(); +// private ScheduledIntervalProcessor mScheduledIntervalProcessor = new ScheduledIntervalProcessor(); /** * Tuner Channel Source is a Digital Drop Channel (DDC) abstract class that defines the minimum functionality @@ -99,15 +92,15 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc /** * Sets the listener to receive the complex buffer sample output from this channel - * @param complexBufferListener to receive complex buffers + * @param complexSamplesListener to receive complex buffers */ - public abstract void setListener(Listener complexBufferListener); + public abstract void setListener(Listener complexSamplesListener); /** * Commands sub-class to process queued samples and distribute them to the consumer. This method will be invoked * by an interval timer. */ - protected abstract void processSamples(); +// protected abstract void processSamples(); /** * Tuner channel for this tuner channel source @@ -125,7 +118,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc //Broadcast current frequency and sample rate so consumer can configure correctly broadcastConsumerSourceEvent(SourceEvent.frequencyChange(this, getFrequency(), "Startup")); broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this)); - mScheduledIntervalProcessor.start(); } /** @@ -136,7 +128,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc { broadcastProducerSourceEvent(SourceEvent.stopSampleStreamRequest(this)); broadcastProducerSourceEvent(SourceEvent.sourceDisposeRequest(this)); - mScheduledIntervalProcessor.stop(); } @Override @@ -261,94 +252,94 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc mConsumerSourceEventListener = null; } - /** - * Processor to periodically invoke buffer sample processing on an interval timer. At each interval this processor - * sends a heartbeat to the registered consumer and then commands the sub-class implementation to process any - * queued buffers and distribute complex buffer sample(s) to the registered consumer. - */ - public class ScheduledIntervalProcessor implements Runnable - { - private ScheduledFuture mScheduledFuture; - private boolean mStopped = false; - - /** - * Commands this processor to do a shutdown at the end of this or the next iteration. Once successfully - * shutdown, it will invoke the performDisposal() method to cleanup this instance. - */ - public void stop() - { - mStopped = true; - } - - /** - * Starts buffer processing on a fixed interval using a scheduled thread pool - */ - public void start() - { - if(mScheduledFuture == null) - { - mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this, 0, - BUFFER_PROCESSOR_RUN_INTERVAL_MILLISECONDS, TimeUnit.MILLISECONDS); - } - } - - /** - * Implementation of the Runnable interface to periodically send a heartbeat and then process buffer samples. - */ - @Override - public void run() - { - try - { - if(!mStopped) - { - try - { - getHeartbeatManager().broadcast(); - } - catch(Throwable t) - { - mLog.error("Error while sending heartbeat", t); - } - } - - if(!mStopped) - { - try - { - processSamples(); - } - catch(Throwable t) - { - mLog.error("Error while processing samples", t); - } - } - - if(mStopped) - { - if(mScheduledFuture != null) - { - //Set may-interrupt to false so that we can complete this iteration - mScheduledFuture.cancel(false); - } - - mScheduledFuture = null; - - try - { - getHeartbeatManager().broadcast(); - performDisposal(); - } - catch(Throwable t) - { - mLog.error("Error during final shutdown processing of samples", t); - } - } - } - catch(Throwable t) - { - mLog.error("Error during heartbeat processing", t); - } - } - } +// /** +// * Processor to periodically invoke buffer sample processing on an interval timer. At each interval this processor +// * sends a heartbeat to the registered consumer and then commands the sub-class implementation to process any +// * queued buffers and distribute complex buffer sample(s) to the registered consumer. +// */ +// public class ScheduledIntervalProcessor implements Runnable +// { +// private ScheduledFuture mScheduledFuture; +// private boolean mStopped = false; +// +// /** +// * Commands this processor to do a shutdown at the end of this or the next iteration. Once successfully +// * shutdown, it will invoke the performDisposal() method to cleanup this instance. +// */ +// public void stop() +// { +// mStopped = true; +// } +// +// /** +// * Starts buffer processing on a fixed interval using a scheduled thread pool +// */ +// public void start() +// { +// if(mScheduledFuture == null) +// { +// mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this, 0, +// BUFFER_PROCESSOR_RUN_INTERVAL_MILLISECONDS, TimeUnit.MILLISECONDS); +// } +// } +// +// /** +// * Implementation of the Runnable interface to periodically send a heartbeat and then process buffer samples. +// */ +// @Override +// public void run() +// { +// try +// { +// if(!mStopped) +// { +// try +// { +// getHeartbeatManager().broadcast(); +// } +// catch(Throwable t) +// { +// mLog.error("Error while sending heartbeat", t); +// } +// } +// +// if(!mStopped) +// { +// try +// { +// processSamples(); +// } +// catch(Throwable t) +// { +// mLog.error("Error while processing samples", t); +// } +// } +// +// if(mStopped) +// { +// if(mScheduledFuture != null) +// { +// //Set may-interrupt to false so that we can complete this iteration +// mScheduledFuture.cancel(false); +// } +// +// mScheduledFuture = null; +// +// try +// { +// getHeartbeatManager().broadcast(); +// performDisposal(); +// } +// catch(Throwable t) +// { +// mLog.error("Error during final shutdown processing of samples", t); +// } +// } +// } +// catch(Throwable t) +// { +// mLog.error("Error during heartbeat processing", t); +// } +// } +// } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java index 9b469276..2a1612e3 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,13 +14,13 @@ * * 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.fcd; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.adapter.ComplexShortAdapter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.mixer.ComplexMixer; import io.github.dsheirer.source.tuner.MixerTunerType; @@ -90,11 +90,11 @@ public abstract class FCDTunerController extends TunerController } mComplexMixer = new ComplexMixer( mixerTDL, tunerType.getAudioFormat(), tunerType.getDisplayString(), - new ComplexShortAdapter(tunerType.getDisplayString())); + new ComplexShortAdapter()); mComplexMixer.setBufferSize(tunerType.getAudioFormat().getFrameSize() * getBufferSampleCount()); - mComplexMixer.setBufferListener(mReusableBufferBroadcaster); + mComplexMixer.setBufferListener(mNativeBufferBroadcaster); } @Override @@ -107,7 +107,7 @@ public abstract class FCDTunerController extends TunerController * Overrides the super class functionality to auto-start the complex mixer and provide samples to listeners */ @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { boolean hasExistingListeners = hasBufferListeners(); @@ -124,7 +124,7 @@ public abstract class FCDTunerController extends TunerController * more registered listeners */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { super.removeBufferListener(listener); diff --git a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java index 43cd895c..644550f5 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java @@ -1,32 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.hackrf; +import io.github.dsheirer.buffer.SignedByteNativeBufferFactory; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; import io.github.dsheirer.source.tuner.usb.USBTunerController; -import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; -import io.github.dsheirer.source.tuner.usb.converter.SignedByteSampleConverter; import org.apache.commons.io.EndianUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +57,7 @@ public class HackRFTunerController extends USBTunerController public static final double USABLE_BANDWIDTH_PERCENT = 0.90; public static final int DC_SPIKE_AVOID_BUFFER = 5000; - private NativeBufferConverter mNativeBufferConverter = new SignedByteSampleConverter(); + private SignedByteNativeBufferFactory mNativeBufferFactory = new SignedByteNativeBufferFactory(); private USBTransferProcessor mUSBTransferProcessor; private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE_5_0; @@ -131,7 +127,7 @@ public class HackRFTunerController extends USBTunerController name = "HackRF - Unidentified Serial"; } - mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mNativeBufferConverter, + mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mNativeBufferFactory, USB_TRANSFER_BUFFER_SIZE, this); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java index e41983d8..070c58fb 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java @@ -1,34 +1,31 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.manager; +import io.github.dsheirer.buffer.NativeSampleDelayBuffer; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexDelayBuffer; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.TunerController; -import io.github.dsheirer.source.tuner.channel.CICTunerChannelSource; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; +import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import org.slf4j.Logger; @@ -48,11 +45,12 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager private final static int DELAY_BUFFER_DURATION_MILLISECONDS = 2000; - private List mChannelSources = new CopyOnWriteArrayList<>(); +// private List mChannelSources = new CopyOnWriteArrayList<>(); + private List mChannelSources = new CopyOnWriteArrayList<>(); private SortedSet mTunerChannels = new TreeSet<>(); private TunerController mTunerController; private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor(); - private ReusableComplexDelayBuffer mSampleDelayBuffer; + private NativeSampleDelayBuffer mSampleDelayBuffer; public HeterodyneChannelSourceManager(TunerController tunerController) { @@ -80,8 +78,11 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager try { //Attempt to create the channel source first, in case we get a filter design exception - CICTunerChannelSource tunerChannelSource = new CICTunerChannelSource(mChannelSourceEventProcessor, - tunerChannel, mTunerController.getSampleRate(), channelSpecification); +// CICTunerChannelSource tunerChannelSource = new CICTunerChannelSource(mChannelSourceEventProcessor, +// tunerChannel, mTunerController.getSampleRate(), channelSpecification); + + HalfBandTunerChannelSource tunerChannelSource = new HalfBandTunerChannelSource(mChannelSourceEventProcessor, + tunerChannel, mTunerController.getSampleRate(), channelSpecification); //Add to the list of channel sources so that it will receive the tuner frequency change mChannelSources.add(tunerChannelSource); @@ -181,7 +182,8 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager */ private void broadcastToChannels(SourceEvent sourceEvent) { - for(CICTunerChannelSource channelSource : mChannelSources) +// for(CICTunerChannelSource channelSource : mChannelSources) + for(HalfBandTunerChannelSource channelSource : mChannelSources) { try { @@ -201,7 +203,8 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager */ private void updateTunerFrequency(long tunerFrequency) { - for(CICTunerChannelSource channelSource : mChannelSources) +// for(CICTunerChannelSource channelSource : mChannelSources) + for(HalfBandTunerChannelSource channelSource : mChannelSources) { channelSource.setFrequency(tunerFrequency); } @@ -216,19 +219,13 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager if(mSampleDelayBuffer == null) { int delayBufferSize = (int)(DELAY_BUFFER_DURATION_MILLISECONDS / mTunerController.getBufferDuration()); - mSampleDelayBuffer = new ReusableComplexDelayBuffer(delayBufferSize, mTunerController.getBufferDuration()); - - mLog.debug("Created/registered complex sample delay buffer of size [" + delayBufferSize + - "] buffers and delay duration [" + DELAY_BUFFER_DURATION_MILLISECONDS + - "ms] for tuner controller [" + mTunerController.getClass().getName() + - "] with buffer duration [" + mTunerController.getBufferDuration() + "]"); - + mSampleDelayBuffer = new NativeSampleDelayBuffer(delayBufferSize, mTunerController.getBufferDuration()); mTunerController.addBufferListener(mSampleDelayBuffer); } } /** - * Deregisters the complex sample delay buffer and disposes of any queued reusable buffers. + * De-registers the complex sample delay buffer and disposes of any queued reusable buffers. */ private void stopDelayBuffer() { @@ -251,28 +248,32 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager switch(sourceEvent.getEvent()) { case REQUEST_START_SAMPLE_STREAM: - if(sourceEvent.getSource() instanceof CICTunerChannelSource) + if(sourceEvent.getSource() instanceof HalfBandTunerChannelSource) { startDelayBuffer(); //The start sample stream request contains a start timestamp and the delay buffer //will preload the channel with delayed sample buffers that either contain the //timestamp or occur later/newer than the timestamp. - mSampleDelayBuffer.addListener((CICTunerChannelSource)sourceEvent.getSource(), - sourceEvent.getValue().longValue()); +// mSampleDelayBuffer.addListener((CICTunerChannelSource)sourceEvent.getSource(), +// sourceEvent.getValue().longValue()); + mSampleDelayBuffer.addListener((HalfBandTunerChannelSource)sourceEvent.getSource(), + sourceEvent.getValue().longValue()); } break; case REQUEST_STOP_SAMPLE_STREAM: - if(sourceEvent.getSource() instanceof CICTunerChannelSource) + if(sourceEvent.getSource() instanceof HalfBandTunerChannelSource) { - mSampleDelayBuffer.removeListener((CICTunerChannelSource)sourceEvent.getSource()); +// mSampleDelayBuffer.removeListener((CICTunerChannelSource)sourceEvent.getSource()); + mSampleDelayBuffer.removeListener((HalfBandTunerChannelSource)sourceEvent.getSource()); stopDelayBuffer(); } break; case REQUEST_SOURCE_DISPOSE: - if(sourceEvent.getSource() instanceof CICTunerChannelSource) + if(sourceEvent.getSource() instanceof HalfBandTunerChannelSource) { - CICTunerChannelSource channelSource = (CICTunerChannelSource)sourceEvent.getSource(); +// CICTunerChannelSource channelSource = (CICTunerChannelSource)sourceEvent.getSource(); + HalfBandTunerChannelSource channelSource = (HalfBandTunerChannelSource) sourceEvent.getSource(); mChannelSources.remove(channelSource); mTunerChannels.remove(channelSource.getTunerChannel()); channelSource.dispose(); diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java index 151f88ca..d769a02e 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.manager; @@ -405,6 +402,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager * Allocates a tuner channel source for the tuner channel. * * @param tunerChannel for requested source + * @param channelSpecification for the requested channel * @return allocated DDC tuner channel source, or null if the channel cannot be provided by this source manager */ @Override @@ -434,7 +432,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager } //If we're successful to here, allocate the channel - return mPolyphaseChannelManager.getChannel(tunerChannel, channelSpecification); + return mPolyphaseChannelManager.getChannel(tunerChannel); } catch(SourceException se) { diff --git a/src/main/java/io/github/dsheirer/source/tuner/recording/RecordingTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/recording/RecordingTunerController.java index 3528a4a4..77a846ba 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/recording/RecordingTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/recording/RecordingTunerController.java @@ -1,28 +1,25 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.recording; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.TunerController; @@ -73,15 +70,7 @@ public class RecordingTunerController extends TunerController } mComplexWaveSource = new ComplexWaveSource(new File(recordingPath), true); - mComplexWaveSource.setListener(new Listener() - { - @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) - { - //Send to parent class to broadcast to listeners - broadcast(reusableComplexBuffer); - } - }); + mComplexWaveSource.setListener(complexSamples -> broadcast(complexSamples)); try { @@ -135,7 +124,7 @@ public class RecordingTunerController extends TunerController } @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { super.addBufferListener(listener); @@ -150,13 +139,13 @@ public class RecordingTunerController extends TunerController } @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { super.removeBufferListener(listener); - if(!mReusableBufferBroadcaster.hasListeners() && mComplexWaveSource != null) + if(!mNativeBufferBroadcaster.hasListeners() && mComplexWaveSource != null) { - mComplexWaveSource.setListener((Listener)null); + mComplexWaveSource.setListener((Listener)null); mComplexWaveSource.stop(); mRunning = false; } diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java index a2bc3ef5..b39d1d95 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java @@ -1,36 +1,32 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.rtl; +import io.github.dsheirer.buffer.ByteNativeBufferFactory; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.ITunerErrorListener; import io.github.dsheirer.source.tuner.TunerManager; import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; import io.github.dsheirer.source.tuner.usb.USBTunerController; -import io.github.dsheirer.source.tuner.usb.converter.ByteSampleConverter; -import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.usb4java.Device; @@ -79,7 +75,7 @@ public abstract class RTL2832TunerController extends USBTunerController private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE; - protected NativeBufferConverter mNativeBufferConverter = new ByteSampleConverter(); + protected ByteNativeBufferFactory mNativeBufferFactory = new ByteNativeBufferFactory(); protected int mOscillatorFrequency = 28800000; //28.8 MHz protected USBTransferProcessor mUSBTransferProcessor; protected Descriptor mDescriptor; @@ -162,7 +158,7 @@ public abstract class RTL2832TunerController extends USBTunerController String deviceName = getTunerType().getLabel() + " " + getUniqueID(); - mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferConverter, + mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferFactory, USB_TRANSFER_BUFFER_SIZE_HIGH_SAMPLE_RATE, this); } @@ -1526,7 +1522,7 @@ public abstract class RTL2832TunerController extends USBTunerController * Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started. */ @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { boolean hasExistingListeners = hasBufferListeners(); @@ -1542,7 +1538,7 @@ public abstract class RTL2832TunerController extends USBTunerController * Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners. */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { super.removeBufferListener(listener); @@ -1564,15 +1560,15 @@ public abstract class RTL2832TunerController extends USBTunerController * * @param deviceName to use when logging information or errors * @param deviceHandle to the USB bulk transfer device - * @param nativeBufferConverter specific to the tuner's byte buffer format for converting to floating point I/Q samples + * @param nativeBufferFactory specific to the tuner's byte buffer format for converting to floating point I/Q samples * @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144. * @param tunerErrorListener to receive error state message */ public RTL2832USBTransferProcessor(String deviceName, DeviceHandle deviceHandle, - NativeBufferConverter nativeBufferConverter, int bufferSize, + ByteNativeBufferFactory nativeBufferFactory, int bufferSize, ITunerErrorListener tunerErrorListener) { - super(deviceName, deviceHandle, nativeBufferConverter, bufferSize, tunerErrorListener); + super(deviceName, deviceHandle, nativeBufferFactory, bufferSize, tunerErrorListener); } @Override diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java index bc1ad6f7..305ed705 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.rtl.e4k; @@ -256,7 +253,7 @@ public class E4KTunerController extends RTL2832TunerController String deviceName = getTunerType().getLabel() + " " + getUniqueID(); - mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferConverter, + mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferFactory, USB_TRANSFER_BUFFER_SIZE_HIGH_SAMPLE_RATE, this); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java index 0e5cdab6..8914c7fa 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java @@ -1,26 +1,29 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.test; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.dsp.oscillator.IComplexOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; import io.github.dsheirer.util.ThreadPool; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; @@ -33,12 +36,11 @@ public class SampleGenerator { private final static Logger mLog = LoggerFactory.getLogger(SampleGenerator.class); - private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster(); - private IOscillator mOscillator; + private Broadcaster mNativeBufferBroadcaster = new Broadcaster<>(); + private IComplexOscillator mComplexOscillator; private int mSweepUpdateInterval; private long mInterval; - private int mSamplesPerInterval; - private boolean mReuseBuffers; + private int mSamplesPerInterval = 65536; private ScheduledFuture mScheduledFuture; /** @@ -48,22 +50,20 @@ public class SampleGenerator * * @param sampleRate of the complex samples * @param frequency of the tone produced by the generator - * @param interval in milliseconds for generating samples * @param sweepUpdateRate to turn on sweeping of the tone frequency where the frequency is incremented by the * rate specified at each interval, resetting to 1 Hz after reaching/exceeding the highest frequency. */ - public SampleGenerator(int sampleRate, long frequency, long interval, int sweepUpdateRate) + public SampleGenerator(int sampleRate, long frequency, int sweepUpdateRate) { if(FastMath.abs(sweepUpdateRate) >= sampleRate) { throw new IllegalArgumentException("Sweep update rate cannot be greater than sample rate"); } - mOscillator = new LowPhaseNoiseOscillator(frequency, sampleRate); - mInterval = interval; - mSweepUpdateInterval = sweepUpdateRate; + mComplexOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate); - updateSamplesPerInterval(); + mInterval = 1000 / (sampleRate / mSamplesPerInterval); + mSweepUpdateInterval = sweepUpdateRate; } /** @@ -73,19 +73,10 @@ public class SampleGenerator * * @param sampleRate of the complex samples * @param frequency of the tone produced by the generator - * @param interval in milliseconds for generating samples */ - public SampleGenerator(int sampleRate, long frequency, long interval) + public SampleGenerator(int sampleRate, long frequency) { - this(sampleRate, frequency, interval, 0); - } - - /** - * Updates the number of complex samples generated per interval - */ - private void updateSamplesPerInterval() - { - mSamplesPerInterval = (int)((double)mOscillator.getSampleRate() * ((double)mInterval / 1000.0)); + this(sampleRate, frequency, 0); } /** @@ -125,11 +116,11 @@ public class SampleGenerator * * @param listener to receive complex sample buffers */ - public void addListener(Listener listener) + public void addListener(Listener listener) { - mComplexBufferBroadcaster.addListener(listener); + mNativeBufferBroadcaster.addListener(listener); - if(mComplexBufferBroadcaster.getListenerCount() == 1) + if(mNativeBufferBroadcaster.getListenerCount() == 1) { start(); } @@ -138,11 +129,11 @@ public class SampleGenerator /** * Removes the listener and stops the sample generator if there are no more listeners. */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { - mComplexBufferBroadcaster.removeListener(listener); + mNativeBufferBroadcaster.removeListener(listener); - if(mComplexBufferBroadcaster.getListenerCount() == 0) + if(mNativeBufferBroadcaster.getListenerCount() == 0) { stop(); } @@ -154,7 +145,7 @@ public class SampleGenerator */ public void setFrequency(long frequency) { - mOscillator.setFrequency(frequency); + mComplexOscillator.setFrequency(frequency); } /** @@ -162,7 +153,7 @@ public class SampleGenerator */ public long getFrequency() { - return (long)mOscillator.getFrequency(); + return (long) mComplexOscillator.getFrequency(); } /** @@ -171,13 +162,13 @@ public class SampleGenerator */ public void setSampleRate(int sampleRate) { - mOscillator.setSampleRate(sampleRate); - updateSamplesPerInterval(); + mComplexOscillator.setSampleRate(sampleRate); + mInterval = 1000 / (sampleRate / mSamplesPerInterval); } public double getSampleRate() { - return mOscillator.getSampleRate(); + return mComplexOscillator.getSampleRate(); } /** @@ -186,44 +177,52 @@ public class SampleGenerator public class Generator implements Runnable { private int mTriggerInterval = 0; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("SampleGenerator"); @Override public void run() { - if(mComplexBufferBroadcaster.hasListeners()) + try { - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(mSamplesPerInterval); - - mOscillator.generateComplex(reusableComplexBuffer); - - mComplexBufferBroadcaster.broadcast(reusableComplexBuffer); - - if(mSweepUpdateInterval != 0) + if(mNativeBufferBroadcaster.hasListeners()) { - mTriggerInterval++; + float[] samples = mComplexOscillator.generate(mSamplesPerInterval); - if(mTriggerInterval >= 10) + long now = System.currentTimeMillis(); + + FloatNativeBuffer buffer = new FloatNativeBuffer(samples, now); + + mNativeBufferBroadcaster.broadcast(buffer); + + if(mSweepUpdateInterval != 0) { - mTriggerInterval = 0; + mTriggerInterval++; - long updatedFrequency = (long)mOscillator.getFrequency() + mSweepUpdateInterval; + if(mTriggerInterval >= 10) + { + mTriggerInterval = 0; - if(updatedFrequency > mOscillator.getSampleRate() / 2) - { - mOscillator.setFrequency(mOscillator.getSampleRate() / -2); - } - else if(updatedFrequency < mOscillator.getSampleRate() / -2) - { - mOscillator.setFrequency(mOscillator.getSampleRate() / 2); - } - else - { - mOscillator.setFrequency(updatedFrequency); + long updatedFrequency = (long) mComplexOscillator.getFrequency() + mSweepUpdateInterval; + + if(updatedFrequency > mComplexOscillator.getSampleRate() / 2) + { + mComplexOscillator.setFrequency(mComplexOscillator.getSampleRate() / -2); + } + else if(updatedFrequency < mComplexOscillator.getSampleRate() / -2) + { + mComplexOscillator.setFrequency(mComplexOscillator.getSampleRate() / 2); + } + else + { + mComplexOscillator.setFrequency(updatedFrequency); + } } } } } + catch(Throwable t) + { + t.printStackTrace(); + } } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java index 00876662..a3cd4da4 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,12 +14,12 @@ * * 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.test; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.TunerController; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; @@ -32,7 +32,7 @@ public class TestTunerController extends TunerController public static final long MINIMUM_FREQUENCY = 1l; public static final long MAXIMUM_FREQUENCY = 1000000000l; - public static final int SAMPLE_RATE = 10000000; + public static final int SAMPLE_RATE = 2_400_000; public static final int DC_NOISE_BANDWIDTH = 0; public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00; @@ -50,9 +50,9 @@ public class TestTunerController extends TunerController super(MINIMUM_FREQUENCY, MAXIMUM_FREQUENCY, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH_PERCENTAGE); int sweepRate = 0; //Hz per interval - long initialToneFrequency = SAMPLE_RATE / 2 + 100; - mSampleGenerator = new SampleGenerator(SAMPLE_RATE, initialToneFrequency, SAMPLE_GENERATION_INTERVAL, - sweepRate); +// long initialToneFrequency = SAMPLE_RATE / 2 + 100; + long initialToneFrequency = 50000; + mSampleGenerator = new SampleGenerator(SAMPLE_RATE, initialToneFrequency, sweepRate); try { @@ -78,13 +78,13 @@ public class TestTunerController extends TunerController } @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { mSampleGenerator.addListener(listener); } @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mSampleGenerator.removeListener(listener); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java index 42ac28e5..067e15d4 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.usb; @@ -86,18 +83,8 @@ public class USBMasterProcessor { mProcessor.reset(); mProcessorThread = new Thread(mProcessor); - mProcessorThread.setName("USB Event Processor"); - mProcessorThread.setDaemon(true); - - try - { - mProcessorThread.setPriority(Thread.MAX_PRIORITY); - } - catch(IllegalArgumentException iae) - { - //Do nothing ... couldn't set the priority - } - + mProcessorThread.setName("sdrtrunk USB event processor"); + mProcessorThread.setPriority(Thread.MAX_PRIORITY); mProcessorThread.start(); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java index 54d5d7be..972ab226 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java @@ -1,31 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.source.tuner.usb; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.INativeBufferFactory; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.tuner.ITunerErrorListener; import io.github.dsheirer.source.tuner.TunerManager; -import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,13 +56,13 @@ public class USBTransferProcessor implements TransferCallback private List mTransfersToDispose = new ArrayList<>(); private List mTransfersToSubmit = new ArrayList<>(); - //Tuner format-specific byte buffer to IQ float sample converter - private NativeBufferConverter mNativeBufferConverter; + //Tuner specific native buffer factory for processing/conversion of raw samples + private INativeBufferFactory mNativeBufferFactory; //Byte array transfer buffers size in bytes private int mBufferSize; - private Listener mComplexBufferListener; + private Listener mNativeBufferListener; //Handle to the USB bulk transfer device private DeviceHandle mUsbBulkTransferDeviceHandle; @@ -84,16 +81,16 @@ public class USBTransferProcessor implements TransferCallback * * @param deviceName to use when logging information or errors * @param usbBulkTransferDeviceHandle to the USB bulk transfer device - * @param nativeBufferConverter specific to the tuner's byte buffer format for converting to floating point I/Q samples + * @param nativeBufferFactory specific to the tuner's byte buffer format for converting to floating point I/Q samples * @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144. */ public USBTransferProcessor(String deviceName, DeviceHandle usbBulkTransferDeviceHandle, - NativeBufferConverter nativeBufferConverter, int bufferSize, + INativeBufferFactory nativeBufferFactory, int bufferSize, ITunerErrorListener tunerErrorListener) { mDeviceName = deviceName; mUsbBulkTransferDeviceHandle = usbBulkTransferDeviceHandle; - mNativeBufferConverter = nativeBufferConverter; + mNativeBufferFactory = nativeBufferFactory; mBufferSize = bufferSize; mITunerErrorListener = tunerErrorListener; } @@ -358,11 +355,11 @@ public class USBTransferProcessor implements TransferCallback /** * Sets the listener and auto-starts the buffer processor */ - public void setListener(Listener listener) + public void setListener(Listener listener) { - if(mComplexBufferListener == null || !mComplexBufferListener.equals(listener)) + if(mNativeBufferListener == null || !mNativeBufferListener.equals(listener)) { - mComplexBufferListener = listener; + mNativeBufferListener = listener; boolean success = start(); @@ -378,10 +375,10 @@ public class USBTransferProcessor implements TransferCallback */ public void removeListener() { - if(mComplexBufferListener != null) + if(mNativeBufferListener != null) { stop(); - mComplexBufferListener = null; + mNativeBufferListener = null; } } @@ -555,14 +552,13 @@ public class USBTransferProcessor implements TransferCallback { if(mRunning.get()) { - ByteBuffer nativeBuffer = transfer.buffer(); - - ReusableComplexBuffer reusableComplexBuffer = - mNativeBufferConverter.convert(nativeBuffer, transfer.actualLength()); - - if(mComplexBufferListener != null) + if(mNativeBufferListener != null) { - mComplexBufferListener.receive(reusableComplexBuffer); + //Pass the transfer's byte buffer so the native buffer factory can make a copy of the + //byte array contents and package it as a native buffer. + INativeBuffer nativeBuffer = mNativeBufferFactory.getBuffer(transfer.buffer(), + System.currentTimeMillis()); + mNativeBufferListener.receive(nativeBuffer); } } @@ -577,7 +573,7 @@ public class USBTransferProcessor implements TransferCallback } else { - ThreadPool.SCHEDULED.submit(() -> restart()); + ThreadPool.CACHED.submit(() -> restart()); transfer = null; } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java index 185f901a..ccf2e8af 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,12 +14,12 @@ * * 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.usb; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.tuner.TunerController; public abstract class USBTunerController extends TunerController @@ -44,7 +44,7 @@ public abstract class USBTunerController extends TunerController * Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started. */ @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { boolean hasExistingListeners = hasBufferListeners(); @@ -60,7 +60,7 @@ public abstract class USBTunerController extends TunerController * Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners. */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { super.removeBufferListener(listener); diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java index eed4e47f..61c8b565 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java @@ -1,30 +1,30 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.usb.converter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; import java.nio.ByteBuffer; import java.nio.FloatBuffer; public abstract class NativeBufferConverter { - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("NativeBufferConverter"); - /** * Converts native byte buffers into complex float samples and produces reusable complex sample buffers. Tracks * each reusable buffer until the downstream consumer(s) are finished with the buffer and then reuses the buffer. @@ -34,23 +34,20 @@ public abstract class NativeBufferConverter } /** - * Converts the native byte buffer to complex float samples and returns the samples in a reusable buffer. Internally - * tracks each reusable buffer until all consumers indicate they are finished processing the buffer and then reuses - * the buffer. + * Converts the native byte buffer to complex float samples. * * @param byteBuffer containing native memory byte samples - * @return native buffer samples converted to complex floats loaded into a reusable buffer + * @return native buffer samples converted to interleaved complex floats */ - public ReusableComplexBuffer convert(ByteBuffer byteBuffer, int length) + public InterleavedComplexSamples convert(ByteBuffer byteBuffer, int length) { FloatBuffer floatBuffer = convertSamples(byteBuffer, length); - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(floatBuffer.capacity()); + float[] samples = new float[floatBuffer.capacity()]; + floatBuffer.rewind(); + floatBuffer.get(samples); - //The reusable buffer will rewind the float buffer to the beginning - reusableComplexBuffer.reloadFrom(floatBuffer, System.currentTimeMillis()); - - return reusableComplexBuffer; + return new InterleavedComplexSamples(samples, System.currentTimeMillis()); } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeSampleBuffer.java b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeSampleBuffer.java new file mode 100644 index 00000000..567d61d4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeSampleBuffer.java @@ -0,0 +1,51 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.source.tuner.usb.converter; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.util.List; + +/** + * Base class for native samples byte buffer. This class holds a copy of the byte array of samples + * that was copied from a native byte buffer into the JVM heap space. + * + * Consumers of this sample buffer must make a copy of the contents in either interleaved or non-interleaved + * sample array buffers. + */ +public abstract class NativeSampleBuffer +{ + private byte[] mSamples; + + public NativeSampleBuffer(byte[] samples) + { + mSamples = samples; + } + + protected byte[] getSamples() + { + return mSamples; + } + + public abstract List convert(); + + public abstract List convertInterleaved(); +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/SignedByteSampleConverter.java b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/SignedByteSampleConverter.java index 90c94006..5096aa46 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/SignedByteSampleConverter.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/SignedByteSampleConverter.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.usb.converter; import java.nio.ByteBuffer; @@ -77,4 +80,10 @@ public class SignedByteSampleConverter extends NativeBufferConverter return mFloatBuffer; } + + public static void main(String[] args) + { + SignedByteSampleConverter converter = new SignedByteSampleConverter(); + int a = 0; + } } 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 0cefd028..e089d192 100644 --- a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java +++ b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java @@ -1,33 +1,31 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.source.wave; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.source.ComplexSource; +import io.github.dsheirer.sample.SampleType; import io.github.dsheirer.source.IControllableFileSource; import io.github.dsheirer.source.IFrameLocationListener; +import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.util.ThreadPool; import org.apache.commons.math3.util.FastMath; @@ -44,7 +42,7 @@ import java.util.Arrays; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -public class ComplexWaveSource extends ComplexSource implements IControllableFileSource, AutoCloseable +public class ComplexWaveSource extends Source implements IControllableFileSource, AutoCloseable { private final static Logger mLog = LoggerFactory.getLogger(ComplexWaveSource.class); @@ -52,10 +50,9 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil private int mBytesPerFrame; private int mFrameCounter = 0; private long mFrequency = 0; - private Listener mListener; + private Listener mListener; private AudioInputStream mInputStream; private File mFile; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ComplexWaveSource"); private boolean mAutoReplay; private ScheduledFuture mReplayController; @@ -85,6 +82,11 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil this(file, false); } + @Override public SampleType getSampleType() + { + return SampleType.COMPLEX; + } + @Override public void setSourceEventListener(Listener listener) { @@ -167,7 +169,6 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil @Override public long getFrameCount() throws IOException { - // TODO Auto-generated method stub return 0; } @@ -275,11 +276,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil } float[] samples = ConversionUtils.convertFromSigned16BitSamples(buffer); - - ReusableComplexBuffer reusableBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); - System.arraycopy(samples, 0, reusableBuffer.getSamples(), 0, samples.length); - reusableBuffer.setTimestamp(System.currentTimeMillis()); - mListener.receive(reusableBuffer); + mListener.receive(new FloatNativeBuffer(samples, System.currentTimeMillis())); } } } @@ -288,8 +285,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil * 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; } @@ -297,7 +293,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil /** * Unregisters the listener from receiving sample buffers */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mListener = null; } 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 3c1f0213..89983f49 100644 --- a/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java +++ b/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java @@ -1,29 +1,29 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.wave; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.SampleType; import io.github.dsheirer.source.IControllableFileSource; import io.github.dsheirer.source.IFrameLocationListener; -import io.github.dsheirer.source.RealSource; +import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,16 +36,14 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; -public class RealWaveSource extends RealSource implements IControllableFileSource, AutoCloseable +public class RealWaveSource extends Source implements IControllableFileSource, AutoCloseable { 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; @@ -54,6 +52,11 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc mFile = file; } + @Override public SampleType getSampleType() + { + return SampleType.REAL; + } + @Override public void setSourceEventListener(Listener listener) { @@ -229,11 +232,7 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc } float[] samples = ConversionUtils.convertFromSigned16BitSamples(buffer); - - ReusableFloatBuffer reusableFloatBuffer = mReusableBufferQueue.getBuffer(samples.length); - reusableFloatBuffer.reloadFrom(samples, System.currentTimeMillis()); - - mListener.receive(reusableFloatBuffer); + mListener.receive(samples); } } } @@ -241,8 +240,7 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc /** * 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; } @@ -250,8 +248,7 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc /** * Unregisters the listener from receiving sample buffers */ - @Override - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mListener = null; } diff --git a/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java b/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java deleted file mode 100644 index c8b8de57..00000000 --- a/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java +++ /dev/null @@ -1,518 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 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.spectrum; - -import io.github.dsheirer.controller.channel.Channel; -import io.github.dsheirer.controller.channel.ChannelEvent; -import io.github.dsheirer.controller.channel.ChannelEventListener; -import io.github.dsheirer.controller.channel.ChannelProcessingManager; -import io.github.dsheirer.dsp.filter.Filters; -import io.github.dsheirer.dsp.filter.Window.WindowType; -import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter; -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.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.settings.ColorSetting.ColorSettingName; -import io.github.dsheirer.settings.ColorSettingMenuItem; -import io.github.dsheirer.settings.Setting; -import io.github.dsheirer.settings.SettingChangeListener; -import io.github.dsheirer.settings.SettingsManager; -import io.github.dsheirer.source.SourceEvent; -import io.github.dsheirer.spectrum.converter.DFTResultsConverter; -import io.github.dsheirer.spectrum.converter.RealDecibelConverter; -import io.github.dsheirer.spectrum.menu.AveragingItem; -import io.github.dsheirer.spectrum.menu.DFTSizeItem; -import io.github.dsheirer.spectrum.menu.FFTWindowTypeItem; -import io.github.dsheirer.spectrum.menu.FrameRateItem; -import io.github.dsheirer.spectrum.menu.SmoothingItem; -import io.github.dsheirer.spectrum.menu.SmoothingTypeItem; -import net.miginfocom.swing.MigLayout; - -import javax.swing.JLayeredPane; -import javax.swing.JMenu; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JSeparator; -import javax.swing.SwingUtilities; -import java.awt.Component; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.util.concurrent.atomic.AtomicBoolean; - -public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener, - Listener, SettingChangeListener, SpectralDisplayAdjuster -{ - private static final long serialVersionUID = 1L; - - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.REAL); - private DFTResultsConverter mDFTConverter = new RealDecibelConverter(); - private JLayeredPane mLayeredPane; - private SpectrumPanel mSpectrumPanel; - private ChannelOverlayPanel mOverlayPanel; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ChannelSpectrumPanel"); - - private Channel mCurrentChannel; - - private int mSampleBufferSize = 2400; - - private HalfBandFilter mDecimatingFilter = new HalfBandFilter( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true); - - private AtomicBoolean mEnabled = new AtomicBoolean(); - - private SettingsManager mSettingsManager; - private ChannelProcessingManager mChannelProcessingManager; - - public ChannelSpectrumPanel(SettingsManager settingsManager, - ChannelProcessingManager channelProcessingManager) - { - mSettingsManager = settingsManager; - mChannelProcessingManager = channelProcessingManager; - - if(mSettingsManager != null) - { - mSettingsManager.addListener(this); - } - - mSpectrumPanel = new SpectrumPanel(mSettingsManager); - mSpectrumPanel.setAveraging(1); - - mOverlayPanel = new ChannelOverlayPanel(mSettingsManager); - mDFTProcessor.addConverter(mDFTConverter); - mDFTConverter.addListener(mSpectrumPanel); - - /* Set the DFTProcessor to the decimated 24kHz sample rate */ - mDFTProcessor.process(SourceEvent.sampleRateChange(24000.0)); - - initGui(); - } - - public void dispose() - { - setEnabled(false); - - mDFTProcessor.dispose(); - - if(mSettingsManager != null) - { - mSettingsManager.removeListener(this); - } - - mSettingsManager = null; - mCurrentChannel = null; - mDFTProcessor = null; - mSpectrumPanel = null; - } - - public void setFrameRate(int framesPerSecond) - { - mSampleBufferSize = (int)(48000 / framesPerSecond); - - mDFTProcessor.setFrameRate(framesPerSecond); - } - - private void initGui() - { - setLayout(new MigLayout("insets 0 0 0 0 ", - "[grow,fill]", - "[grow,fill]")); - - mLayeredPane = new JLayeredPane(); - mLayeredPane.addComponentListener(new ResizeListener()); - - MouseEventProcessor mouser = new MouseEventProcessor(); - - mOverlayPanel.addMouseListener(mouser); - mOverlayPanel.addMouseMotionListener(mouser); - - mLayeredPane.add(mSpectrumPanel, 0, 0); - mLayeredPane.add(mOverlayPanel, 1, 0); - - add(mLayeredPane); - } - - public void setEnabled(boolean enabled) - { - if(enabled && mEnabled.compareAndSet(false, true)) - { - start(); - } - else if(!enabled && mEnabled.compareAndSet(true, false)) - { - stop(); - } - } - - @Override - public void channelChanged(ChannelEvent event) - { - switch(event.getEvent()) - { - case NOTIFICATION_SELECTION_CHANGE: - //ChannelSelectionManager ensures that only 1 channel can be - //selected and any previously selected channel will be first - //deselected before we get a new selection event - if(event.getChannel().isSelected()) - { - if(mCurrentChannel != null) - { - stop(); - mCurrentChannel = null; - } - - mCurrentChannel = event.getChannel(); - - if(mEnabled.get()) - { - start(); - } - } - else - { - stop(); - mCurrentChannel = null; - } - break; - case NOTIFICATION_PROCESSING_STOP: - if(event.getChannel() == mCurrentChannel) - { - if(mEnabled.get()) - { - stop(); - } - - mCurrentChannel = null; - } - break; - } - } - - private void start() - { - if(mEnabled.get() && mCurrentChannel != null && mCurrentChannel.isProcessing()) - { - ProcessingChain processingChain = mChannelProcessingManager - .getProcessingChain(mCurrentChannel); - - if(processingChain != null) - { - processingChain.addDemodulatedAudioListener(this); - mDFTProcessor.start(); - } - } - } - - private void stop() - { - if(mCurrentChannel != null && mCurrentChannel.isProcessing()) - { - ProcessingChain processingChain = mChannelProcessingManager.getProcessingChain(mCurrentChannel); - - if(processingChain != null) - { - processingChain.removeDemodulatedAudioListener(this); - } - } - - mDFTProcessor.stop(); - - mSpectrumPanel.clearSpectrum(); - } - - @Override - public void settingChanged(Setting setting) - { - if(mSpectrumPanel != null) - { - mSpectrumPanel.settingChanged(setting); - } - if(mOverlayPanel != null) - { - mOverlayPanel.settingChanged(setting); - } - } - - @Override - public void settingDeleted(Setting setting) - { - if(mSpectrumPanel != null) - { - mSpectrumPanel.settingDeleted(setting); - } - - if(mOverlayPanel != null) - { - mOverlayPanel.settingDeleted(setting); - } - } - - @Override - public void receive(ReusableFloatBuffer buffer) - { - ReusableFloatBuffer 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()); - mDFTProcessor.receive(reusableComplexBuffer); - } - - /** - * Monitors the sizing of the layered pane and resizes the spectrum and - * channel panels whenever the layered pane is resized - */ - public class ResizeListener implements ComponentListener - { - @Override - public void componentResized(ComponentEvent e) - { - Component c = e.getComponent(); - - mSpectrumPanel.setBounds(0, 0, c.getWidth(), c.getHeight()); - mOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight()); - } - - @Override - public void componentHidden(ComponentEvent arg0) - { - } - - @Override - public void componentMoved(ComponentEvent arg0) - { - } - - @Override - public void componentShown(ComponentEvent arg0) - { - } - } - - /** - * Mouse event handler for the channel panel. - */ - public class MouseEventProcessor implements MouseMotionListener, MouseListener - { - @Override - public void mouseMoved(MouseEvent event) - { - update(event); - } - - @Override - public void mouseDragged(MouseEvent event) - { - update(event); - } - - private void update(MouseEvent event) - { - if(event.getComponent() == mOverlayPanel) - { - mOverlayPanel.setCursorLocation(event.getPoint()); - } - } - - @Override - public void mouseEntered(MouseEvent e) - { - if(e.getComponent() == mOverlayPanel) - { - mOverlayPanel.setCursorVisible(true); - } - } - - @Override - public void mouseExited(MouseEvent e) - { - mOverlayPanel.setCursorVisible(false); - } - - /** - * Displays the context menu. - */ - @Override - public void mouseClicked(MouseEvent event) - { - if(SwingUtilities.isRightMouseButton(event)) - { - JPopupMenu contextMenu = new JPopupMenu(); - - /** - * Color Menus - */ - JMenu colorMenu = new JMenu("Color"); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_CURSOR)); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_LINE)); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_BACKGROUND)); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_GRADIENT_BOTTOM)); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_GRADIENT_TOP)); - - contextMenu.add(colorMenu); - - /** - * Display items: fft and frame rate - */ - JMenu displayMenu = new JMenu("Display"); - contextMenu.add(displayMenu); - - /** - * Averaging menu - */ - JMenu averagingMenu = new JMenu("Averaging"); - averagingMenu.add( - new AveragingItem(ChannelSpectrumPanel.this, 2)); - displayMenu.add(averagingMenu); - - /** - * FFT width - */ - JMenu fftWidthMenu = new JMenu("FFT Width"); - displayMenu.add(fftWidthMenu); - - for(DFTSize width : DFTSize.values()) - { - fftWidthMenu.add(new DFTSizeItem(mDFTProcessor, width)); - } - - /** - * DFT Processor Frame Rate - */ - JMenu frameRateMenu = new JMenu("Frame Rate"); - displayMenu.add(frameRateMenu); - - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 14)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 16)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 18)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 20)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 25)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 30)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 40)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 50)); - - /** - * FFT Window Type - */ - JMenu fftWindowType = new JMenu("Window Type"); - displayMenu.add(fftWindowType); - - for(WindowType type : WindowType.values()) - { - fftWindowType.add( - new FFTWindowTypeItem(mDFTProcessor, type)); - } - - /** - * Smoothing menu - */ - JMenu smoothingMenu = new JMenu("Smoothing"); - - if(mSpectrumPanel.getSmoothingType() != SmoothingType.NONE) - { - smoothingMenu.add(new SmoothingItem(ChannelSpectrumPanel.this, 5)); - smoothingMenu.add(new JSeparator()); - } - - smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.GAUSSIAN)); - smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.TRIANGLE)); - smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.RECTANGLE)); - smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.NONE)); - - displayMenu.add(smoothingMenu); - - if(contextMenu != null) - { - contextMenu.show(mOverlayPanel, - event.getX(), - event.getY()); - } - } - } - - @Override - public void mousePressed(MouseEvent e) - { - } - - @Override - public void mouseReleased(MouseEvent e) - { - } - } - - @Override - public int getAveraging() - { - return mSpectrumPanel.getAveraging(); - } - - @Override - public void setAveraging(int averaging) - { - mSpectrumPanel.setAveraging(averaging); - } - - public void setSampleSize(double sampleSize) - { - mSpectrumPanel.setSampleSize(sampleSize); - } - - @Override - public int getSmoothing() - { - return mSpectrumPanel.getSmoothing(); - } - - @Override - public void setSmoothing(int smoothing) - { - mSpectrumPanel.setSmoothing(smoothing); - } - - @Override - public SmoothingType getSmoothingType() - { - return mSpectrumPanel.getSmoothingType(); - } - - @Override - public void setSmoothingType(SmoothingType type) - { - mSpectrumPanel.setSmoothingType(type); - } -} diff --git a/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java b/src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java similarity index 52% rename from src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java rename to src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java index c1b5bb95..092fa521 100644 --- a/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java +++ b/src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -15,28 +14,25 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.spectrum; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.Window.WindowType; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.controller.NamingThreadFactory; +import io.github.dsheirer.dsp.window.WindowFactory; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.properties.SystemProperties; -import io.github.dsheirer.sample.IOverflowListener; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.source.ISourceEventProcessor; -import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.spectrum.converter.DFTResultsConverter; -import io.github.dsheirer.util.ThreadPool; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -45,37 +41,29 @@ import java.util.concurrent.atomic.AtomicBoolean; * Processes both complex samples or float samples and dispatches a float array of DFT results, using configurable fft * size and output dispatch timelines. */ -public class DFTProcessor implements Listener, ISourceEventProcessor, IDFTWidthChangeProcessor +public class ComplexDftProcessor implements Listener, IDFTWidthChangeProcessor { - private static final Logger mLog = LoggerFactory.getLogger(DFTProcessor.class); - private static final int BUFFER_QUEUE_MAX_SIZE = 20; - private static final int BUFFER_QUEUE_OVERFLOW_RESET_THRESHOLD = 6; + private static final Logger mLog = LoggerFactory.getLogger(ComplexDftProcessor.class); private static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate"; //The Cosine and Hann windows seem to offer the best spectral display with minimal bin leakage/smearing private WindowType mWindowType = WindowType.HANN; - private double[] mWindow; + private float[] mWindow; private DFTSize mDFTSize = DFTSize.FFT04096; private DFTSize mNewDFTSize = DFTSize.FFT04096; private FloatFFT_1D mFFT = new FloatFFT_1D(mDFTSize.getSize()); private int mFrameRate; - private int mSampleRate = 2400000; //Initial high value until we receive update from tuner - private int mFrameSize; - private int mFrameFlushCount; - private int mFrameOverlapCount; - private SampleType mSampleType; private AtomicBoolean mRunning = new AtomicBoolean(); private ScheduledFuture mProcessorTaskHandle; - private CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList(); - private OverflowableBufferStream mOverflowableBufferStream = new OverflowableBufferStream(BUFFER_QUEUE_MAX_SIZE, - BUFFER_QUEUE_OVERFLOW_RESET_THRESHOLD, mDFTSize.getSize()); - private float[] mPreviousSamples; + private ScheduledExecutorService mExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk dft processor")); + private CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList<>(); + private NativeBufferManager mDftBufferManager = new NativeBufferManager(mDFTSize.getSize() * 2); + private float[] mPreviousSamples = new float[mDFTSize.getSize() * 2]; - public DFTProcessor(SampleType sampleType) + public ComplexDftProcessor() { - setSampleType(sampleType); mFrameRate = SystemProperties.getInstance().get(FRAME_RATE_PROPERTY, 20); - calculateConsumptionRate(); + setWindowType(mWindowType); start(); } @@ -84,19 +72,9 @@ public class DFTProcessor implements Listener, ISourceEve stop(); mListeners.clear(); - mOverflowableBufferStream.clear(); mWindow = null; } - /** - * Sets the listener to receive buffer overflow/reset indications - * @param listener - */ - public void setOverflowListener(IOverflowListener listener) - { - mOverflowableBufferStream.setOverflowListener(listener); - } - public WindowType getWindowType() { return mWindowType; @@ -105,30 +83,12 @@ public class DFTProcessor implements Listener, ISourceEve public void setWindowType(WindowType windowType) { mWindowType = windowType; - - if(mSampleType == SampleType.COMPLEX) - { - mWindow = Window.getWindow(mWindowType, mDFTSize.getSize() * 2); - } - else - { - mWindow = Window.getWindow(mWindowType, mDFTSize.getSize()); - } + updateWindow(); } - /** - * Sets the processor mode to Float or Complex, depending on the sample - * types that will be delivered for processing - */ - public void setSampleType(SampleType type) + private void updateWindow() { - mSampleType = type; - setWindowType(mWindowType); - } - - public SampleType getSampleType() - { - return mSampleType; + mWindow = WindowFactory.getWindow(mWindowType, mDFTSize.getSize() * 2); } /** @@ -159,11 +119,7 @@ public class DFTProcessor implements Listener, ISourceEve } mFrameRate = framesPerSecond; - SystemProperties.getInstance().set(FRAME_RATE_PROPERTY, mFrameRate); - - calculateConsumptionRate(); - restart(); } @@ -175,7 +131,7 @@ public class DFTProcessor implements Listener, ISourceEve int initialDelay = 0; int period = (int) (1000 / mFrameRate); - mProcessorTaskHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period, + mProcessorTaskHandle = mExecutorService.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period, TimeUnit.MILLISECONDS); } } @@ -205,9 +161,9 @@ public class DFTProcessor implements Listener, ISourceEve * Places the sample into a transfer queue for future processing. */ @Override - public void receive(ReusableComplexBuffer sampleBuffer) + public void receive(T buffer) { - mOverflowableBufferStream.offer(sampleBuffer); + mDftBufferManager.add(buffer); } private void calculate() @@ -219,31 +175,15 @@ public class DFTProcessor implements Listener, ISourceEve try { - if(mFrameFlushCount > 0) - { - mOverflowableBufferStream.flush(mFrameFlushCount); - } - //If this throws an IO exception, the buffer queue is (temporarily) empty and we return from the method - float[] samples = mOverflowableBufferStream.get(mFrameSize, mFrameOverlapCount); - - Window.apply(mWindow, samples); - - if(mSampleType == SampleType.REAL) - { - mFFT.realForward(samples); - } - else - { - mFFT.complexForward(samples); - } - + float[] samples = mDftBufferManager.get(mDFTSize.getSize()); + WindowFactory.apply(mWindow, samples); + mFFT.complexForward(samples); mPreviousSamples = samples; } catch(IOException ioe) { - //No new data, dispatch the previous samples again - //no-op + //Not enough samples available, dispatch the previous samples again } catch(Exception e) { @@ -264,8 +204,8 @@ public class DFTProcessor implements Listener, ISourceEve */ private void dispatch(float[] results) { - - for (DFTResultsConverter mListener : mListeners) { + for (DFTResultsConverter mListener : mListeners) + { mListener.receive(results); } } @@ -286,9 +226,7 @@ public class DFTProcessor implements Listener, ISourceEve if(mRunning.compareAndSet(false, true)) { checkFFTSize(); - calculate(); - mRunning.set(false); } } @@ -309,98 +247,13 @@ public class DFTProcessor implements Listener, ISourceEve if(mNewDFTSize.getSize() != mDFTSize.getSize()) { mDFTSize = mNewDFTSize; - - calculateConsumptionRate(); - - setWindowType(mWindowType); - + updateWindow(); mFFT = new FloatFFT_1D(mDFTSize.getSize()); } } public void clearBuffer() { - mOverflowableBufferStream.clear(); - } - - @Override - public void process(SourceEvent event) - { - switch(event.getEvent()) - { - case NOTIFICATION_SAMPLE_RATE_CHANGE: - mSampleRate = event.getValue().intValue(); - calculateConsumptionRate(); - break; - default: - break; - } - } - - /** - * Calculates the frame size, flush count and overlap count to use for each calculation cycle. - */ - private void calculateConsumptionRate() - { - int floatsPerSample = mSampleType == SampleType.COMPLEX ? 2 : 1; - - mFrameSize = mDFTSize.getSize() * floatsPerSample; - mPreviousSamples = new float[mFrameSize]; - - int productionRate = mSampleRate * floatsPerSample; - int consumptionRate = mFrameRate * mFrameSize; - int residual = productionRate - consumptionRate; - - if(residual < 0) - { - mFrameFlushCount = 0; - mFrameOverlapCount = -residual / mFrameRate; - - if(mFrameOverlapCount * mFrameRate < -residual) - { - mFrameOverlapCount ++; - } - } - else if(residual > 0) - { - mFrameOverlapCount = 0; - mFrameFlushCount = residual / mFrameRate; - - if(mFrameFlushCount * mFrameRate < residual) - { - mFrameFlushCount++; - } - } - - //Ensure we're flushing or overlapping by even multiples of the sample size - if(mFrameOverlapCount % floatsPerSample != 0) - { - mFrameOverlapCount++; - } - - if(mFrameFlushCount % floatsPerSample != 0) - { - mFrameFlushCount++; - } - - //If the overlap size is greater than the frame size, we can't do that. Automatically decrease the frame rate - //until we reach a legitimate overlap value. - if(mFrameOverlapCount >= mFrameSize) - { - mLog.warn("Unable to provide frame rate [" + mFrameRate + "] for current DFT size [" + mDFTSize.getSize() + - "] - reducing frame rate"); - - mFrameRate--; - - if(mFrameRate <= 0) - { - //Consider reducing the FFT size as a possible alternative corrective measure - throw new IllegalStateException("Unable to determine a viable frame rate based on incoming sample " + - "rate, DFT size and DFT frame rate"); - } - - //Recursively call this method to test the new frame rate - calculateConsumptionRate(); - } + mDftBufferManager.clear(); } } diff --git a/src/main/java/io/github/dsheirer/spectrum/IDFTWidthChangeProcessor.java b/src/main/java/io/github/dsheirer/spectrum/IDFTWidthChangeProcessor.java index 76714e66..5efa52e5 100644 --- a/src/main/java/io/github/dsheirer/spectrum/IDFTWidthChangeProcessor.java +++ b/src/main/java/io/github/dsheirer/spectrum/IDFTWidthChangeProcessor.java @@ -1,7 +1,26 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.spectrum; public interface IDFTWidthChangeProcessor { - public void setDFTSize( DFTSize size ); - public DFTSize getDFTSize(); + void setDFTSize( DFTSize size ); + DFTSize getDFTSize(); } diff --git a/src/main/java/io/github/dsheirer/spectrum/NativeBufferManager.java b/src/main/java/io/github/dsheirer/spectrum/NativeBufferManager.java new file mode 100644 index 00000000..840ed5a3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/spectrum/NativeBufferManager.java @@ -0,0 +1,150 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.spectrum; + +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.LinkedTransferQueue; + +/** + * Thread-safe native buffer manager. Receives incoming native buffers in a non-blocking manner. Received buffers are + * only enqueued if they are needed to meet the next requested sample array size. Once the incoming producer quota + * is reached, incoming buffers are ignored. + * + * Transferred native buffers are enqueued on the consumer side until enough samples are amassed to satisfy a get + * operation. + * + * The queuing on both the producer and consumer side ensures the thread-safe transfer queue never overfills. + * + * The estimated request size argument sets the initial quota of samples to enqueue on the producer size. This value + * is updated on the consumer side with each request. This class is designed for the get() operation to consistently + * request the same size buffer, with occasional changes. + * + * @param native buffer + */ +public class NativeBufferManager +{ + private LinkedTransferQueue mTransferQueue = new LinkedTransferQueue<>(); + private List mProducerQueue = new ArrayList<>(); + private List mConsumerQueue = new ArrayList<>(); + private int mRequestSize; + private int mProducerAvailable; + + /** + * Constructs an instance + * @param estimatedRequestSize initially, for each complex sample retrieval. + */ + public NativeBufferManager(int estimatedRequestSize) + { + mRequestSize = estimatedRequestSize; + } + /** + * Adds the buffer to this manager if it's needed. + * @param nativeBuffer to add + */ + public void add(T nativeBuffer) + { + //If we have enough produced buffers and the transfer queue is empty ... move them to the queue + if(mProducerAvailable >= mRequestSize && mTransferQueue.isEmpty()) + { + mTransferQueue.addAll(mProducerQueue); + mProducerQueue.clear(); + mProducerAvailable = 0; + } + + //Add this buffer to the producer queue if it's needed to meet the anticipated request size + if(mProducerAvailable < mRequestSize) + { + mProducerQueue.add(nativeBuffer); + mProducerAvailable += nativeBuffer.sampleCount(); + } + } + + /** + * Clears/removes all queued native buffers + */ + public void clear() + { + mTransferQueue.clear(); + mProducerQueue.clear(); + mConsumerQueue.clear(); + } + + /** + * Returns an array of complex samples of the length requested. If there are not sufficient samples in the queue + * to fully satisfy the request, an IOException is thrown. + * @param requestedSamples number of complex sample pairs. + * @return a float array with twice as many (2 samples for each I & Q pair) values as requested. + * @throws IOException if there are (temporarily) insufficient samples available. + */ + public float[] get(int requestedSamples) throws IOException + { + //Update the producer's sample quota if it changes + if(requestedSamples != mRequestSize) + { + mRequestSize = requestedSamples; + } + + List drained = new ArrayList<>(); + mTransferQueue.drainTo(drained); + mConsumerQueue.addAll(drained); + + int count = 0; + + for(T buffer: mConsumerQueue) + { + count += buffer.sampleCount(); + } + + if(count < requestedSamples) + { + throw new IOException("Insufficient samples. Please try again later"); + } + + float[] samples = new float[requestedSamples * 2]; + int samplesPointer = 0; + + for(T buffer: mConsumerQueue) + { + Iterator iterator = buffer.iteratorInterleaved(); + + while(iterator.hasNext() && samplesPointer < samples.length) + { + InterleavedComplexSamples complexSamples = iterator.next(); + int toCopy = Math.min(samples.length - samplesPointer, complexSamples.samples().length); + System.arraycopy(complexSamples.samples(), 0, samples, samplesPointer, toCopy); + samplesPointer += toCopy; + } + + if(samplesPointer >= samples.length) + { + break; + } + } + + mConsumerQueue.clear(); + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java b/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java index 46c90006..ddb1dca8 100644 --- a/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java +++ b/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java @@ -1,39 +1,45 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.spectrum; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.OverflowableTransferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Iterator; import java.util.LinkedList; -public class OverflowableBufferStream extends OverflowableTransferQueue +public class OverflowableBufferStream extends OverflowableTransferQueue { private static final Logger mLog = LoggerFactory.getLogger(OverflowableBufferStream.class); private int mFlushCount = 0; - private ReusableComplexBuffer mCurrentBuffer; + private Iterator mCurrentNativeBufferIterator; + private InterleavedComplexSamples mCurrentBuffer; private int mCurrentBufferPointer = 0; private FloatBuffer mFloatBuffer; - private LinkedList mBufferList = new LinkedList<>(); + private LinkedList mBufferList = new LinkedList<>(); private int mBufferFetchLimit; /** @@ -90,7 +96,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue 0) { - mFloatBuffer.put(mCurrentBuffer.getSamples(), mCurrentBufferPointer, toCopy); + mFloatBuffer.put(mCurrentBuffer.samples(), mCurrentBufferPointer, toCopy); mCurrentBufferPointer += toCopy; } } @@ -150,7 +156,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue 0) { - int available = mCurrentBuffer.getSamples().length - mCurrentBufferPointer; + int available = mCurrentBuffer.samples().length - mCurrentBufferPointer; if(available <= mFlushCount) { @@ -181,35 +187,41 @@ public class OverflowableBufferStream extends OverflowableTransferQueue - * * ***************************************************************************** + * 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.spectrum; import com.jidesoft.swing.JideSplitPane; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.ChannelProcessingManager; -import io.github.dsheirer.dsp.filter.Window.WindowType; import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.eventbus.MyEventBus; import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest; import io.github.dsheirer.playlist.PlaylistManager; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.settings.ColorSetting.ColorSettingName; import io.github.dsheirer.settings.ColorSettingMenuItem; import io.github.dsheirer.settings.SettingsManager; @@ -83,8 +79,8 @@ import java.awt.event.MouseWheelEvent; import java.util.ArrayList; import java.util.Hashtable; -public class SpectralDisplayPanel extends JPanel implements Listener, ISourceEventProcessor, - IDFTWidthChangeProcessor +public class SpectralDisplayPanel extends JPanel + implements Listener, ISourceEventProcessor, IDFTWidthChangeProcessor { private static final long serialVersionUID = 1L; @@ -104,7 +100,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener * Mouse scrolling and zooming are supported and the waterfall display can * be paused. - * + *

* Complex sample buffers are processed by a DFTProcessor and the output of * the DFT is translated to decibels for display in the spectrum and * waterfall components. @@ -175,8 +171,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener * 0 No Zoom * 1 2x Zoom * 2 4x Zoom @@ -239,8 +233,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel))); + viewChannel.addActionListener( + e -> MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel))); channelMenu.add(viewChannel); } @@ -659,29 +635,21 @@ public class SpectralDisplayPanel extends JPanel implements Listener labels = new Hashtable<>(); + Hashtable labels = new Hashtable<>(); labels.put(0, new JLabel("1x")); labels.put(1, new JLabel("2x")); labels.put(2, new JLabel("4x")); @@ -893,8 +849,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener + * **************************************************************************** + */ + package io.github.dsheirer.spectrum.menu; -import io.github.dsheirer.dsp.filter.Window.WindowType; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.spectrum.ComplexDftProcessor; -import javax.swing.*; +import javax.swing.JCheckBoxMenuItem; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -11,29 +30,28 @@ public class FFTWindowTypeItem extends JCheckBoxMenuItem { private static final long serialVersionUID = 1L; - private DFTProcessor mDFTProcessor; + private ComplexDftProcessor mComplexDftProcessor; private WindowType mWindowType; - - public FFTWindowTypeItem( DFTProcessor processor, WindowType windowType ) - { - super( windowType.toString() ); - - mDFTProcessor = processor; - mWindowType = windowType; - if( processor.getWindowType() == mWindowType ) - { - setSelected( true ); - } - - addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent arg0 ) + public FFTWindowTypeItem(ComplexDftProcessor processor, WindowType windowType) + { + super(windowType.toString()); + + mComplexDftProcessor = processor; + mWindowType = windowType; + + if(processor.getWindowType() == mWindowType) + { + setSelected(true); + } + + addActionListener(new ActionListener() + { + @Override public void actionPerformed(ActionEvent arg0) { - mDFTProcessor.setWindowType( mWindowType ); + mComplexDftProcessor.setWindowType(mWindowType); } - } ); + }); } } diff --git a/src/main/java/io/github/dsheirer/spectrum/menu/FrameRateItem.java b/src/main/java/io/github/dsheirer/spectrum/menu/FrameRateItem.java index cb6c6cf3..3e0cc53b 100644 --- a/src/main/java/io/github/dsheirer/spectrum/menu/FrameRateItem.java +++ b/src/main/java/io/github/dsheirer/spectrum/menu/FrameRateItem.java @@ -1,6 +1,25 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.spectrum.menu; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.ComplexDftProcessor; import javax.swing.*; import java.awt.event.ActionEvent; @@ -10,14 +29,14 @@ public class FrameRateItem extends JCheckBoxMenuItem { private static final long serialVersionUID = 1L; - private DFTProcessor mDFTProcessor; + private ComplexDftProcessor mComplexDftProcessor; private int mFrameRate; - public FrameRateItem( DFTProcessor processor, int frameRate ) + public FrameRateItem(ComplexDftProcessor processor, int frameRate ) { super( String.valueOf( frameRate ) ); - mDFTProcessor = processor; + mComplexDftProcessor = processor; mFrameRate = frameRate; if( processor.getFrameRate() == mFrameRate ) @@ -30,7 +49,7 @@ public class FrameRateItem extends JCheckBoxMenuItem @Override public void actionPerformed( ActionEvent arg0 ) { - mDFTProcessor.setFrameRate( mFrameRate ); + mComplexDftProcessor.setFrameRate( mFrameRate ); } } ); } diff --git a/src/main/java/io/github/dsheirer/util/Dispatcher.java b/src/main/java/io/github/dsheirer/util/Dispatcher.java new file mode 100644 index 00000000..b614727a --- /dev/null +++ b/src/main/java/io/github/dsheirer/util/Dispatcher.java @@ -0,0 +1,200 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.util; + +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Threaded processor for receiving elements from a separate producer thread and forwarding those buffers to a + * registered listener on this consumer/dispatcher thread. + */ +public class Dispatcher implements Listener +{ + private final static Logger mLog = LoggerFactory.getLogger(Dispatcher.class); + private static final long OVERFLOW_LOG_EVENT_WAIT_PERIOD = TimeUnit.SECONDS.toMillis(10); + private LinkedBlockingQueue mQueue; + private Listener mListener; + private AtomicBoolean mRunning = new AtomicBoolean(); + private String mThreadName; + private Thread mThread; + private E mPoisonPill; + private long mLastOverflowLogEvent; + + /** + * Constructs an instance + * @param maxSize of the internal queue + * @param threadName to name the dispatcher thread + * @param poisonPill of type E, used to kill the thread + */ + public Dispatcher(int maxSize, String threadName, E poisonPill) + { + if(poisonPill == null) + { + throw new IllegalArgumentException("Poison pill must be non-null"); + } + mQueue = new LinkedBlockingQueue<>(maxSize); + mThreadName = threadName; + mPoisonPill = poisonPill; + } + + /** + * Listener to receive the queued buffers each time this processor runs. + */ + protected Listener getListener() + { + return mListener; + } + + /** + * Sets or changes the listener to receive buffers from this processor. + * @param listener to receive buffers + */ + public void setListener(Listener listener) + { + mListener = listener; + } + + /** + * Primary input method for adding buffers to this processor. Note: incoming buffers will be ignored if this + * processor is in a stopped state. You must invoke start() to allow incoming buffers and initiate buffer + * processing. + * + * @param e to enqueue for distribution to a registered listener + */ + public void receive(E e) + { + if(mRunning.get()) + { + if(!mQueue.offer(e)) + { + if(System.currentTimeMillis() > (mLastOverflowLogEvent + OVERFLOW_LOG_EVENT_WAIT_PERIOD)) + { + mLastOverflowLogEvent = System.currentTimeMillis(); + mLog.warn("Temporary buffer overflow for thread [" + mThreadName + "] - throwing away samples"); + } + } + } + else + { + mLog.error("Dispatcher [" + mThreadName + "] received an element when not in a running state - element:" + e.getClass()); + } + } + + /** + * Starts this buffer processor and allows queuing of incoming buffers. + */ + public void start() + { + if(mRunning.compareAndSet(false, true)) + { + mThread = new Thread(new Processor()); + mThread.setName(mThreadName); + mThread.setPriority(Thread.MAX_PRIORITY); + mThread.start(); + } + } + + /** + * Stops this buffer processor and waits up to two seconds for the processing thread to terminate. + */ + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + mQueue.offer(mPoisonPill); + + try + { + mThread.join(2000); + mThread = null; + } + catch(Exception e) + { + mLog.error("Timeout while waiting to join terminating buffer processor thread"); + } + } + } + + /** + * Finishes processing any queued buffers and then stops the processing thread. + */ + public void flushAndStop() + { + mQueue.offer(mPoisonPill); + } + + /** + * Indicates if this processor is currently running + */ + public boolean isRunning() + { + return mRunning.get(); + } + + /** + * Processor to service the buffer queue and distribute the buffers to the registered listener + */ + class Processor implements Runnable + { + @Override + public void run() + { + mQueue.clear(); + + E element; + + while(mRunning.get()) + { + try + { + element = mQueue.take(); + + if(mPoisonPill.equals(element)) + { + mRunning.set(false); + } + else if(element != null) + { + if(mListener == null) + { + throw new IllegalStateException("Listener for [" + mThreadName + "] is null"); + } + mListener.receive(element); + } + } + catch(InterruptedException e) + { + mLog.error("Buffer processor thread was interrupted"); + } + catch(Exception e) + { + mLog.error("Error while processing element", e); + } + } + + mQueue.clear(); + } + } +} diff --git a/src/main/java/io/github/dsheirer/util/ThreadPool.java b/src/main/java/io/github/dsheirer/util/ThreadPool.java index 3458263e..18d66c15 100644 --- a/src/main/java/io/github/dsheirer/util/ThreadPool.java +++ b/src/main/java/io/github/dsheirer/util/ThreadPool.java @@ -1,6 +1,6 @@ -/******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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 @@ -14,32 +14,25 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.util; import io.github.dsheirer.controller.NamingThreadFactory; -import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; public class ThreadPool { private final static Logger mLog = LoggerFactory.getLogger(ThreadPool.class); - - private static int CORES = Runtime.getRuntime().availableProcessors(); - public static ScheduledExecutorService SCHEDULED; - - static - { - //Create a scheduled thread pool sized according to the available processors/cores, minimum 2 - CORES = (FastMath.max(CORES, 2)); - - SCHEDULED = Executors.newScheduledThreadPool(CORES, new NamingThreadFactory("sdrtrunk")); - } + public static ScheduledExecutorService SCHEDULED = + Executors.newScheduledThreadPool(2, new NamingThreadFactory("sdrtrunk scheduled")); + public static ExecutorService CACHED = + Executors.newCachedThreadPool(new NamingThreadFactory("sdrtrunk cached")); /** * Application-wide shared thread pools and scheduled executor service. @@ -50,6 +43,6 @@ public class ThreadPool public static void logSettings() { - mLog.info("Application thread pool created with [" + CORES + "] threads"); + mLog.info("Application thread pool created SCHEDULED and CACHED executors threads"); } } diff --git a/src/main/java/io/github/dsheirer/vector/VectorUtilities.java b/src/main/java/io/github/dsheirer/vector/VectorUtilities.java new file mode 100644 index 00000000..05b459d5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/VectorUtilities.java @@ -0,0 +1,194 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorSpecies; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utilities for working with Project Panama SIMD vectors in JDK 17+. + */ +public class VectorUtilities +{ + private static final Logger mLog = LoggerFactory.getLogger(VectorUtilities.class); + private static boolean mSpeciesMismatchLogged = false; + + /** + * Checks the species to determine if it is compatible with the preferred species for the runtime CPU + * and logs a warning if the species' lane width is wider than the preferred species ... which would be + * hugely inefficient. + * + * @param species to test + */ + public static void checkSpecies(VectorSpecies species) + { + if(FloatVector.SPECIES_PREFERRED.length() < species.length()) + { + if(!mSpeciesMismatchLogged) + { + mLog.warn("CPU supports maximum SIMD instructions of " + FloatVector.SPECIES_PREFERRED); + mSpeciesMismatchLogged = true; + } + } + } + + /** + * Checks the I/Q sample array length to be an integer multiple of the SIMD lane width. + * @param i samples + * @param q samples + * @param species used for SIMD operations + */ + public static void checkComplexArrayLength(float[] i, float[] q, VectorSpecies species) + { + if(i.length % species.length() != 0) + { + throw new IllegalArgumentException("I/Q buffer lengths [" + i.length + "] must be a power of 2 multiple of SIMD lane width [" + + species.length() + "]"); + } + } + + /** + * Checks the I/Q sample array length to be an integer multiple of the SIMD lane width. + * @param samples buffer + * @param species used for SIMD operations + */ + public static void checkComplexArrayLength(ComplexSamples samples, VectorSpecies species) + { + checkComplexArrayLength(samples.i(), samples.q(), species); + } + + /** + * Creates a vector mask for deinterleaving I samples from an interleaved complex sample vector. + * @param species of SIMD + * @return vector mask + */ + public static VectorMask getIVectorMask(VectorSpecies species) + { + switch(species.length()) + { + case 2: + return VectorMask.fromArray(species, new boolean[]{true,false}, 0); + case 4: + return VectorMask.fromArray(species, new boolean[]{true,false,true,false}, 0); + case 8: + return VectorMask.fromArray(species, new boolean[]{true,false,true,false,true,false,true,false}, 0); + case 16: + return VectorMask.fromArray(species, new boolean[]{true,false,true,false,true,false,true,false,true, + false,true,false,true,false,true,false}, 0); + default: + throw new IllegalArgumentException("Unrecognized vector species: " + species); + } + } + + /** + * Creates a vector mask for deinterleaving Q samples from an interleaved complex sample vector. + * @param species of SIMD + * @return vector mask + */ + public static VectorMask getQVectorMask(VectorSpecies species) + { + switch(species.length()) + { + case 2: + return VectorMask.fromArray(species, new boolean[]{false,true}, 0); + case 4: + return VectorMask.fromArray(species, new boolean[]{false,true,false,true}, 0); + case 8: + return VectorMask.fromArray(species, new boolean[]{false,true,false,true,false,true,false,true}, 0); + case 16: + return VectorMask.fromArray(species, new boolean[]{false,true,false,true,false,true,false,true,false,true, + false,true,false,true,false,true}, 0); + default: + throw new IllegalArgumentException("Unrecognized vector species: " + species); + } + } + + /** + * Creates an index map for routing inphase samples into a complex sample array + * @param species to determine lane width + * @return index map + */ + public static int[] getIIndexMap(VectorSpecies species) + { + switch(species.length()) + { + case 2: + return new int[]{0,2}; + case 4: + return new int[]{0,2,4,6}; + case 8: + return new int[]{0,2,4,6,8,10,12,14}; + case 16: + return new int[]{0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30}; + default: + throw new IllegalArgumentException("Unrecognized vector species: " + species); + } + } + + /** + * Creates an index map for routing quadrature samples into a complex sample array + * @param species to determine lane width + * @return index map + */ + public static int[] getQIndexMap(VectorSpecies species) + { + switch(species.length()) + { + case 2: + return new int[]{1,3}; + case 4: + return new int[]{1,3,5,7}; + case 8: + return new int[]{1,3,5,7,9,11,13,15}; + case 16: + return new int[]{1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31}; + default: + throw new IllegalArgumentException("Unrecognized vector species: " + species); + } + } + + /** + * Interleaves the samples from the I and Q vectors into a complex sample array + * @param iVector samples + * @param qVector samples + * @return interleaved sample array + */ + public static float[] interleave(FloatVector iVector, FloatVector qVector) + { + float[] interleaved = new float[iVector.length() * 2]; + float[] i = new float[iVector.length()]; + float[] q = new float[qVector.length()]; + + iVector.intoArray(i, 0); + qVector.intoArray(q, 0); + + for(int x = 0; x < i.length; x++) + { + interleaved[2 * x] = i[x]; + interleaved[2 * x + 1] = q[x]; + } + + return interleaved; + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java new file mode 100644 index 00000000..72a62b6e --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java @@ -0,0 +1,143 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.Random; +import java.util.prefs.Preferences; + +/** + * Abstract calibration plugin base class + */ +public abstract class Calibration +{ + protected static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0"); + public static final Logger mLog = LoggerFactory.getLogger(Calibration.class); + private Preferences mPreferences = Preferences.userNodeForPackage(Calibration.class); + private CalibrationType mType; + private Implementation mImplementation; + + /** + * Constructs an instance + * @param type + */ + public Calibration(CalibrationType type) + { + mType = type; + } + + public Logger getLogger() + { + return mLog; + } + + /** + * Type of calibration plugin + */ + public CalibrationType getType() + { + return mType; + } + + /** + * Indicates if this plugin is calibrated. + */ + public boolean isCalibrated() + { + return getImplementation() != Implementation.UNCALIBRATED; + } + + /** + * Resets calibration status to uncalibrated for the plugin + */ + public void reset() + { + setImplementation(Implementation.UNCALIBRATED); + } + + /** + * Optimal implementation for this plugin + * @return implementation or uncalibrated if this plugin has not yet been calibrated + */ + public Implementation getImplementation() + { + if(mImplementation == null) + { + String implementation = mPreferences.get(getType().getPreferenceKey(), Implementation.UNCALIBRATED.name()); + mImplementation = Implementation.valueOf(implementation); + } + + return mImplementation; + } + + /** + * Sets the optimal implementation as determined via calibration. + * @param implementation to set + */ + protected void setImplementation(Implementation implementation) + { + mImplementation = implementation; + mPreferences.put(getType().getPreferenceKey(), implementation.name()); + } + + /** + * Executes calibration for the plugin + */ + public abstract void calibrate() throws CalibrationException; + + /** + * Generates an array of floating point samples in the range -1.0 - 1.0 + * @param size of array + * @return generated samples + */ + protected float[] getFloatSamples(int size) + { + Random random = new Random(); + + float[] samples = new float[size]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + return samples; + } + + /** + * Generates an array of floating point samples in the range -1.0 - 1.0 + * @param size of array + * @return generated samples + */ + protected short[] getShortSamples(int size) + { + Random random = new Random(); + + short[] samples = new short[size]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = (short)((random.nextFloat() * 2.0f - 1.0f) * Short.MAX_VALUE); + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationException.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationException.java new file mode 100644 index 00000000..fc837dbb --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationException.java @@ -0,0 +1,17 @@ +package io.github.dsheirer.vector.calibrate; + +/** + * Error encountered while calibrating + */ +public class CalibrationException extends Exception +{ + /** + * Constructs a new instance + * @param description of the error + * @param error nested + */ + public CalibrationException(String description, Exception error) + { + super(description, error); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java new file mode 100644 index 00000000..e40faaf8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java @@ -0,0 +1,294 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate; + +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.calibration.VectorCalibrationPreference; +import io.github.dsheirer.vector.calibrate.airspy.AirspySampleConverterCalibration; +import io.github.dsheirer.vector.calibrate.airspy.AirspyUnpackedCalibration; +import io.github.dsheirer.vector.calibrate.airspy.AirspyUnpackedInterleavedCalibration; +import io.github.dsheirer.vector.calibrate.demodulator.FmDemodulatorCalibration; +import io.github.dsheirer.vector.calibrate.demodulator.SquelchingFmDemodulatorCalibration; +import io.github.dsheirer.vector.calibrate.filter.FirFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealDcRemovalCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBand11TapFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBand15TapFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBand23TapFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBand63TapFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBandDefaultFilterCalibration; +import io.github.dsheirer.vector.calibrate.gain.ComplexGainCalibration; +import io.github.dsheirer.vector.calibrate.gain.ComplexGainControlCalibration; +import io.github.dsheirer.vector.calibrate.mixer.ComplexMixerCalibration; +import io.github.dsheirer.vector.calibrate.oscillator.ComplexOscillatorCalibration; +import io.github.dsheirer.vector.calibrate.oscillator.RealOscillatorCalibration; +import io.github.dsheirer.vector.calibrate.window.WindowCalibration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Determines the optimal (scalar vs vector) class to use for the current CPU architecture. + */ +public class CalibrationManager +{ + private static final Logger mLog = LoggerFactory.getLogger(CalibrationManager.class); + private Map mCalibrationMap = new HashMap<>(); + private static CalibrationManager sInstance; + private static VectorCalibrationPreference sVectorCalibrationPreference; + + /** + * Uses the singleton pattern to construct a single instance. + */ + private CalibrationManager() + { + } + + /** + * Access a singleton instance of this class, initializing the instance with the + * specified User Preferences instance, if it hasn't already been intitialized. + * + * Note: invoke this method first with a preferences instance to ensure it is the one that is used. + */ + public static CalibrationManager getInstance(UserPreferences userPreferences) + { + if(sVectorCalibrationPreference == null) + { + sVectorCalibrationPreference = userPreferences.getVectorCalibrationPreference(); + } + + return getInstance(); + } + + /** + * Access a singleton instance of this class. + */ + public static CalibrationManager getInstance() + { + if(sInstance == null) + { + if(sVectorCalibrationPreference == null) + { + sVectorCalibrationPreference = new UserPreferences().getVectorCalibrationPreference(); + } + + sInstance = new CalibrationManager(); + + sInstance.add(new AirspySampleConverterCalibration()); + sInstance.add(new AirspyUnpackedCalibration()); + sInstance.add(new AirspyUnpackedInterleavedCalibration()); + sInstance.add(new ComplexGainCalibration()); + sInstance.add(new ComplexGainControlCalibration()); + sInstance.add(new ComplexOscillatorCalibration()); + sInstance.add(new ComplexMixerCalibration()); + sInstance.add(new FirFilterCalibration()); + sInstance.add(new FmDemodulatorCalibration()); + sInstance.add(new RealDcRemovalCalibration()); + sInstance.add(new RealHalfBand11TapFilterCalibration()); + sInstance.add(new RealHalfBand15TapFilterCalibration()); + sInstance.add(new RealHalfBand23TapFilterCalibration()); + sInstance.add(new RealHalfBand63TapFilterCalibration()); + sInstance.add(new RealHalfBandDefaultFilterCalibration()); + sInstance.add(new RealOscillatorCalibration()); + sInstance.add(new SquelchingFmDemodulatorCalibration()); + +// sInstance.add(new HilbertCalibration()); //Not currently used + sInstance.add(new WindowCalibration()); //Not currently used + } + + return sInstance; + } + + /** + * Adds the calibration to the map of calibrations for this manager. + * @param calibration to add + */ + private void add(Calibration calibration) + { + if(mCalibrationMap.containsKey(calibration.getType())) + { + throw new IllegalStateException("Calibration Type [" + calibration.getType() + + "] is already registered for [" + mCalibrationMap.get(calibration.getType()).getClass()); + } + + mCalibrationMap.put(calibration.getType(), calibration); + } + + /** + * List of calibration types available + */ + public List getCalibrationTypes() + { + return List.copyOf(mCalibrationMap.keySet()); + } + + /** + * Access a calibration by type + * @param type of calibration + * @return calibration instance, or null. + */ + public Calibration getCalibration(CalibrationType type) + { + return mCalibrationMap.get(type); + } + + /** + * Identifies the optimal operation for the calibration type. + * @param type of calibration + * @return operation + */ + public Implementation getImplementation(CalibrationType type) + { + if(sVectorCalibrationPreference.isVectorEnabled()) + { + Calibration calibration = getCalibration(type); + + if(calibration != null) + { + return calibration.getImplementation(); + } + + return Implementation.UNCALIBRATED; + } + + return Implementation.SCALAR; + } + + /** + * Indicates if all calibrations are calibrated. + * @return true if all calibrations are calibrated or if there are no calibrations registered. + */ + public boolean isCalibrated() + { + for(Calibration calibration: mCalibrationMap.values()) + { + if(!calibration.isCalibrated()) + { + return false; + } + } + + return true; + } + + /** + * Resets all calibrations to an uncalibrated state. + */ + public void reset() + { + for(Calibration calibration: mCalibrationMap.values()) + { + calibration.reset(); + } + } + + /** + * Resets a specific calibration type. + */ + public void reset(CalibrationType type) + { + Calibration calibration = mCalibrationMap.get(type); + + if(calibration != null) + { + calibration.reset(); + } + } + + /** + * Calibrates any calibrations that are not currently calibrated + * @throws CalibrationException if any errors are encountered by any of the calibrations. + */ + public void calibrate() throws CalibrationException + { + List uncalibrated = getUncalibrated(); + + if(uncalibrated.isEmpty()) + { + mLog.info("No additional calibrations are required at this time."); + } + else + { + mLog.info("Calibrating software for optimal performance on this computer."); + mLog.info("*** Please be patient, this may take a few minutes ***"); + + int calibrationCounter = 0; + + for(Calibration calibration: uncalibrated) + { + if(!calibration.isCalibrated()) + { + mLog.info("Calibrating [" + ++calibrationCounter + " of " + uncalibrated.size() + + "] Type: " + calibration.getType()); + calibration.calibrate(); + } + } + + mLog.info("Calibration Complete!"); + } + } + + /** + * List of calibrations that need to be performed. + */ + public List getUncalibrated() + { + List uncalibrated = new ArrayList<>(); + + for(Calibration calibration: mCalibrationMap.values()) + { + if(!calibration.isCalibrated()) + { + uncalibrated.add(calibration); + } + } + + //Sort by calibration type + Collections.sort(uncalibrated, Comparator.comparing(Calibration::getType)); + + return uncalibrated; + } + + public static void main(String[] args) + { + CalibrationManager manager = getInstance(); +// manager.reset(); + manager.reset(CalibrationType.WINDOW); + + if(!manager.isCalibrated()) + { + try + { + manager.calibrate(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + + System.out.println("Complete"); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java new file mode 100644 index 00000000..3a0d77c7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java @@ -0,0 +1,92 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate; + +/** + * Calibrations used by the system + * + * Version numbers in each calibration can be incremented any time the calibration changes + * so that the calibration will automatically run. + */ +public enum CalibrationType +{ + //There's an oddity with these two vector implementations where it takes ~30x longer when it runs + //after any other calibrations ... so we always sort to top to run this one first + OSCILLATOR_COMPLEX("Complex Oscillator", 1), + GAIN_COMPLEX("Complex Gain", 1), + + AIRSPY_SAMPLE_CONVERTER("Airspy Sample Converter", 1), + AIRSPY_UNPACKED_INTERLEAVED_ITERATOR("Airspy Unpacked Interleaved Iterator", 1), + AIRSPY_UNPACKED_ITERATOR("Airspy Unpacked Iterator", 1), + DC_REMOVAL_REAL("Real DC Removal Filter", 1), + FILTER_FIR("FIR Filter", 1), + FILTER_HALF_BAND_REAL_11_TAP("Real Half-Band Decimation Filter - 11 Tap", 1), + FILTER_HALF_BAND_REAL_15_TAP("Real Half-Band Decimation Filter - 15 Tap", 1), + FILTER_HALF_BAND_REAL_23_TAP("Real Half-Band Decimation Filter - 23 Tap", 1), + FILTER_HALF_BAND_REAL_63_TAP("Real Half-Band Decimation Filter - 63 Tap", 1), + FILTER_HALF_BAND_REAL_DEFAULT("Real Half-Band Decimation Filter - Default", 1), + FM_DEMODULATOR("FM Demodulator", 1), + GAIN_CONTROL_COMPLEX("Complex Gain Control", 1), + HILBERT_TRANSFORM("Hilbert Transform", 1), + MIXER_COMPLEX("Complex Mixer", 1), + OSCILLATOR_REAL("Real Oscillator", 1), + SQUELCHING_FM_DEMODULATOR("Squelching FM Demodulator", 1), + WINDOW("Window", 1); + + private String mDescription; + private int mVersion; + + /** + * Constructs an instance + * @param description + * @param version of the calibration. + */ + CalibrationType(String description, int version) + { + mDescription = description; + mVersion = version; + } + + /** + * Formulates a unique value for the entry for use as a key value. + */ + public String getPreferenceKey() + { + return name() + " - V" + getVersion(); + } + + public String getDescription() + { + return mDescription; + } + + /** + * Version of the calibration + */ + public int getVersion() + { + return mVersion; + } + + @Override public String toString() + { + return getDescription(); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/Implementation.java b/src/main/java/io/github/dsheirer/vector/calibrate/Implementation.java new file mode 100644 index 00000000..54d898cb --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/Implementation.java @@ -0,0 +1,15 @@ +package io.github.dsheirer.vector.calibrate; + +/** + * Identifies the optimal implementation (Scalar vs Vector) identified through calibration testing + */ +public enum Implementation +{ + SCALAR, + VECTOR_SIMD_PREFERRED, + VECTOR_SIMD_64, + VECTOR_SIMD_128, + VECTOR_SIMD_256, + VECTOR_SIMD_512, + UNCALIBRATED; +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java new file mode 100644 index 00000000..9dae658c --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java @@ -0,0 +1,138 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.airspy; + +import io.github.dsheirer.buffer.airspy.ScalarUnpackedSampleConverter; +import io.github.dsheirer.buffer.airspy.VectorUnpackedSampleConverter; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +import java.nio.ByteBuffer; + +public class AirspySampleConverterCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 262144; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARM_UP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + /** + * Constructs an instance + */ + public AirspySampleConverterCalibration() + { + super(CalibrationType.AIRSPY_SAMPLE_CONVERTER); + } + + @Override + public void calibrate() throws CalibrationException + { + Mean scalarMean = new Mean(); + Mean vectorMean = new Mean(); + + byte[] samples = new byte[BUFFER_SIZE]; + ByteBuffer buffer = ByteBuffer.wrap(samples); + + for(int scalarTest = 0; scalarTest < WARM_UP_ITERATIONS; scalarTest++) + { + long score = testScalar(buffer); + scalarMean.increment(score); + } + + mLog.info("AIRSPY CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + for(int vectorTest = 0; vectorTest < WARM_UP_ITERATIONS; vectorTest++) + { + long score = testVector(buffer); + vectorMean.increment(score); + } + + mLog.info("AIRSPY CONVERTER WARMUP - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + vectorMean.clear(); + + for(int scalarTest = 0; scalarTest < TEST_ITERATIONS; scalarTest++) + { + long score = testScalar(buffer); + scalarMean.increment(score); + } + + mLog.info("AIRSPY CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + for(int vectorTest = 0; vectorTest < TEST_ITERATIONS; vectorTest++) + { + long score = testVector(buffer); + vectorMean.increment(score); + } + + mLog.info("AIRSPY CONVERTER - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("AIRSPY CONVERTER - SETTING OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(ByteBuffer buffer) + { + long start = System.currentTimeMillis(); + + ScalarUnpackedSampleConverter scalor = new ScalarUnpackedSampleConverter(); + + long count = 0; + long accumulator = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + short[] converted = scalor.convert(buffer); + accumulator += converted[2]; + count++; + } + + return count + (accumulator * 0); + } + + private long testVector(ByteBuffer buffer) + { + VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter(); + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + short[] converted = vector.convert(buffer); + accumulator += converted[2]; + count++; + } + + return count + (accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java new file mode 100644 index 00000000..1be80f57 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java @@ -0,0 +1,309 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.airspy; + +import io.github.dsheirer.buffer.airspy.AirspyBufferIterator; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorScalar; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector128Bits; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector256Bits; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector512Bits; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector64Bits; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calculates optimal implementation (SCALAR vs VECTOR) for non-interleaved airspy native buffers. + */ +public class AirspyUnpackedCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 131072; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + /** + * Constructs an instance + */ + public AirspyUnpackedCalibration() + { + super(CalibrationType.AIRSPY_UNPACKED_ITERATOR); + } + + @Override + public void calibrate() throws CalibrationException + { + short[] samples = getShortSamples(BUFFER_SIZE); + short[] residualI = getShortSamples(AirspyBufferIterator.I_OVERLAP); + short[] residualQ = getShortSamples(AirspyBufferIterator.Q_OVERLAP); + + //Warm-Up Phase .... + Mean scalarMean = new Mean(); + + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testScalar(samples, residualI, residualQ); + scalarMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + switch(FloatVector.SPECIES_PREFERRED.length()) + { + //Deliberate fall-through of each case statement so that we can test the largest + //SIMD lane width supported by hardware down to the smallest SIMD lane width. + case 16: + { + Mean vector512Mean = new Mean(); + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testVector512(samples, residualI, residualQ); + vector512Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + case 8: + { + Mean vector256Mean = new Mean(); + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testVector256(samples, residualI, residualQ); + vector256Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + case 4: + { + Mean vector128Mean = new Mean(); + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testVector128(samples, residualI, residualQ); + vector128Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + case 2: + { + Mean vector64Mean = new Mean(); + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testVector64(samples, residualI, residualQ); + vector64Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + } + + //Test Phase .... + scalarMean.clear(); + + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testScalar(samples, residualI, residualQ); + scalarMean.increment(score); + } + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + mLog.info("AIRSPY UNPACKED - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + switch(FloatVector.SPECIES_PREFERRED.length()) + { + //Deliberate fall-through of each case statement so that we can test the largest + //SIMD lane width supported by hardware down to the smallest SIMD lane width. + case 16: + { + Mean vector512Mean = new Mean(); + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testVector512(samples, residualI, residualQ); + vector512Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + case 8: + { + Mean vector256Mean = new Mean(); + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testVector256(samples, residualI, residualQ); + vector256Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + case 4: + { + Mean vector128Mean = new Mean(); + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testVector128(samples, residualI, residualQ); + vector128Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + case 2: + { + Mean vector64Mean = new Mean(); + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testVector64(samples, residualI, residualQ); + vector64Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + } + + mLog.info("AIRSPY UNPACKED - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorScalar iterator = new AirspyBufferIteratorScalar(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long testVector64(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorVector64Bits iterator = new AirspyBufferIteratorVector64Bits(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long testVector128(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorVector128Bits iterator = new AirspyBufferIteratorVector128Bits(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long testVector256(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorVector256Bits iterator = + new AirspyBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long testVector512(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorVector512Bits iterator = + new AirspyBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java new file mode 100644 index 00000000..aef91fa3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java @@ -0,0 +1,321 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.airspy; + +import io.github.dsheirer.buffer.airspy.AirspyBufferIterator; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorScalar; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector128Bits; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector256Bits; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector512Bits; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector64Bits; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calculates optimal implementation (SCALAR vs VECTOR) for interleaved airspy native buffers. + */ +public class AirspyUnpackedInterleavedCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 131072; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + /** + * Constructs an instance + */ + public AirspyUnpackedInterleavedCalibration() + { + super(CalibrationType.AIRSPY_UNPACKED_INTERLEAVED_ITERATOR); + } + + @Override + public void calibrate() throws CalibrationException + { + short[] samples = getShortSamples(BUFFER_SIZE); + short[] residualI = getShortSamples(AirspyBufferIterator.I_OVERLAP); + short[] residualQ = getShortSamples(AirspyBufferIterator.Q_OVERLAP); + + //Warm-Up Phase .... + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateScalar(samples, residualI, residualQ); + scalarMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + switch(FloatVector.SPECIES_PREFERRED.length()) + { + //Deliberate fall-through of each case statement so that we can test the largest + //SIMD lane width supported by hardware down to the smallest SIMD lane width. + case 16: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateVector512(samples, residualI, residualQ); + vectorMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 8: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateVector256(samples, residualI, residualQ); + vectorMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 4: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateVector128(samples, residualI, residualQ); + vectorMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 2: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateVector64(samples, residualI, residualQ); + vectorMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + + //Test Phase .... + scalarMean.clear(); + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateScalar(samples, residualI, residualQ); + scalarMean.increment(score); + } + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + mLog.info("AIRSPY UNPACKED INTERLEAVED - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + switch(FloatVector.SPECIES_PREFERRED.length()) + { + //Deliberate fall-through of each case statement so that we can test the largest + //SIMD lane width supported by hardware down to the smallest SIMD lane width. + case 16: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateVector512(samples, residualI, residualQ); + vectorMean.increment(score); + } + + if(vectorMean.getResult() > bestScore) + { + bestScore = vectorMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 8: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateVector256(samples, residualI, residualQ); + vectorMean.increment(score); + } + + if(vectorMean.getResult() > bestScore) + { + bestScore = vectorMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 4: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateVector128(samples, residualI, residualQ); + vectorMean.increment(score); + } + + if(vectorMean.getResult() > bestScore) + { + bestScore = vectorMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 2: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateVector64(samples, residualI, residualQ); + vectorMean.increment(score); + } + + if(vectorMean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + } + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long calibrateScalar(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyInterleavedBufferIteratorScalar iterator = new AirspyInterleavedBufferIteratorScalar(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long calibrateVector64(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyInterleavedBufferIteratorVector64Bits iterator = new AirspyInterleavedBufferIteratorVector64Bits(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long calibrateVector128(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + for(int x = 0; x < ITERATION_DURATION_MS; x++) + { + AirspyInterleavedBufferIteratorVector128Bits iterator = new AirspyInterleavedBufferIteratorVector128Bits(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long calibrateVector256(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyInterleavedBufferIteratorVector256Bits iterator = + new AirspyInterleavedBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long calibrateVector512(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyInterleavedBufferIteratorVector512Bits iterator = + new AirspyInterleavedBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/FmDemodulatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/FmDemodulatorCalibration.java new file mode 100644 index 00000000..20ae919f --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/FmDemodulatorCalibration.java @@ -0,0 +1,142 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.demodulator; + +import io.github.dsheirer.dsp.fm.IFmDemodulator; +import io.github.dsheirer.dsp.fm.ScalarFMDemodulator; +import io.github.dsheirer.dsp.fm.VectorFMDemodulator; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibrates FM demodulator options + */ +public class FmDemodulatorCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + private IFmDemodulator mScalarDemodulator = new ScalarFMDemodulator(); + private IFmDemodulator mVectorDemodulator = new VectorFMDemodulator(); + + + /** + * Constructs an instance + */ + public FmDemodulatorCalibration() + { + super(CalibrationType.FM_DEMODULATOR); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(BUFFER_SIZE); + float[] q = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("FM DEMODULATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("FM DEMODULATOR WARMUP - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + + //Start tests + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("FM DEMODULATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("FM DEMODULATOR - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("FM DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mScalarDemodulator.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + double accumulator = 0.0; + long count = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mVectorDemodulator.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/SquelchingFmDemodulatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/SquelchingFmDemodulatorCalibration.java new file mode 100644 index 00000000..1380a571 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/SquelchingFmDemodulatorCalibration.java @@ -0,0 +1,148 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.demodulator; + +import io.github.dsheirer.dsp.fm.IFmDemodulator; +import io.github.dsheirer.dsp.fm.ScalarSquelchingFMDemodulator; +import io.github.dsheirer.dsp.fm.VectorSquelchingFMDemodulator; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibrates squelching FM demodulator options + */ +public class SquelchingFmDemodulatorCalibration extends Calibration +{ + private static final float POWER_SQUELCH_ALPHA_DECAY = 0.0004f; + private static final float POWER_SQUELCH_THRESHOLD_DB = -78.0f; + private static final int POWER_SQUELCH_RAMP = 4; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IFmDemodulator mScalar = new ScalarSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, + POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + private IFmDemodulator mVector = new VectorSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, + POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + + /** + * Constructs an instance + */ + public SquelchingFmDemodulatorCalibration() + { + super(CalibrationType.SQUELCHING_FM_DEMODULATOR); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(BUFFER_SIZE); + float[] q = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("SQUELCHING FM DEMODULATOR WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("SQUELCHING FM DEMODULATOR WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + //Test begins ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("SQUELCHING FM DEMODULATOR - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("SQUELCHING FM DEMODULATOR - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("SQUELCHING FM DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mScalar.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mVector.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/FirFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/FirFilterCalibration.java new file mode 100644 index 00000000..f02cc345 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/FirFilterCalibration.java @@ -0,0 +1,378 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter128Bit; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter256Bit; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter512Bit; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter64Bit; +import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilterDefaultBit; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibration plugin for FIR filters + */ +public class FirFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealFilter mScalar; + private IRealFilter mVectorPreferred; + private IRealFilter mVector512; + private IRealFilter mVector256; + private IRealFilter mVector128; + private IRealFilter mVector64; + + + /** + * Constructs an instance + */ + public FirFilterCalibration() + { + super(CalibrationType.FILTER_FIR); + + float[] coefficients; + + try + { + coefficients = FilterFactory.getSinc(0.25, 31, WindowType.BLACKMAN); + mScalar = new RealFIRFilter(coefficients); + mVectorPreferred = new VectorRealFIRFilterDefaultBit(coefficients); + mVector64 = new VectorRealFIRFilter64Bit(coefficients); + mVector128 = new VectorRealFIRFilter128Bit(coefficients); + mVector256 = new VectorRealFIRFilter256Bit(coefficients); + mVector512 = new VectorRealFIRFilter512Bit(coefficients); + } + catch(FilterDesignException fde) + { + mLog.error("Error creating sinc filter for real FIR filter calibration"); + } + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + if(mScalar == null) + { + return; + } + + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorPreferredMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("FIR FILTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + vectorPreferredMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + if(vectorPreferredMean.getResult() > bestScore) + { + bestScore = vectorPreferredMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("FIR FILTER - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0f; + + long start = System.currentTimeMillis(); + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVectorPreferred(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVectorPreferred.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealDcRemovalCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealDcRemovalCalibration.java new file mode 100644 index 00000000..8968aaeb --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealDcRemovalCalibration.java @@ -0,0 +1,144 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.dc.IDcRemovalFilter; +import io.github.dsheirer.dsp.filter.dc.ScalarDcRemovalFilter; +import io.github.dsheirer.dsp.filter.dc.VectorDcRemovalFilter; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Determines optimal DC removal implementation + */ +public class RealDcRemovalCalibration extends Calibration +{ + private static final float GAIN = 0.15f; + private static final int BUFFER_SIZE = 8192; + private static final int ITERATION_DURATION_MS = 1000; + private static final int TEST_ITERATIONS = 5; + private static final int WARMUP_ITERATIONS = 5; + + private IDcRemovalFilter mScalar = new ScalarDcRemovalFilter(GAIN); + private IDcRemovalFilter mVector = new VectorDcRemovalFilter(GAIN); + + /** + * Constructs an instance + */ + public RealDcRemovalCalibration() + { + super(CalibrationType.DC_REMOVAL_REAL); + } + + @Override + public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL DC REMOVAL WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(samples); + vectorMean.increment(score); + } + + mLog.info("REAL DC REMOVAL WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL DC REMOVAL - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(samples); + vectorMean.increment(score); + } + + mLog.info("REAL DC REMOVAL - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("REAL DC REMOVAL - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand11TapFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand11TapFilterCalibration.java new file mode 100644 index 00000000..e0b90a45 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand11TapFilterCalibration.java @@ -0,0 +1,316 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap64Bit; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBand11TapFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBand11TapFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_11_TAP); + float[] coefficients = FilterFactory.getHalfBand(11, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter11Tap512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter11Tap256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter11Tap128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter11Tap64Bit(coefficients); + } + + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand15TapFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand15TapFilterCalibration.java new file mode 100644 index 00000000..598d2840 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand15TapFilterCalibration.java @@ -0,0 +1,315 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap64Bit; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBand15TapFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1_000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBand15TapFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_15_TAP); + float[] coefficients = FilterFactory.getHalfBand(15, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter15Tap512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter15Tap256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter15Tap128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter15Tap64Bit(coefficients); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand23TapFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand23TapFilterCalibration.java new file mode 100644 index 00000000..367e14db --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand23TapFilterCalibration.java @@ -0,0 +1,315 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap64Bit; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBand23TapFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBand23TapFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_23_TAP); + float[] coefficients = FilterFactory.getHalfBand(23, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter23Tap512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter23Tap256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter23Tap128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter23Tap64Bit(coefficients); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand63TapFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand63TapFilterCalibration.java new file mode 100644 index 00000000..edfd5f76 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand63TapFilterCalibration.java @@ -0,0 +1,315 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap64Bit; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBand63TapFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBand63TapFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_63_TAP); + float[] coefficients = FilterFactory.getHalfBand(63, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter63Tap512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter63Tap256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter63Tap128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter63Tap64Bit(coefficients); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBandDefaultFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBandDefaultFilterCalibration.java new file mode 100644 index 00000000..955997d0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBandDefaultFilterCalibration.java @@ -0,0 +1,361 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter64Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilterDefaultBit; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBandDefaultFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVectorPreferred; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBandDefaultFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_DEFAULT); + float[] coefficients = FilterFactory.getHalfBand(63, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVectorPreferred = new VectorRealHalfBandDecimationFilterDefaultBit(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter64Bit(coefficients); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorPreferredMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + vectorPreferredMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + if(vectorPreferredMean.getResult() > bestScore) + { + bestScore = vectorPreferredMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVectorPreferred(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVectorPreferred.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainCalibration.java new file mode 100644 index 00000000..d5811b75 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainCalibration.java @@ -0,0 +1,150 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.gain; + +import io.github.dsheirer.dsp.gain.complex.ComplexGain; +import io.github.dsheirer.dsp.gain.complex.ScalarComplexGain; +import io.github.dsheirer.dsp.gain.complex.VectorComplexGain; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Determines the optimal scalar vs vector implementation of complex gain. + */ +public class ComplexGainCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + private static final float GAIN = 0.99f; + + /** + * Constructs an instance + */ + public ComplexGainCalibration() + { + super(CalibrationType.GAIN_COMPLEX); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(BUFFER_SIZE); + float[] q = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX GAIN WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + i = getFloatSamples(BUFFER_SIZE); + q = getFloatSamples(BUFFER_SIZE); + + Mean vectorMean = new Mean(); + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX GAIN WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + i = getFloatSamples(BUFFER_SIZE); + q = getFloatSamples(BUFFER_SIZE); + + scalarMean.clear();; + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX GAIN - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + i = getFloatSamples(BUFFER_SIZE); + q = getFloatSamples(BUFFER_SIZE); + + vectorMean.clear(); + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX GAIN - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("COMPLEX GAIN - SET IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + float accumulator = 0.0f; + long count = 0; + + ComplexGain scalar = new ScalarComplexGain(GAIN); + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples amplified = scalar.apply(i, q); + accumulator += amplified.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + float accumulator = 0.0f; + long count = 0; + + ComplexGain scalar = new VectorComplexGain(GAIN); + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples amplified = scalar.apply(i, q); + accumulator += amplified.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainControlCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainControlCalibration.java new file mode 100644 index 00000000..ea5146e7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainControlCalibration.java @@ -0,0 +1,143 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.gain; + +import io.github.dsheirer.dsp.gain.complex.ComplexGainControl; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; +import io.github.dsheirer.dsp.gain.complex.VectorComplexGainControl; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Determines the optimal scalar vs vector implementation of complex gain control. + */ +public class ComplexGainControlCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IComplexGainControl mScalarGainControl = new ComplexGainControl(); + private IComplexGainControl mVectorGainControl = new VectorComplexGainControl(); + + /** + * Constructs an instance + */ + public ComplexGainControlCalibration() + { + super(CalibrationType.GAIN_CONTROL_COMPLEX); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(BUFFER_SIZE); + float[] q = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX GAIN CONTROL WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX GAIN CONTROL WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX GAIN CONTROL - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX GAIN CONTROL - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("COMPLEX GAIN CONTROL - SET IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + float accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples amplified = mScalarGainControl.process(i, q); + accumulator += amplified.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + float accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples amplified = mVectorGainControl.process(i, q); + accumulator += amplified.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/hilbert/HilbertCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/hilbert/HilbertCalibration.java new file mode 100644 index 00000000..0a71df56 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/hilbert/HilbertCalibration.java @@ -0,0 +1,350 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.hilbert; + +import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; +import io.github.dsheirer.dsp.filter.hilbert.ScalarHilbertTransform; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransform128Bits; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransform256Bits; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransform512Bits; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransform64Bits; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransformDefaultBits; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +public class HilbertCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private HilbertTransform mScalar = new ScalarHilbertTransform(); + private HilbertTransform mVectorPreferred = new VectorHilbertTransformDefaultBits(); + private HilbertTransform mVector512 = new VectorHilbertTransform512Bits(); + private HilbertTransform mVector256 = new VectorHilbertTransform256Bits(); + private HilbertTransform mVector128 = new VectorHilbertTransform128Bits(); + private HilbertTransform mVector64 = new VectorHilbertTransform64Bits(); + + /** + * Constructs an instance + */ + public HilbertCalibration() + { + super(CalibrationType.HILBERT_TRANSFORM); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("HILBERT WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorPreferredMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("HILBERT - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + vectorPreferredMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("HILBERT - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + if(vectorPreferredMean.getResult() > bestScore) + { + bestScore = vectorPreferredMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("HILBERT - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("HILBERT - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("HILBERT - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("HILBERT - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("HILBERT - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mScalar.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVectorPreferred(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVectorPreferred.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVector512.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVector256.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVector128.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVector64.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/mixer/ComplexMixerCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/mixer/ComplexMixerCalibration.java new file mode 100644 index 00000000..9bc312fe --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/mixer/ComplexMixerCalibration.java @@ -0,0 +1,141 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.mixer; + +import io.github.dsheirer.dsp.mixer.ScalarComplexMixer; +import io.github.dsheirer.dsp.mixer.VectorComplexMixer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibrates the complex mixer implementations to determine the optimal instance. + */ +public class ComplexMixerCalibration extends Calibration +{ + private static final double FREQUENCY = 2.0; + private static final double SAMPLE_RATE = 10.0; + private static final int SAMPLE_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + private ScalarComplexMixer mScalarComplexMixer = new ScalarComplexMixer(FREQUENCY, SAMPLE_RATE); + private VectorComplexMixer mVectorComplexMixer = new VectorComplexMixer(FREQUENCY, SAMPLE_RATE); + + /** + * Constructs an instance + */ + public ComplexMixerCalibration() + { + super(CalibrationType.MIXER_COMPLEX); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(SAMPLE_SIZE); + float[] q = getFloatSamples(SAMPLE_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX MIXER WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX MIXER WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX MIXER - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX MIXER - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("COMPLEX MIXER - IMPLEMENTATION SET TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + long start = System.currentTimeMillis(); + double accumulator = 0.0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples mixed = mScalarComplexMixer.mix(i, q); + accumulator += mixed.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + long start = System.currentTimeMillis(); + double accumulator = 0.0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples mixed = mVectorComplexMixer.mix(i, q); + accumulator += mixed.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/ComplexOscillatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/ComplexOscillatorCalibration.java new file mode 100644 index 00000000..68a1e233 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/ComplexOscillatorCalibration.java @@ -0,0 +1,154 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.oscillator; + +import io.github.dsheirer.dsp.oscillator.IComplexOscillator; +import io.github.dsheirer.dsp.oscillator.ScalarComplexOscillator; +import io.github.dsheirer.dsp.oscillator.VectorComplexOscillator; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibration plugin for complex oscillators + */ +public class ComplexOscillatorCalibration extends Calibration +{ + private static final double FREQUENCY = 5.0d; + private static final double SAMPLE_RATE = 100.0d; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IComplexOscillator mScalarOscillator = new ScalarComplexOscillator(FREQUENCY, SAMPLE_RATE); + private IComplexOscillator mVectorOscillator = new VectorComplexOscillator(FREQUENCY, SAMPLE_RATE); + + + /** + * Constructs an instance + */ + public ComplexOscillatorCalibration() + { + super(CalibrationType.OSCILLATOR_COMPLEX); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(); + scalarMean.increment(score); + } + + mLog.info("COMPLEX OSCILLATOR WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(); + vectorMean.increment(score); + } + + mLog.info("COMPLEX OSCILLATOR WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(); + scalarMean.increment(score); + } + + mLog.info("COMPLEX OSCILLATOR - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(); + vectorMean.increment(score); + } + + mLog.info("COMPLEX OSCILLATOR - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("COMPLEX OSCILLATOR - SETTING OPTIMAL OPERATION TO:" + getImplementation()); + } + + /** + * Calculates the time duration needed to generate the sample buffers of the specified size and iteration count + * @return time duration in milliseconds + */ + private long testScalar() + { + float accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] generated = mScalarOscillator.generate(BUFFER_SIZE); + accumulator += generated[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + /** + * Calculates the time duration to process the sample buffer with the filter coefficients. + * @return time duration in milliseconds + */ + private long testVector() + { + float accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] generated = mVectorOscillator.generate(BUFFER_SIZE); + accumulator += generated[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/RealOscillatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/RealOscillatorCalibration.java new file mode 100644 index 00000000..ffa0d619 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/RealOscillatorCalibration.java @@ -0,0 +1,146 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.oscillator; + +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.ScalarRealOscillator; +import io.github.dsheirer.dsp.oscillator.VectorRealOscillator; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibration plugin for real oscillators + */ +public class RealOscillatorCalibration extends Calibration +{ + private static final double FREQUENCY = 5.0d; + private static final double SAMPLE_RATE = 100.0d; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealOscillator mScalar = new ScalarRealOscillator(FREQUENCY, SAMPLE_RATE); + private IRealOscillator mVector = new VectorRealOscillator(FREQUENCY, SAMPLE_RATE); + + /** + * Constructs an instance + */ + public RealOscillatorCalibration() + { + super(CalibrationType.OSCILLATOR_REAL); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(); + scalarMean.increment(score); + } + + mLog.info("REAL OSCILLATOR WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(); + vectorMean.increment(score); + } + + mLog.info("REAL OSCILLATOR WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + //Test begins + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(); + scalarMean.increment(score); + } + + mLog.info("REAL OSCILLATOR - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(); + vectorMean.increment(score); + } + + mLog.info("REAL OSCILLATOR - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("REAL OSCILLATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar() + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] generated = mScalar.generate(BUFFER_SIZE); + accumulator += generated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector() + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] generated = mVector.generate(BUFFER_SIZE); + accumulator += generated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/window/WindowCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/window/WindowCalibration.java new file mode 100644 index 00000000..68653620 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/window/WindowCalibration.java @@ -0,0 +1,144 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 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.vector.calibrate.window; + +import io.github.dsheirer.dsp.window.ScalarWindow; +import io.github.dsheirer.dsp.window.VectorWindow; +import io.github.dsheirer.dsp.window.Window; +import io.github.dsheirer.dsp.window.WindowFactory; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calculates the optimal (scalar vs vector) implementation for windowing samples. + */ +public class WindowCalibration extends Calibration +{ + private static final int WINDOW_SIZE = 8192; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + private Window mScalarWindow; + private Window mVectorWindow; + + /** + * Constructs an instance + */ + public WindowCalibration() + { + super(CalibrationType.WINDOW); + float[] window = WindowFactory.getBlackman(WINDOW_SIZE); + mScalarWindow = new ScalarWindow(window); + mVectorWindow = new VectorWindow(window); + } + + @Override public void calibrate() throws CalibrationException + { + Mean scalarMean = new Mean(); + + float[] samples = getFloatSamples(WINDOW_SIZE); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + scalarMean.increment(testScalar(samples)); + } + + mLog.info("WINDOW WARMUP - SCALAR: " + scalarMean.getResult()); + + Mean vectorMean = new Mean(); + + samples = getFloatSamples(WINDOW_SIZE); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + vectorMean.increment(testVector(samples)); + } + + mLog.info("WINDOW WARMUP - VECTOR: " + vectorMean.getResult()); + + //Test starts ... + scalarMean.clear(); + + samples = getFloatSamples(WINDOW_SIZE); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + scalarMean.increment(testScalar(samples)); + } + + mLog.info("WINDOW - SCALAR: " + scalarMean.getResult()); + + vectorMean.clear(); + + samples = getFloatSamples(WINDOW_SIZE); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + vectorMean.increment(testVector(samples)); + } + + mLog.info("WINDOW - VECTOR: " + vectorMean.getResult()); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("WINDOW - OPTIMAL IMPLEMENTATION SET TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + long start = System.currentTimeMillis(); + double accumulator = 0.0d; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + mScalarWindow.apply(samples); + accumulator += samples[3]; + count++; + } + + return count + (long)(accumulator * 0); + } + private long testVector(float[] samples) + { + long start = System.currentTimeMillis(); + double accumulator = 0.0d; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + mVectorWindow.apply(samples); + accumulator += samples[3]; + count++; + } + + return count + (long)(accumulator * 0); + } +}