From 70e62ebb35b27c60fdee0096defaca113fdba150 Mon Sep 17 00:00:00 2001 From: denny Date: Fri, 30 Oct 2015 20:49:22 -0400 Subject: [PATCH] Adds airspy USB tuner support and updates the spectral display component. -Airspy supported with 10 and 2.5 MSps sample rates. Additional decimation may be added later. -Spectral display updated to include smoothing and filtering options. Note: the decimated output of a tuner channel from the airspy is 48,076.9 samples per second, versus the normal 48 kHz decimated rate. This is 1.0015 times the normal rate and doesn't appear to impact the decoders or audio processing. Therefore, no fractional resampling has been added. --- config/52-airspy.rules | 1 + license/copyright_header.txt | 17 + src/audio/AudioChannelPanel.java | 1 - src/audio/AudioManager.java | 19 + src/audio/AudioModule.java | 3 +- src/audio/inverted/AudioAdapter.java | 2 +- src/audio/inverted/AudioInverter.java | 2 +- src/audio/output/AudioOutput.java | 1 - src/audio/output/StereoAudioOutput.java | 1 - src/dsp/filter/Filters.java | 1244 ++++++++-------- .../filter/cic/ComplexPrimeCICDecimate.java | 8 +- src/dsp/filter/dc/DCRemovalFilter.java | 14 +- ...Filter_RB.java => DCRemovalFilter_CB.java} | 55 +- src/dsp/filter/dc/DCRemovalFilter_RB.java | 38 +- src/dsp/filter/dc/DCRemovalFilter_R_R.java | 1 - .../dc/IIRSinglePoleDCRemovalFilter_RB.java | 25 +- .../halfband/real/HalfBandFilter_RB_RB.java | 30 +- src/dsp/filter/hilbert/HilbertTransform.java | 281 ++++ .../InterpolatingHilbertTransform.java | 281 ++++ .../smoothing/GaussianSmoothingFilter.java | 39 + .../filter/smoothing/NoSmoothingFilter.java | 31 + .../smoothing/RectangularSmoothingFilter.java | 49 + src/dsp/filter/smoothing/SmoothingFilter.java | 97 ++ .../smoothing/TriangularSmoothingFilter.java | 38 + src/{util => dsp/mixer}/Oscillator.java | 2 +- .../InstrumentableProcessingChain.java | 4 +- src/module/ProcessingChain.java | 2 +- src/module/decode/ltrnet/LTRCallEvent.java | 1 - src/module/decode/mpt1327/MPT1327Decoder.java | 1 - src/module/decode/p25/P25DecoderPanel.java | 1 - src/module/decode/p25/P25_LSMDecoder.java | 1 - .../decode/p25/audio/P25AudioModule.java | 2 +- src/module/decode/state/ChannelList.java | 3 +- .../decode/state/ChannelStatePanel.java | 6 - .../demodulate/fm/FMDemodulatorModule.java | 3 +- src/module/log/EventLogManager.java | 8 +- .../wave/ComplexBufferWaveRecorder.java | 3 +- src/record/wave/WaveWriter.java | 3 - src/sample/Buffer.java | 24 + src/sample/ConversionUtils.java | 3 +- src/sample/adapter/ByteSampleAdapter.java | 4 +- src/sample/adapter/ChannelShortAdapter.java | 2 +- ...SampleAdapter.java => ISampleAdapter.java} | 4 +- src/sample/adapter/ShortAdapter.java | 2 +- src/sample/complex/ComplexBuffer.java | 43 +- src/sample/real/RealBuffer.java | 39 +- src/settings/Settings.java | 4 +- src/settings/SettingsManager.java | 9 + src/source/mixer/ComplexMixer.java | 6 +- src/source/mixer/RealMixerSource.java | 6 +- src/source/tuner/MixerTuner.java | 6 +- src/source/tuner/Tuner.java | 5 + src/source/tuner/TunerChannelSource.java | 7 +- src/source/tuner/TunerClass.java | 5 + src/source/tuner/TunerManager.java | 38 +- src/source/tuner/TunerType.java | 1 + .../tuner/airspy/AirspyDeviceInformation.java | 159 +++ .../tuner/airspy/AirspyInformationPanel.java | 77 + .../tuner/airspy/AirspySampleAdapter.java | 149 ++ src/source/tuner/airspy/AirspyTuner.java | 168 +++ .../airspy/AirspyTunerConfiguration.java | 119 ++ .../airspy/AirspyTunerConfigurationPanel.java | 587 ++++++++ .../tuner/airspy/AirspyTunerController.java | 1257 +++++++++++++++++ .../tuner/airspy/AirspyTunerEditorPanel.java | 113 ++ src/source/tuner/ettus/B100Tuner.java | 6 + src/source/tuner/fcd/FCDTuner.java | 6 + src/source/tuner/hackrf/HackRFTuner.java | 25 +- .../tuner/hackrf/HackRFTunerController.java | 17 +- src/source/tuner/rtl/RTL2832Tuner.java | 6 + src/spectrum/ChannelSpectrumPanel.java | 127 +- src/spectrum/DFTProcessor.java | 24 +- src/spectrum/SpectralDisplayAdjuster.java | 29 +- src/spectrum/SpectralDisplayPanel.java | 35 +- src/spectrum/SpectrumPanel.java | 143 +- .../converter/ComplexDecibelConverter.java | 79 +- .../converter/DFTResultsConverter.java | 6 + .../converter/RealDecibelConverter.java | 55 +- src/spectrum/menu/AveragingItem.java | 7 +- .../{DBScaleItem.java => SmoothingItem.java} | 39 +- src/spectrum/menu/SmoothingTypeItem.java | 36 + 80 files changed, 4781 insertions(+), 1014 deletions(-) create mode 100644 config/52-airspy.rules create mode 100644 license/copyright_header.txt rename src/dsp/filter/dc/{AveragingDCRemovalFilter_RB.java => DCRemovalFilter_CB.java} (55%) create mode 100644 src/dsp/filter/hilbert/HilbertTransform.java create mode 100644 src/dsp/filter/hilbert/InterpolatingHilbertTransform.java create mode 100644 src/dsp/filter/smoothing/GaussianSmoothingFilter.java create mode 100644 src/dsp/filter/smoothing/NoSmoothingFilter.java create mode 100644 src/dsp/filter/smoothing/RectangularSmoothingFilter.java create mode 100644 src/dsp/filter/smoothing/SmoothingFilter.java create mode 100644 src/dsp/filter/smoothing/TriangularSmoothingFilter.java rename src/{util => dsp/mixer}/Oscillator.java (99%) create mode 100644 src/sample/Buffer.java rename src/sample/adapter/{SampleAdapter.java => ISampleAdapter.java} (93%) create mode 100644 src/source/tuner/airspy/AirspyDeviceInformation.java create mode 100644 src/source/tuner/airspy/AirspyInformationPanel.java create mode 100644 src/source/tuner/airspy/AirspySampleAdapter.java create mode 100644 src/source/tuner/airspy/AirspyTuner.java create mode 100644 src/source/tuner/airspy/AirspyTunerConfiguration.java create mode 100644 src/source/tuner/airspy/AirspyTunerConfigurationPanel.java create mode 100644 src/source/tuner/airspy/AirspyTunerController.java create mode 100644 src/source/tuner/airspy/AirspyTunerEditorPanel.java rename src/spectrum/menu/{DBScaleItem.java => SmoothingItem.java} (51%) create mode 100644 src/spectrum/menu/SmoothingTypeItem.java diff --git a/config/52-airspy.rules b/config/52-airspy.rules new file mode 100644 index 00000000..f7da199c --- /dev/null +++ b/config/52-airspy.rules @@ -0,0 +1 @@ +ATTR{idVendor}=="1d50", ATTR{idProduct}=="60a1", SYMLINK+="airspy-%k", MODE="660", GROUP="plugdev" \ No newline at end of file diff --git a/license/copyright_header.txt b/license/copyright_header.txt new file mode 100644 index 00000000..62c7ab8b --- /dev/null +++ b/license/copyright_header.txt @@ -0,0 +1,17 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java index ca793384..298e6b18 100644 --- a/src/audio/AudioChannelPanel.java +++ b/src/audio/AudioChannelPanel.java @@ -20,7 +20,6 @@ import settings.Setting; import settings.SettingChangeListener; import settings.SettingsManager; import alias.Alias; -import audio.AudioEvent.Type; import audio.metadata.AudioMetadata; import audio.metadata.Metadata; import audio.metadata.MetadataType; diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java index 8783e00e..39d1834a 100644 --- a/src/audio/AudioManager.java +++ b/src/audio/AudioManager.java @@ -1,6 +1,25 @@ package audio; +/******************************************************************************* + * 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 + ******************************************************************************/ + import java.util.ArrayList; + import java.util.Collections; import java.util.Comparator; import java.util.HashMap; diff --git a/src/audio/AudioModule.java b/src/audio/AudioModule.java index 2905e423..5a9c789a 100644 --- a/src/audio/AudioModule.java +++ b/src/audio/AudioModule.java @@ -18,7 +18,6 @@ import audio.squelch.SquelchState; import controller.channel.ChannelEvent; import controller.channel.IChannelEventListener; import dsp.filter.Filters; -import dsp.filter.dc.AveragingDCRemovalFilter_RB; import dsp.filter.dc.DCRemovalFilter_RB; import dsp.filter.fir.real.RealFIRFilter_R_R; @@ -73,7 +72,7 @@ public class AudioModule extends Module implements IAudioPacketProvider, if( mRemoveDC ) { - mDCRemovalFilter = new AveragingDCRemovalFilter_RB( DC_REMOVAL_RATIO ); + mDCRemovalFilter = new DCRemovalFilter_RB( DC_REMOVAL_RATIO ); mDCRemovalFilter.setListener( this ); } } diff --git a/src/audio/inverted/AudioAdapter.java b/src/audio/inverted/AudioAdapter.java index 3c4b737b..f529fb06 100644 --- a/src/audio/inverted/AudioAdapter.java +++ b/src/audio/inverted/AudioAdapter.java @@ -17,10 +17,10 @@ ******************************************************************************/ package audio.inverted; -import util.Oscillator; import dsp.filter.FilterFactory; import dsp.filter.FloatFIRFilter; import dsp.filter.Window.WindowType; +import dsp.mixer.Oscillator; /** * Based on the AudioType applied, produces normal, muted, or audio inverted diff --git a/src/audio/inverted/AudioInverter.java b/src/audio/inverted/AudioInverter.java index c3b22236..e14dbb15 100644 --- a/src/audio/inverted/AudioInverter.java +++ b/src/audio/inverted/AudioInverter.java @@ -19,9 +19,9 @@ package audio.inverted; import audio.InversionFrequency; import sample.real.RealSampleListener; -import util.Oscillator; import dsp.filter.Filters; import dsp.filter.FloatFIRFilter; +import dsp.mixer.Oscillator; /** * Applies audio inversion (or un-inversion) to a stream of float audio samples diff --git a/src/audio/output/AudioOutput.java b/src/audio/output/AudioOutput.java index 6c831b24..3b0abed8 100644 --- a/src/audio/output/AudioOutput.java +++ b/src/audio/output/AudioOutput.java @@ -4,7 +4,6 @@ import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; -import javax.sound.sampled.BooleanControl; import javax.sound.sampled.FloatControl; import org.slf4j.Logger; diff --git a/src/audio/output/StereoAudioOutput.java b/src/audio/output/StereoAudioOutput.java index bd1d8338..2a7b9dad 100644 --- a/src/audio/output/StereoAudioOutput.java +++ b/src/audio/output/StereoAudioOutput.java @@ -10,7 +10,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.sound.sampled.BooleanControl; -import javax.sound.sampled.Control; import javax.sound.sampled.FloatControl; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; diff --git a/src/dsp/filter/Filters.java b/src/dsp/filter/Filters.java index 0381e5ea..4a015bf9 100644 --- a/src/dsp/filter/Filters.java +++ b/src/dsp/filter/Filters.java @@ -1086,616 +1086,640 @@ public enum Filters .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 - } ); + 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 + } ); float[] mCoefficients; diff --git a/src/dsp/filter/cic/ComplexPrimeCICDecimate.java b/src/dsp/filter/cic/ComplexPrimeCICDecimate.java index 33ee6149..80b0c206 100644 --- a/src/dsp/filter/cic/ComplexPrimeCICDecimate.java +++ b/src/dsp/filter/cic/ComplexPrimeCICDecimate.java @@ -31,7 +31,7 @@ import dsp.filter.Window.WindowType; import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; import dsp.filter.halfband.complex.HalfBandFilter_CB_CB; -public class ComplexPrimeCICDecimate +public class ComplexPrimeCICDecimate implements Listener { public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53, 59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151, @@ -163,6 +163,7 @@ public class ComplexPrimeCICDecimate /** * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc. */ + @Override public void receive( ComplexBuffer buffer ) { if( mFirstDecimatingStage != null ) @@ -343,9 +344,8 @@ public class ComplexPrimeCICDecimate /** - * 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. + * Output adapter - applies gain correction and cleanup filter and broadcast + * to registered listener. */ public class Output implements ComplexSampleListener { diff --git a/src/dsp/filter/dc/DCRemovalFilter.java b/src/dsp/filter/dc/DCRemovalFilter.java index ef2fadf2..292f2925 100644 --- a/src/dsp/filter/dc/DCRemovalFilter.java +++ b/src/dsp/filter/dc/DCRemovalFilter.java @@ -17,18 +17,16 @@ ******************************************************************************/ package dsp.filter.dc; -public abstract class DCRemovalFilter +public class DCRemovalFilter { - private float mAverage; - private float mRatio; + protected float mAverage; + protected float mRatio; public DCRemovalFilter( float ratio ) { mRatio = ratio; } - public abstract void dispose(); - public void reset() { mAverage = 0.0f; @@ -36,8 +34,8 @@ public abstract class DCRemovalFilter public float filter( float sample ) { - mAverage += mRatio * ( sample - mAverage ); - - return sample - mAverage; + float filtered = sample - mAverage; + mAverage += mRatio * filtered; + return filtered; } } \ No newline at end of file diff --git a/src/dsp/filter/dc/AveragingDCRemovalFilter_RB.java b/src/dsp/filter/dc/DCRemovalFilter_CB.java similarity index 55% rename from src/dsp/filter/dc/AveragingDCRemovalFilter_RB.java rename to src/dsp/filter/dc/DCRemovalFilter_CB.java index a2fc95f4..201634fe 100644 --- a/src/dsp/filter/dc/AveragingDCRemovalFilter_RB.java +++ b/src/dsp/filter/dc/DCRemovalFilter_CB.java @@ -18,17 +18,18 @@ package dsp.filter.dc; import sample.Listener; -import sample.real.RealBuffer; +import sample.complex.ComplexBuffer; -public class AveragingDCRemovalFilter_RB extends DCRemovalFilter_RB - implements Listener +public class DCRemovalFilter_CB implements Listener { - private float mAverage; - private float mRatio; - - public AveragingDCRemovalFilter_RB( float ratio ) + private Listener mListener; + private DCRemovalFilter mIFilter; + private DCRemovalFilter mQFilter; + + public DCRemovalFilter_CB( float ratio ) { - mRatio = ratio; + mIFilter = new DCRemovalFilter( ratio ); + mQFilter = new DCRemovalFilter( ratio ); } public void dispose() @@ -36,31 +37,35 @@ public class AveragingDCRemovalFilter_RB extends DCRemovalFilter_RB mListener = null; } - public void reset() + public float[] filter( float[] samples ) { - mAverage = 0.0f; - } + for( int x = 0; x < samples.length; x += 2 ) + { + samples[ x ] = mIFilter.filter( samples[ x ] ); + samples[ x + 1 ] = mQFilter.filter( samples[ x + 1 ] ); + } - public float filter( float sample ) - { - mAverage += mRatio * ( sample - mAverage ); + return samples; + } - return sample - mAverage; + public ComplexBuffer filter( ComplexBuffer buffer ) + { + filter( buffer.getSamples() ); + + return buffer; } - + @Override - public void receive( RealBuffer buffer ) + public void receive( ComplexBuffer buffer ) { if( mListener != null ) { - float[] samples = buffer.getSamples(); - - for( int x = 0; x < samples.length; x++ ) - { - samples[ x ] = filter( samples[ x ] ); - } - - mListener.receive( buffer ); + mListener.receive( filter( buffer ) ); } } + + public void setListener( Listener listener ) + { + mListener = listener; + } } diff --git a/src/dsp/filter/dc/DCRemovalFilter_RB.java b/src/dsp/filter/dc/DCRemovalFilter_RB.java index 3b7edd66..07ef4582 100644 --- a/src/dsp/filter/dc/DCRemovalFilter_RB.java +++ b/src/dsp/filter/dc/DCRemovalFilter_RB.java @@ -3,13 +3,14 @@ package dsp.filter.dc; import sample.Listener; import sample.real.RealBuffer; -public abstract class DCRemovalFilter_RB implements Listener +public class DCRemovalFilter_RB extends DCRemovalFilter + implements Listener { protected Listener mListener; - public DCRemovalFilter_RB() + public DCRemovalFilter_RB( float ratio ) { - + super( ratio ); } public void setListener( Listener listener ) @@ -27,5 +28,34 @@ public abstract class DCRemovalFilter_RB implements Listener mListener = null; } - public abstract void reset(); + public float[] filter( float[] samples ) + { + for( int x = 0; x < samples.length; x++ ) + { + samples[ x ] = filter( samples[ x ] ); + } + + return samples; + } + + public RealBuffer filter( RealBuffer buffer ) + { + float[] samples = buffer.getSamples(); + + for( int x = 0; x < samples.length; x++ ) + { + samples[ x ] = filter( samples[ x ] ); + } + + return buffer; + } + + @Override + public void receive( RealBuffer buffer ) + { + if( mListener != null ) + { + mListener.receive( filter( buffer ) ); + } + } } diff --git a/src/dsp/filter/dc/DCRemovalFilter_R_R.java b/src/dsp/filter/dc/DCRemovalFilter_R_R.java index de4fd627..c2c8f1be 100644 --- a/src/dsp/filter/dc/DCRemovalFilter_R_R.java +++ b/src/dsp/filter/dc/DCRemovalFilter_R_R.java @@ -28,7 +28,6 @@ public class DCRemovalFilter_R_R extends DCRemovalFilter implements RealSampleLi super( ratio ); } - @Override public void dispose() { mListener = null; diff --git a/src/dsp/filter/dc/IIRSinglePoleDCRemovalFilter_RB.java b/src/dsp/filter/dc/IIRSinglePoleDCRemovalFilter_RB.java index c4f4a371..b52f6912 100644 --- a/src/dsp/filter/dc/IIRSinglePoleDCRemovalFilter_RB.java +++ b/src/dsp/filter/dc/IIRSinglePoleDCRemovalFilter_RB.java @@ -1,12 +1,14 @@ package dsp.filter.dc; +import sample.Listener; import sample.real.RealBuffer; -public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB +public class IIRSinglePoleDCRemovalFilter_RB implements Listener { private float mAlpha; private float mPreviousInput = 0.0f; private float mPreviousOutput = 0.0f; + private Listener mListener; /** * IIR single-pole DC removal filter, as described by J M de Freitas in @@ -24,6 +26,14 @@ public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB @Override public void receive( RealBuffer buffer ) + { + if( mListener != null ) + { + mListener.receive( filter( buffer ) ); + } + } + + public RealBuffer filter( RealBuffer buffer ) { float[] samples = buffer.getSamples(); @@ -31,11 +41,8 @@ public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB { samples[ x ] = filter( samples[ x ] ); } - - if( mListener != null ) - { - mListener.receive( buffer ); - } + + return buffer; } public float filter( float sample ) @@ -49,10 +56,14 @@ public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB return currentOutput; } - @Override public void reset() { mPreviousInput = 0.0f; mPreviousOutput = 0.0f; } + + public void setListener( Listener listener ) + { + mListener = listener; + } } diff --git a/src/dsp/filter/halfband/real/HalfBandFilter_RB_RB.java b/src/dsp/filter/halfband/real/HalfBandFilter_RB_RB.java index 90414814..fe673d48 100644 --- a/src/dsp/filter/halfband/real/HalfBandFilter_RB_RB.java +++ b/src/dsp/filter/halfband/real/HalfBandFilter_RB_RB.java @@ -10,10 +10,21 @@ public class HalfBandFilter_RB_RB extends HalfBandFilter implements Listener mListener; /** + * Halfband filter for real buffers with optional decimation by two. * - * @param coefficients - * @param gain - * @param decimate + * If this filter is used in non-decimating mode, the filter will overwrite + * the inbound sample buffer with the filtered samples. This will cause + * issues if the buffers are being used across several processes are sharing + * the buffer, since this filter will modify the contents of the object, so + * consider making a copy of the buffer prior to feeding the buffer to this + * filter. + * + * If used in decimating mode, a new (half-length) buffer will be created + * for the filtered samples. + * + * @param coefficients - filter kernel + * @param gain - gain to apply to the outputs + * @param decimate - true for decimate by 2, or false for no decimation */ public HalfBandFilter_RB_RB( float[] coefficients, float gain, boolean decimate ) { @@ -28,12 +39,18 @@ public class HalfBandFilter_RB_RB extends HalfBandFilter implements Listener listener ) diff --git a/src/dsp/filter/hilbert/HilbertTransform.java b/src/dsp/filter/hilbert/HilbertTransform.java new file mode 100644 index 00000000..5b5c0b77 --- /dev/null +++ b/src/dsp/filter/hilbert/HilbertTransform.java @@ -0,0 +1,281 @@ +package dsp.filter.hilbert; + +/******************************************************************************* + * 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 + ******************************************************************************/ + +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.Listener; +import sample.complex.ComplexBuffer; +import sample.real.RealBuffer; +import dsp.filter.Filters; + +public class HilbertTransform implements Listener +{ + private static final Logger mLog = LoggerFactory.getLogger( HilbertTransform.class ); + + private float[] mHilbertFilter; + + private Listener mListener; + + 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 HilbertTransform() + { + convertHalfBandToHilbert( Filters.HALF_BAND_FILTER_47T.getCoefficients() ); + + mBufferSize = mHilbertFilter.length + 1; + mBuffer = new float[ mBufferSize ]; + + mCenterCoefficent = mHilbertFilter[ mHilbertFilter.length / 2 ]; + + generateIndexMap( mHilbertFilter.length ); + } + + @Override + public void receive( RealBuffer buffer ) + { + if( mListener != null ) + { + mListener.receive( filter( buffer ) ); + } + } + + /** + * Sets the listener to receive filtered complex buffers. + */ + public void setListener( Listener listener ) + { + mListener = listener; + } + + /** + * Applies Hilbert transform to the real sample buffer and returns a + * complex sample buffer + */ + public ComplexBuffer filter( RealBuffer buffer ) + { + return new ComplexBuffer( filter( buffer.getSamples() ) ); + } + + /** + * 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 ] = -( mCenterCoefficent * + mBuffer[ mIndexMap[ index ][ mCenterTapIndex ] ] ); + + //quadrature + samples[ y + 1 ] = -accumulator; + } + else + { + //inphase + samples[ y ] = mCenterCoefficent * + 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. + */ + 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 ] = -Math.abs( coefficients[ x ] ); + } + else if( x > middle ) + { + mHilbertFilter[ x ] = Math.abs( coefficients[ x ] ); + } + else + { + mHilbertFilter[ x ] = coefficients[ x ]; + } + } + } +} diff --git a/src/dsp/filter/hilbert/InterpolatingHilbertTransform.java b/src/dsp/filter/hilbert/InterpolatingHilbertTransform.java new file mode 100644 index 00000000..e54f3d07 --- /dev/null +++ b/src/dsp/filter/hilbert/InterpolatingHilbertTransform.java @@ -0,0 +1,281 @@ +package dsp.filter.hilbert; + +/******************************************************************************* + * 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 + * + * ---------------------------------------------------------------------------- + * 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. + ******************************************************************************/ + +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.Listener; +import sample.complex.ComplexBuffer; +import sample.real.RealBuffer; + +public class InterpolatingHilbertTransform implements Listener +{ + private static final Logger mLog = LoggerFactory.getLogger( InterpolatingHilbertTransform.class ); + + private static final float[] HALF_BAND_FILTER = + { + -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 + }; + + private static final float GAIN = 2.0f; + + private Listener mListener; + + private float[] mBuffer; + private int mBufferSize; + private int mBufferPointer; + private int mGroupDelayedInphaseTapPointIndex; + + private float[] mCoefficients; + private int[][] mIndexMap; + + + /** + * 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 InterpolatingHilbertTransform() + { + mBufferSize = HALF_BAND_FILTER.length; + mBuffer = new float[ mBufferSize ]; + + createHilbertCoefficients(); + + generateIndexMap( mBufferSize ); + } + + @Override + public void receive( RealBuffer buffer ) + { + if( mListener != null ) + { + mListener.receive( filter( buffer ) ); + } + } + + /** + * Sets the listener to receive filtered complex buffers. + */ + public void setListener( Listener listener ) + { + mListener = listener; + } + + /** + * Applies Hilbert transform to the real sample buffer and returns a + * complex sample buffer + */ + public ComplexBuffer filter( RealBuffer buffer ) + { + return new ComplexBuffer( filter( buffer.getSamples() ) ); + } + + /** + * Applies a hilbert transform to an array of real samples and returns an + * array twice as long containing I,Q,I,Q ... complex samples + */ + public float[] filter( float[] samples ) + { + float[] filtered = new float[ samples.length * 2 ]; + + float accumulator; + + for( int y = 0; y < samples.length; y++ ) + { + insert( samples[ y ] ); + + accumulator = 0.0f; + + for( int x = 0; x < mCoefficients.length; x++ ) + { + accumulator += mCoefficients[ x ] * + mBuffer[ mIndexMap[ mBufferPointer ][ x ] ] ; + } + + int index = y * 2; + + /* We use the buffered sample at the half group delay point as the + * Inphase value and the accumulator as the quadrature value */ + filtered[ index ] = mBuffer[ mIndexMap[ mBufferPointer ] + [ mGroupDelayedInphaseTapPointIndex ] ]; + + filtered[ index + 1 ] = accumulator; + } + + return filtered; + } + + /** + * Inserts the sample into the circular buffer, overwriting the oldest value + */ + private void insert( float sample ) + { + mBuffer[ mBufferPointer ] = sample; + + mBufferPointer++; + + mBufferPointer = mBufferPointer % mBufferSize; + } + + + /** + * Creates an N x (N + 1 / 2) index map enabling quick access to the + * circular buffer samples to support convolution. + * + * As the buffer pointer shifts right, as each sample is added, we use the + * index map pointed to by the buffer pointer, so that the filter coefficients + * are pointing to the correct circular buffer samples. + * + * We add an extra index to each index map with a value that points to the + * current index of the middle sample in the buffer, so that we can retrieve + * that value as the half-group delayed sample that forms the inphase + * component. + */ + private void generateIndexMap( int size ) + { + int mapWidth = ( ( size + 1 ) / 2 ) + 1; + + int center = ( size - 1 ) / 2; + + //Set center tap index for coefficients array + mGroupDelayedInphaseTapPointIndex = center + 1; + + mIndexMap = new int[ size ][ mapWidth ]; + + //Setup the first row with buffer pointer index 0 as a starting point + for( int x = 0; x < mapWidth - 1; x++ ) + { + mIndexMap[ 0 ][ x ] = size - 1 - ( x * 2 ); + } + + //Place the group delayed in-phase sample tap point in last element of + //the first row + mIndexMap[ 0 ][ mGroupDelayedInphaseTapPointIndex ] = center; + + //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; + } + } + } + } + + public void logIndexMap() + { + for( int[] indexSet: mIndexMap ) + { + mLog.debug( "Row:" + Arrays.toString( indexSet ) ); + } + } + + /** + * Converts the half-band filter coefficients into a packed set of hilbert + * coefficients by removing all of the odd index, zero-valued coefficients + * and the center 0.5 coefficient. Then, all coefficients left of the + * original center are made negative and all coefficients right of the + * original center are made positive. These coefficient sign modifications + * effectively incorporate a frequency translation by FS/4 into the hilbert + * coefficients. A gain of 2.0 is applied to the coefficients to offset the + * gain of 0.5 induced by the filter. + */ + private void createHilbertCoefficients() + { + int half = ( HALF_BAND_FILTER.length + 1 ) / 2; + int middle = half / 2; + + mCoefficients = new float[ half ]; + + /* Pack all of the even numbered HB coefficients into our coefficient + * array and pre-apply a gain of two to offset the loss in the filter */ + for( int x = 0; x < half; x++ ) + { + mCoefficients[ x ] = HALF_BAND_FILTER[ x * 2 ] * GAIN; + + /* Make all of the left of center coefficients negative */ + if( x < middle && mCoefficients[ x ] > 0.0f ) + { + mCoefficients[ x ] = -mCoefficients[ x ]; + } + /* Make all of the right of center coefficients positive */ + else if( x >= middle && mCoefficients[ x ] < 0.0f ) + { + mCoefficients[ x ] = -mCoefficients[ x ]; + } + } + } +} diff --git a/src/dsp/filter/smoothing/GaussianSmoothingFilter.java b/src/dsp/filter/smoothing/GaussianSmoothingFilter.java new file mode 100644 index 00000000..d5cd52f7 --- /dev/null +++ b/src/dsp/filter/smoothing/GaussianSmoothingFilter.java @@ -0,0 +1,39 @@ +package dsp.filter.smoothing; + +import java.util.HashMap; +import java.util.Map; + +public class GaussianSmoothingFilter extends SmoothingFilter +{ + private static Map mMap = new HashMap<>(); + + static + { + mMap.put( 3, new float[] { 0.1842153217f,0.6315693566f,0.1842153217f } ); + mMap.put( 5, new float[] { 0.0646709835f,0.2446878423f,0.3812823483f,0.2446878423f,0.0646709835f } ); + mMap.put( 7, new float[] { 0.0355966134f,0.1103628062f,0.2176061504f,0.27286886f,0.2176061504f,0.1103628062f,0.0355966134f } ); + mMap.put( 9, new float[] { 0.0237625774f,0.061955345f,0.1228439993f,0.185233293f,0.2124095706f,0.185233293f,0.1228439993f,0.061955345f,0.0237625774f } ); + mMap.put( 11, new float[] { 0.0175873706f,0.040124091f,0.0762094006f,0.120506627f,0.1586398759f,0.1738652697f,0.1586398759f,0.120506627f,0.0762094006f,0.040124091f,0.0175873706f } ); + mMap.put( 13, new float[] { 0.0138646514f,0.0285342951f,0.0515030495f,0.0815279159f,0.1131845993f,0.137808478f,0.1471540217f,0.137808478f,0.1131845993f,0.0815279159f,0.0515030495f,0.0285342951f,0.0138646514f } ); + mMap.put( 15, new float[] { 0.011400054f,0.0216349783f,0.0372047506f,0.0579739252f,0.0818576722f,0.1047317717f,0.1214199044f,0.1275538873f,0.1214199044f,0.1047317717f,0.0818576722f,0.0579739252f,0.0372047506f,0.0216349783f,0.011400054f } ); + mMap.put( 17, new float[] { 0.0096581767f,0.0171732764f,0.0282802625f,0.0431306476f,0.0609201251f,0.07969074f,0.0965444243f,0.1083225312f,0.1125596326f,0.1083225312f,0.0965444243f,0.07969074f,0.0609201251f,0.0431306476f,0.0282802625f,0.0171732764f,0.0096581767f } ); + mMap.put( 19, new float[] { 0.0083665107f,0.0141035977f,0.0223580953f,0.0333318211f,0.0467307368f,0.0616120364f,0.0763919939f,0.0890736966f,0.097672084f,0.1007188551f,0.097672084f,0.0890736966f,0.0763919939f,0.0616120364f,0.0467307368f,0.0333318211f,0.0223580953f,0.0141035977f,0.0083665107f } ); + mMap.put( 21, new float[] { 0.0073728906f,0.0118884085f,0.0182292596f,0.0265811467f,0.0368585098f,0.0486027908f,0.0609458331f,0.0726751947f,0.0824114898f,0.0888686744f,0.091131604f,0.0888686744f,0.0824114898f,0.0726751947f,0.0609458331f,0.0486027908f,0.0368585098f,0.0265811467f,0.0182292596f,0.0118884085f,0.0073728906f } ); + mMap.put( 23, new float[] { 0.0065861356f,0.010228416f,0.0152327635f,0.0217541246f,0.0297918261f,0.0391241787f,0.0492703835f,0.0595003129f,0.0689041144f,0.0765180086f,0.0814844543f,0.0832105633f,0.0814844543f,0.0765180086f,0.0689041144f,0.0595003129f,0.0492703835f,0.0391241787f,0.0297918261f,0.0217541246f,0.0152327635f,0.010228416f,0.0065861356f } ); + mMap.put( 25, new float[] { 0.0059484873f,0.0089460248f,0.0129850288f,0.0181905149f,0.0245944051f,0.0320934871f,0.0404191102f,0.0491298959f,0.0576360481f,0.0652577058f,0.0713113508f,0.07520986f,0.076556162f,0.07520986f,0.0713113508f,0.0652577058f,0.0576360481f,0.0491298959f,0.0404191102f,0.0320934871f,0.0245944051f,0.0181905149f,0.0129850288f,0.0089460248f,0.0059484873f } ); + mMap.put( 27, new float[] { 0.0054216802f,0.007930248f,0.0112519401f,0.0154865908f,0.0206762609f,0.0267778655f,0.0336409056f,0.0409965368f,0.0484634619f,0.0555737199f,0.0618176187f,0.0667026128f,0.0698170011f,0.0708871156f,0.0698170011f,0.0667026128f,0.0618176187f,0.0555737199f,0.0484634619f,0.0409965368f,0.0336409056f,0.0267778655f,0.0206762609f,0.0154865908f,0.0112519401f,0.007930248f,0.0054216802f } ); + mMap.put( 29, new float[] { 0.0049794093f,0.0071086836f,0.0098843442f,0.013386093f,0.0176566017f,0.022683377f,0.0283828243f,0.0345900198f,0.0410575774f,0.0474660547f,0.0534466215f,0.0586144488f,0.0626089499f,0.0651351564f,0.0659996766f,0.0651351564f,0.0626089499f,0.0586144488f,0.0534466215f,0.0474660547f,0.0410575774f,0.0345900198f,0.0283828243f,0.022683377f,0.0176566017f,0.013386093f,0.0098843442f,0.0071086836f,0.0049794093f } ); + mMap.put( 31, new float[] { 0.0046030224f,0.0064323659f,0.0087836656f,0.0117208269f,0.0152833344f,0.0194740097f,0.0242476721f,0.0295027244f,0.0350777394f,0.0407547727f,0.0462703471f,0.0513339215f,0.0556523529f,0.0589576307f,0.0610342929f,0.0617426429f,0.0610342929f,0.0589576307f,0.0556523529f,0.0513339215f,0.0462703471f,0.0407547727f,0.0350777394f,0.0295027244f,0.0242476721f,0.0194740097f,0.0152833344f,0.0117208269f,0.0087836656f,0.0064323659f,0.0046030224f } ); + } + + public GaussianSmoothingFilter() + { + super( mMap, 3 ); + } + + @Override + public SmoothingType getSmoothingType() + { + return SmoothingType.GAUSSIAN; + } +} diff --git a/src/dsp/filter/smoothing/NoSmoothingFilter.java b/src/dsp/filter/smoothing/NoSmoothingFilter.java new file mode 100644 index 00000000..e7f09ea7 --- /dev/null +++ b/src/dsp/filter/smoothing/NoSmoothingFilter.java @@ -0,0 +1,31 @@ +package dsp.filter.smoothing; + + +public class NoSmoothingFilter extends SmoothingFilter +{ + public NoSmoothingFilter() + { + super( null, 3 ); + } + + public float[] filter( float[] data ) + { + return data; + } + + public static float[] getCoefficients( int points ) + { + return null; + } + + public int getPointSize() + { + return SMOOTHING_DEFAULT; + } + + @Override + public SmoothingType getSmoothingType() + { + return SmoothingType.NONE; + } +} diff --git a/src/dsp/filter/smoothing/RectangularSmoothingFilter.java b/src/dsp/filter/smoothing/RectangularSmoothingFilter.java new file mode 100644 index 00000000..4ae92726 --- /dev/null +++ b/src/dsp/filter/smoothing/RectangularSmoothingFilter.java @@ -0,0 +1,49 @@ +package dsp.filter.smoothing; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class RectangularSmoothingFilter extends SmoothingFilter +{ + private static Map mMap = new HashMap<>(); + + static + { + mMap.put( 3, getCoefficients( 3 ) ); + mMap.put( 5, getCoefficients( 5 ) ); + mMap.put( 7, getCoefficients( 7 ) ); + mMap.put( 9, getCoefficients( 9 ) ); + mMap.put( 11, getCoefficients( 11 ) ); + mMap.put( 13, getCoefficients( 13 ) ); + mMap.put( 15, getCoefficients( 15 ) ); + mMap.put( 17, getCoefficients( 17 ) ); + mMap.put( 19, getCoefficients( 19 ) ); + mMap.put( 21, getCoefficients( 21 ) ); + mMap.put( 23, getCoefficients( 23 ) ); + mMap.put( 25, getCoefficients( 25 ) ); + mMap.put( 27, getCoefficients( 27 ) ); + mMap.put( 29, getCoefficients( 29 ) ); + mMap.put( 31, getCoefficients( 31 ) ); + } + + public RectangularSmoothingFilter() + { + super( mMap, 3 ); + } + + public static float[] getCoefficients( int points ) + { + float[] coefficients = new float[ points ]; + + Arrays.fill( coefficients, 1.0f / (float)points ); + + return coefficients; + } + + @Override + public SmoothingType getSmoothingType() + { + return SmoothingType.RECTANGLE; + } +} diff --git a/src/dsp/filter/smoothing/SmoothingFilter.java b/src/dsp/filter/smoothing/SmoothingFilter.java new file mode 100644 index 00000000..b9e7a953 --- /dev/null +++ b/src/dsp/filter/smoothing/SmoothingFilter.java @@ -0,0 +1,97 @@ +package dsp.filter.smoothing; + +import java.util.Map; +import java.util.Set; + +public abstract class SmoothingFilter +{ + public static final int SMOOTHING_MINIMUM = 3; + public static final int SMOOTHING_MAXIMUM = 29; + public static final int SMOOTHING_DEFAULT = 9; + + public enum SmoothingType + { + NONE, + RECTANGLE, + TRIANGLE, + GAUSSIAN; + } + + private Map mCoefficientMap; + private float[] mCoefficients; + private float[] mNewCoefficients; + private boolean mNewCoefficientsAvailable; + + public SmoothingFilter( Map coefficients, int index ) + { + mCoefficientMap = coefficients; + + if( mCoefficientMap != null ) + { + mCoefficients = mCoefficientMap.get( index ); + } + } + + public abstract SmoothingType getSmoothingType(); + + public Set getPointSizeList() + { + return mCoefficientMap.keySet(); + } + + public int getPointSize() + { + return mCoefficients.length; + } + + public void setPointSize( int pointSize ) + { + if( mCoefficientMap != null ) + { + if( mCoefficientMap.containsKey( pointSize ) ) + { + mNewCoefficients = mCoefficientMap.get( pointSize ); + mNewCoefficientsAvailable = true; + } + else + { + throw new IllegalArgumentException( "Point size [" + pointSize + + "] is not valid in the current coefficient map" ); + } + } + } + + public float[] filter( float[] data ) + { + if( mNewCoefficientsAvailable ) + { + mCoefficients = mNewCoefficients; + mNewCoefficientsAvailable = false; + } + + int middle = mCoefficients.length / 2; + + float[] filtered = new float[ data.length ]; + + int toCopy = middle; + + System.arraycopy( data, 0, filtered, 0, toCopy ); + System.arraycopy( data, data.length - toCopy, filtered, filtered.length - toCopy, toCopy ); + + float accumulator; + + for( int x = 0; x < data.length - mCoefficients.length + 1; x++ ) + { + accumulator = 0.0f; + + for( int y = 0; y < mCoefficients.length; y++ ) + { + accumulator += data[ x + y ] * mCoefficients[ y ]; + } + + filtered[ x + middle ] = accumulator; + } + + return filtered; + } +} diff --git a/src/dsp/filter/smoothing/TriangularSmoothingFilter.java b/src/dsp/filter/smoothing/TriangularSmoothingFilter.java new file mode 100644 index 00000000..29bdc9c7 --- /dev/null +++ b/src/dsp/filter/smoothing/TriangularSmoothingFilter.java @@ -0,0 +1,38 @@ +package dsp.filter.smoothing; + +import java.util.HashMap; +import java.util.Map; + +public class TriangularSmoothingFilter extends SmoothingFilter +{ + private static Map mMap = new HashMap<>(); + + static + { + mMap.put( 3, new float[]{ 0.25f,0.5f,0.25f } ); + mMap.put( 5, new float[]{ 0.1111111111f,0.2222222222f,0.3333333333f,0.2222222222f,0.1111111111f } ); + mMap.put( 7, new float[]{ 0.0625f,0.125f,0.1875f,0.25f,0.1875f,0.125f,0.0625f } ); + mMap.put( 9, new float[]{ 0.04f,0.08f,0.12f,0.16f,0.2f,0.16f,0.12f,0.08f,0.04f } ); + mMap.put( 11, new float[]{ 0.0277777778f,0.0555555556f,0.0833333333f,0.1111111111f,0.1388888889f,0.1666666667f,0.1388888889f,0.1111111111f,0.0833333333f,0.0555555556f,0.0277777778f } ); + mMap.put( 13, new float[]{ 0.0204081633f,0.0408163265f,0.0612244898f,0.0816326531f,0.1020408163f,0.1224489796f,0.1428571429f,0.1224489796f,0.1020408163f,0.0816326531f,0.0612244898f,0.0408163265f,0.0204081633f } ); + mMap.put( 15, new float[]{ 0.015625f,0.03125f,0.046875f,0.0625f,0.078125f,0.09375f,0.109375f,0.125f,0.109375f,0.09375f,0.078125f,0.0625f,0.046875f,0.03125f,0.015625f } ); + mMap.put( 17, new float[]{ 0.012345679f,0.024691358f,0.037037037f,0.049382716f,0.0617283951f,0.0740740741f,0.0864197531f,0.0987654321f,0.1111111111f,0.0987654321f,0.0864197531f,0.0740740741f,0.0617283951f,0.049382716f,0.037037037f,0.024691358f,0.012345679f } ); + mMap.put( 19, new float[]{ 0.01f,0.02f,0.03f,0.04f,0.05f,0.06f,0.07f,0.08f,0.09f,0.1f,0.09f,0.08f,0.07f,0.06f,0.05f,0.04f,0.03f,0.02f,0.01f } ); + mMap.put( 21, new float[]{ 0.0082644628f,0.0165289256f,0.0247933884f,0.0330578512f,0.041322314f,0.0495867769f,0.0578512397f,0.0661157025f,0.0743801653f,0.0826446281f,0.0909090909f,0.0826446281f,0.0743801653f,0.0661157025f,0.0578512397f,0.0495867769f,0.041322314f,0.0330578512f,0.0247933884f,0.0165289256f,0.0082644628f } ); + mMap.put( 23, new float[]{ 0.0069444444f,0.0138888889f,0.0208333333f,0.0277777778f,0.0347222222f,0.0416666667f,0.0486111111f,0.0555555556f,0.0625f,0.0694444444f,0.0763888889f,0.0833333333f,0.0763888889f,0.0694444444f,0.0625f,0.0555555556f,0.0486111111f,0.0416666667f,0.0347222222f,0.0277777778f,0.0208333333f,0.0138888889f,0.0069444444f } ); + mMap.put( 25, new float[]{ 0.0059171598f,0.0118343195f,0.0177514793f,0.0236686391f,0.0295857988f,0.0355029586f,0.0414201183f,0.0473372781f,0.0532544379f,0.0591715976f,0.0650887574f,0.0710059172f,0.0769230769f,0.0710059172f,0.0650887574f,0.0591715976f,0.0532544379f,0.0473372781f,0.0414201183f,0.0355029586f,0.0295857988f,0.0236686391f,0.0177514793f,0.0118343195f,0.0059171598f } ); + mMap.put( 27, new float[]{ 0.0051020408f,0.0102040816f,0.0153061224f,0.0204081633f,0.0255102041f,0.0306122449f,0.0357142857f,0.0408163265f,0.0459183673f,0.0510204082f,0.056122449f,0.0612244898f,0.0663265306f,0.0714285714f,0.0663265306f,0.0612244898f,0.056122449f,0.0510204082f,0.0459183673f,0.0408163265f,0.0357142857f,0.0306122449f,0.0255102041f,0.0204081633f,0.0153061224f,0.0102040816f,0.0051020408f } ); + mMap.put( 29, new float[]{ 0.0044444444f,0.0088888889f,0.0133333333f,0.0177777778f,0.0222222222f,0.0266666667f,0.0311111111f,0.0355555556f,0.04f,0.0444444444f,0.0488888889f,0.0533333333f,0.0577777778f,0.0622222222f,0.0666666667f,0.0622222222f,0.0577777778f,0.0533333333f,0.0488888889f,0.0444444444f,0.04f,0.0355555556f,0.0311111111f,0.0266666667f,0.0222222222f,0.0177777778f,0.0133333333f,0.0088888889f,0.0044444444f } ); + } + + public TriangularSmoothingFilter() + { + super( mMap, 3 ); + } + + @Override + public SmoothingType getSmoothingType() + { + return SmoothingType.TRIANGLE; + } +} diff --git a/src/util/Oscillator.java b/src/dsp/mixer/Oscillator.java similarity index 99% rename from src/util/Oscillator.java rename to src/dsp/mixer/Oscillator.java index f003c4be..10d62e40 100644 --- a/src/util/Oscillator.java +++ b/src/dsp/mixer/Oscillator.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package util; +package dsp.mixer; import sample.complex.Complex; diff --git a/src/instrument/InstrumentableProcessingChain.java b/src/instrument/InstrumentableProcessingChain.java index 556db2c1..3efef0ae 100644 --- a/src/instrument/InstrumentableProcessingChain.java +++ b/src/instrument/InstrumentableProcessingChain.java @@ -6,11 +6,9 @@ import instrument.tap.TapGroup; import java.util.ArrayList; import java.util.List; -import source.Source; -import source.tuner.TunerChannelSource; -import source.tuner.frequency.FrequencyCorrectionControl; import module.Module; import module.ProcessingChain; +import source.Source; public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable { diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java index ada65bd7..8e9b983f 100644 --- a/src/module/ProcessingChain.java +++ b/src/module/ProcessingChain.java @@ -27,7 +27,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import message.IMessageListener; import message.IMessageProvider; import message.Message; -import module.decode.Decoder; import module.decode.event.CallEvent; import module.decode.event.ICallEventListener; import module.decode.event.ICallEventProvider; @@ -42,6 +41,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Broadcaster; +import sample.Buffer; import sample.Listener; import sample.complex.ComplexBuffer; import sample.complex.IComplexBufferListener; diff --git a/src/module/decode/ltrnet/LTRCallEvent.java b/src/module/decode/ltrnet/LTRCallEvent.java index 316fc313..21a46afb 100644 --- a/src/module/decode/ltrnet/LTRCallEvent.java +++ b/src/module/decode/ltrnet/LTRCallEvent.java @@ -19,7 +19,6 @@ package module.decode.ltrnet; import module.decode.DecoderType; import module.decode.event.CallEvent; -import module.decode.ltrstandard.LTRStandardMessage; import alias.Alias; import alias.AliasList; diff --git a/src/module/decode/mpt1327/MPT1327Decoder.java b/src/module/decode/mpt1327/MPT1327Decoder.java index 6e154ca7..f08a98c2 100644 --- a/src/module/decode/mpt1327/MPT1327Decoder.java +++ b/src/module/decode/mpt1327/MPT1327Decoder.java @@ -40,7 +40,6 @@ import sample.real.RealBuffer; import alias.AliasList; import bits.MessageFramer; import bits.SyncPattern; -import controller.channel.Channel.ChannelType; import dsp.filter.FilterFactory; import dsp.filter.Filters; import dsp.filter.Window.WindowType; diff --git a/src/module/decode/p25/P25DecoderPanel.java b/src/module/decode/p25/P25DecoderPanel.java index 70d4fff7..5db8144a 100644 --- a/src/module/decode/p25/P25DecoderPanel.java +++ b/src/module/decode/p25/P25DecoderPanel.java @@ -21,7 +21,6 @@ import java.awt.EventQueue; import javax.swing.JLabel; -import module.decode.p25.P25Decoder.Modulation; import module.decode.state.ChangedAttribute; import module.decode.state.DecoderPanel; import net.miginfocom.swing.MigLayout; diff --git a/src/module/decode/p25/P25_LSMDecoder.java b/src/module/decode/p25/P25_LSMDecoder.java index af14b085..4d1404fd 100644 --- a/src/module/decode/p25/P25_LSMDecoder.java +++ b/src/module/decode/p25/P25_LSMDecoder.java @@ -36,7 +36,6 @@ import sample.complex.ComplexBufferToStreamConverter; import sample.complex.IComplexBufferListener; import source.tuner.frequency.FrequencyCorrectionControl; import alias.AliasList; -import controller.channel.Channel.ChannelType; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index 7a89b72e..84e860be 100644 --- a/src/module/decode/p25/audio/P25AudioModule.java +++ b/src/module/decode/p25/audio/P25AudioModule.java @@ -135,7 +135,7 @@ public class P25AudioModule extends Module implements Listener, library = (AudioConversionLibrary)temp.newInstance(); if( ( library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 ) || - library.getMajorVersion() > 0 ) + library.getMajorVersion() >= 1 ) { mAudioConverter = library.getAudioConverter( IMBE_CODEC, AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO ); diff --git a/src/module/decode/state/ChannelList.java b/src/module/decode/state/ChannelList.java index 920fe1e0..594d7118 100644 --- a/src/module/decode/state/ChannelList.java +++ b/src/module/decode/state/ChannelList.java @@ -30,10 +30,11 @@ import javax.swing.JPopupMenu; import javax.swing.JSeparator; import javax.swing.SwingUtilities; +import net.miginfocom.swing.MigLayout; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.miginfocom.swing.MigLayout; import playlist.PlaylistManager; import settings.ColorSetting.ColorSettingName; import settings.ColorSettingMenuItem; diff --git a/src/module/decode/state/ChannelStatePanel.java b/src/module/decode/state/ChannelStatePanel.java index b8f05f68..b789fe35 100644 --- a/src/module/decode/state/ChannelStatePanel.java +++ b/src/module/decode/state/ChannelStatePanel.java @@ -7,20 +7,14 @@ import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import javax.swing.JLabel; import javax.swing.JMenu; -import javax.swing.JMenuItem; import javax.swing.JPanel; -import javax.swing.JSeparator; -import module.decode.Decoder; import module.decode.DecoderFactory; -import module.decode.event.ActivitySummaryFrame; import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; diff --git a/src/module/demodulate/fm/FMDemodulatorModule.java b/src/module/demodulate/fm/FMDemodulatorModule.java index 19d428b4..871e2d0c 100644 --- a/src/module/demodulate/fm/FMDemodulatorModule.java +++ b/src/module/demodulate/fm/FMDemodulatorModule.java @@ -33,7 +33,6 @@ import source.tuner.frequency.FrequencyCorrectionControl; import source.tuner.frequency.IFrequencyCorrectionController; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; -import dsp.filter.dc.AveragingDCRemovalFilter_RB; import dsp.filter.dc.DCRemovalFilter_RB; import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; import dsp.fm.FMDemodulator_CB; @@ -102,7 +101,7 @@ public class FMDemodulatorModule extends Module implements IComplexBufferListene if( mRemoveDC ) { - mDCRemovalFilter = new AveragingDCRemovalFilter_RB( DC_REMOVAL_RATIO ); + mDCRemovalFilter = new DCRemovalFilter_RB( DC_REMOVAL_RATIO ); mUnfilteredBufferBroadcaster.addListener( mDCRemovalFilter ); } } diff --git a/src/module/log/EventLogManager.java b/src/module/log/EventLogManager.java index 47b78a33..ee0909ee 100644 --- a/src/module/log/EventLogManager.java +++ b/src/module/log/EventLogManager.java @@ -21,13 +21,13 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import controller.channel.Channel; import module.Module; import module.log.MessageEventLogger.Type; import module.log.config.EventLogConfiguration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import properties.SystemProperties; public class EventLogManager diff --git a/src/record/wave/ComplexBufferWaveRecorder.java b/src/record/wave/ComplexBufferWaveRecorder.java index bb2f3ebc..53eed8cb 100644 --- a/src/record/wave/ComplexBufferWaveRecorder.java +++ b/src/record/wave/ComplexBufferWaveRecorder.java @@ -32,6 +32,7 @@ import module.Module; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sample.Buffer; import sample.ConversionUtils; import sample.Listener; import sample.complex.ComplexBuffer; @@ -163,7 +164,7 @@ public class ComplexBufferWaveRecorder extends Module { try { - ComplexBuffer buffer = mBuffers.poll(); + Buffer buffer = mBuffers.poll(); while( buffer != null ) { diff --git a/src/record/wave/WaveWriter.java b/src/record/wave/WaveWriter.java index cce4a0ad..1f275099 100644 --- a/src/record/wave/WaveWriter.java +++ b/src/record/wave/WaveWriter.java @@ -30,9 +30,6 @@ import java.util.regex.Pattern; import javax.sound.sampled.AudioFormat; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class WaveWriter implements AutoCloseable { // private final static Logger mLog = LoggerFactory.getLogger( WaveWriter.class ); diff --git a/src/sample/Buffer.java b/src/sample/Buffer.java new file mode 100644 index 00000000..943fb385 --- /dev/null +++ b/src/sample/Buffer.java @@ -0,0 +1,24 @@ +package sample; + +public class Buffer +{ + protected float[] mSamples; + + public Buffer( float[] samples ) + { + mSamples = samples; + } + + public float[] getSamples() + { + return mSamples; + } + + /** + * Cleanup method to nullify all data and references + */ + public void dispose() + { + mSamples = null; + } +} \ No newline at end of file diff --git a/src/sample/ConversionUtils.java b/src/sample/ConversionUtils.java index 0cd11dc6..c2e2f6fc 100644 --- a/src/sample/ConversionUtils.java +++ b/src/sample/ConversionUtils.java @@ -4,7 +4,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; -import sample.complex.ComplexBuffer; import sample.real.RealBuffer; /** @@ -57,7 +56,7 @@ public class ConversionUtils * Converts the float samples in a complex buffer to a little endian 16-bit * buffer */ - public static ByteBuffer convertToSigned16BitSamples( ComplexBuffer buffer ) + public static ByteBuffer convertToSigned16BitSamples( Buffer buffer ) { return convertToSigned16BitSamples( buffer.getSamples() ); } diff --git a/src/sample/adapter/ByteSampleAdapter.java b/src/sample/adapter/ByteSampleAdapter.java index e0c7085f..17295a3b 100644 --- a/src/sample/adapter/ByteSampleAdapter.java +++ b/src/sample/adapter/ByteSampleAdapter.java @@ -17,7 +17,7 @@ ******************************************************************************/ package sample.adapter; -public class ByteSampleAdapter extends SampleAdapter +public class ByteSampleAdapter implements ISampleAdapter { private final static float[] LOOKUP_VALUES; @@ -33,8 +33,6 @@ public class ByteSampleAdapter extends SampleAdapter { LOOKUP_VALUES[ x ] = (float)( x - 127 ) / 128.0f; } -// -// LOOKUP_VALUES[ 128 ] = 0.0000001f; } @Override diff --git a/src/sample/adapter/ChannelShortAdapter.java b/src/sample/adapter/ChannelShortAdapter.java index 983ac9d7..6245a250 100644 --- a/src/sample/adapter/ChannelShortAdapter.java +++ b/src/sample/adapter/ChannelShortAdapter.java @@ -25,7 +25,7 @@ import source.mixer.MixerChannel; /** * Converts 16-bit little endian byte data into an array of floats */ -public class ChannelShortAdapter extends SampleAdapter +public class ChannelShortAdapter implements ISampleAdapter { private ShortToFloatMap mMap = new ShortToFloatMap(); private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; diff --git a/src/sample/adapter/SampleAdapter.java b/src/sample/adapter/ISampleAdapter.java similarity index 93% rename from src/sample/adapter/SampleAdapter.java rename to src/sample/adapter/ISampleAdapter.java index a5e3ef50..a1c829bc 100644 --- a/src/sample/adapter/SampleAdapter.java +++ b/src/sample/adapter/ISampleAdapter.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer + * 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 @@ -21,7 +21,7 @@ package sample.adapter; * Adapter to convert byte data received from a data stream into float[] * samples */ -public abstract class SampleAdapter +public interface ISampleAdapter { public abstract float[] convert( byte[] samples ); } diff --git a/src/sample/adapter/ShortAdapter.java b/src/sample/adapter/ShortAdapter.java index 3d1d44c9..ad8622e8 100644 --- a/src/sample/adapter/ShortAdapter.java +++ b/src/sample/adapter/ShortAdapter.java @@ -23,7 +23,7 @@ import java.nio.ByteOrder; /** * Converts 16-bit little endian byte data into an array of floats */ -public class ShortAdapter extends SampleAdapter +public class ShortAdapter implements ISampleAdapter { private ShortToFloatMap mMap = new ShortToFloatMap(); private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; diff --git a/src/sample/complex/ComplexBuffer.java b/src/sample/complex/ComplexBuffer.java index af87965d..6cb6464e 100644 --- a/src/sample/complex/ComplexBuffer.java +++ b/src/sample/complex/ComplexBuffer.java @@ -1,21 +1,17 @@ package sample.complex; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -public class ComplexBuffer +import sample.Buffer; + +public class ComplexBuffer extends Buffer { - private float[] mSamples; - /** * Wrapper around float array containing interleaved I/Q samples */ public ComplexBuffer( float[] samples ) { - mSamples = samples; + super( samples ); } /** @@ -27,35 +23,4 @@ public class ComplexBuffer return new ComplexBuffer( copy ); } - - /** - * Interleaved I/Q float sample array - */ - public float[] getSamples() - { - return mSamples; - } - - /** - * Converts this sample buffer into a list of complex samples - */ - public List getComplexSamples() - { - List samples = new ArrayList(); - - for( int x = 0; x < mSamples.length; x += 2 ) - { - samples.add( new Complex( mSamples[ x ], mSamples[ x + 1 ] ) ); - } - - return samples; - } - - /** - * Cleanup method to nullify all data and references - */ - public void dispose() - { - mSamples = null; - } } diff --git a/src/sample/real/RealBuffer.java b/src/sample/real/RealBuffer.java index 4771218a..75a86d07 100644 --- a/src/sample/real/RealBuffer.java +++ b/src/sample/real/RealBuffer.java @@ -2,31 +2,18 @@ package sample.real; import java.util.Arrays; -public class RealBuffer -{ - private float[] mSamples; +import sample.Buffer; +public class RealBuffer extends Buffer +{ /** - * Wrapper around float array containing float samples + * Wrapper around float array containing real float samples */ public RealBuffer( float[] samples ) { - mSamples = samples; + super( samples ); } - /** - * Package private to allow subclass construction and subsequent definition - * of the sample data - */ - RealBuffer() - { - } - - protected void setSamples( float[] samples ) - { - mSamples = samples; - } - /** * Creates a deep copy of the buffer */ @@ -36,20 +23,4 @@ public class RealBuffer return new RealBuffer( copy ); } - - /** - * Sample data - */ - public float[] getSamples() - { - return mSamples; - } - - /** - * Cleanup method to nullify all data and references - */ - public void dispose() - { - mSamples = null; - } } diff --git a/src/settings/Settings.java b/src/settings/Settings.java index fc9e1783..464e4d46 100644 --- a/src/settings/Settings.java +++ b/src/settings/Settings.java @@ -31,6 +31,7 @@ import source.recording.RecordingConfiguration; import source.tuner.TunerConfiguration; import source.tuner.TunerConfigurationAssignment; import source.tuner.TunerType; +import source.tuner.airspy.AirspyTunerConfiguration; import source.tuner.fcd.proV1.FCD1TunerConfiguration; import source.tuner.fcd.proplusV2.FCD2TunerConfiguration; import source.tuner.hackrf.HackRFTunerConfiguration; @@ -40,7 +41,8 @@ import source.tuner.rtl.r820t.R820TTunerConfiguration; /** * Support for persisting via JAXB all configured SDRTrunk settings */ -@XmlSeeAlso( { ColorSetting.class, +@XmlSeeAlso( { AirspyTunerConfiguration.class, + ColorSetting.class, DefaultIcon.class, E4KTunerConfiguration.class, FCD1TunerConfiguration.class, diff --git a/src/settings/SettingsManager.java b/src/settings/SettingsManager.java index 8d8b31a6..e37317e1 100644 --- a/src/settings/SettingsManager.java +++ b/src/settings/SettingsManager.java @@ -50,6 +50,7 @@ import source.recording.RecordingConfiguration; import source.tuner.TunerConfiguration; import source.tuner.TunerConfigurationAssignment; import source.tuner.TunerType; +import source.tuner.airspy.AirspyTunerConfiguration; import source.tuner.fcd.proV1.FCD1TunerConfiguration; import source.tuner.fcd.proplusV2.FCD2TunerConfiguration; import source.tuner.hackrf.HackRFTunerConfiguration; @@ -573,6 +574,14 @@ public class SettingsManager { switch( type ) { + case AIRSPY_R820T: + AirspyTunerConfiguration airspyConfig = + new AirspyTunerConfiguration( name ); + + getSettings().addTunerConfiguration( airspyConfig ); + + save(); + return airspyConfig; case ELONICS_E4000: E4KTunerConfiguration e4KConfig = new E4KTunerConfiguration( name ); diff --git a/src/source/mixer/ComplexMixer.java b/src/source/mixer/ComplexMixer.java index fccdaff7..009666ef 100644 --- a/src/source/mixer/ComplexMixer.java +++ b/src/source/mixer/ComplexMixer.java @@ -28,7 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; -import sample.adapter.SampleAdapter; +import sample.adapter.ISampleAdapter; import sample.complex.ComplexBuffer; import source.SourceException; @@ -45,7 +45,7 @@ public class ComplexMixer private AudioFormat mAudioFormat; private String mName; private int mBytesPerFrame = 0; - private SampleAdapter mSampleAdapter; + private ISampleAdapter mSampleAdapter; private Listener mListener; /** @@ -68,7 +68,7 @@ public class ComplexMixer public ComplexMixer( TargetDataLine targetDataLine, AudioFormat format, String name, - SampleAdapter sampleAdapter, + ISampleAdapter sampleAdapter, Listener listener ) { mTargetDataLine = targetDataLine; diff --git a/src/source/mixer/RealMixerSource.java b/src/source/mixer/RealMixerSource.java index 2de626eb..58b8d953 100644 --- a/src/source/mixer/RealMixerSource.java +++ b/src/source/mixer/RealMixerSource.java @@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory; import dsp.gain.AutomaticGainControl; import sample.Listener; -import sample.adapter.SampleAdapter; +import sample.adapter.ISampleAdapter; import sample.real.RealBuffer; import source.RealSource; import source.SourceException; @@ -49,7 +49,7 @@ public class RealMixerSource extends RealSource private AudioFormat mAudioFormat; private String mDisplayName; private int mBytesPerFrame = 0; - private SampleAdapter mSampleAdapter; + private ISampleAdapter mSampleAdapter; private AutomaticGainControl mAGC = new AutomaticGainControl(); CopyOnWriteArrayList> mSampleListeners = @@ -74,7 +74,7 @@ public class RealMixerSource extends RealSource public RealMixerSource( TargetDataLine targetDataLine, AudioFormat format, String name, - SampleAdapter sampleAdapter ) + ISampleAdapter sampleAdapter ) { super( name ); diff --git a/src/source/tuner/MixerTuner.java b/src/source/tuner/MixerTuner.java index 0de4ef66..42786ca8 100644 --- a/src/source/tuner/MixerTuner.java +++ b/src/source/tuner/MixerTuner.java @@ -25,7 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; -import sample.adapter.SampleAdapter; +import sample.adapter.ISampleAdapter; import sample.complex.ComplexBuffer; import sample.complex.ComplexSampleListListener; import source.mixer.ComplexMixer; @@ -48,7 +48,7 @@ public abstract class MixerTuner extends Tuner implements Listener 0 ) + { + /* Truncate the version string if we have a line break - ASCII 10 */ + for( int x = 0; x < data.length; x++ ) + { + if( data[ x ] == (byte)10 ) + { + mFirmwareVersion = new String( Arrays.copyOf( data, x ) ); + mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" ); + return; + } + } + + mFirmwareVersion = new String( data ); + mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" ); + } + else + { + mLog.error( "Error setting airspy version byte data - null:" + + ( data == null ? " TRUE" : " FALSE length:" + data.length ) ); + } + } + + public String getVersion() + { + return mFirmwareVersion; + } + + public void setPartAndSerialNumber( byte[] data ) + { + if( data != null && data.length == 24 ) + { + //Note: values are byte-reversed (big-endian) 32-bit chunks + StringBuilder part = new StringBuilder(); + + part.append( format( data[ 3 ] ) ); + part.append( format( data[ 2 ] ) ); + part.append( format( data[ 1 ] ) ); + part.append( format( data[ 0 ] ) ); + part.append( format( data[ 7 ] ) ); + part.append( format( data[ 6 ] ) ); + part.append( format( data[ 5 ] ) ); + part.append( format( data[ 4 ] ) ); + + mPartNumber = part.toString(); + + StringBuilder serial = new StringBuilder(); + + //Note: current airspy library only exposes 64-bits of serial number + //address space, but appears to have room for 128-bits. We won't + //add the first 64 bits if it's zeros + if( !( data[ 8 ] == 0 && data[ 9 ] == 0 && data[ 10 ] == 0 && data[ 11 ] == 0 ) ) + { + serial.append( format( data[ 11 ] ) ); + serial.append( format( data[ 10 ] ) ); + serial.append( format( data[ 9 ] ) ); + serial.append( format( data[ 8 ] ) ); + serial.append( "-" ); + } + + if( !( data[ 12 ] == 0 && data[ 13 ] == 0 && data[ 14 ] == 0 && data[ 15 ] == 0 ) ) + { + serial.append( format( data[ 15 ] ) ); + serial.append( format( data[ 14 ] ) ); + serial.append( format( data[ 13 ] ) ); + serial.append( format( data[ 12 ] ) ); + serial.append( "-" ); + } + + serial.append( format( data[ 19 ] ) ); + serial.append( format( data[ 18 ] ) ); + serial.append( format( data[ 17 ] ) ); + serial.append( format( data[ 16 ] ) ); + serial.append( "-" ); + serial.append( format( data[ 23 ] ) ); + serial.append( format( data[ 22 ] ) ); + serial.append( format( data[ 21 ] ) ); + serial.append( format( data[ 20 ] ) ); + + mSerialNumber = serial.toString(); + } + else + { + mLog.error( "Error setting airspy part and serial byte data - null:" + + ( data == null ? " TRUE" : " FALSE length:" + data.length ) ); + } + } + + private String format( byte value ) + { + return String.format( "%02X", value ); + } + + public String getSerialNumber() + { + return mSerialNumber; + } + + public String getPartNumber() + { + return mPartNumber; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Airspy Device Information\n" ); + sb.append( "Board: " ); + sb.append( getBoardID().getLabel() ); + sb.append( "\nPart Number: " ); + sb.append( getPartNumber() ); + sb.append( "\nSerial Number: " ); + sb.append( getSerialNumber() ); + sb.append( "\nFirmware: " ); + sb.append( getVersion() ); + + return sb.toString(); + } +} diff --git a/src/source/tuner/airspy/AirspyInformationPanel.java b/src/source/tuner/airspy/AirspyInformationPanel.java new file mode 100644 index 00000000..6b3ef2cb --- /dev/null +++ b/src/source/tuner/airspy/AirspyInformationPanel.java @@ -0,0 +1,77 @@ +package source.tuner.airspy; + +/******************************************************************************* + * 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 + ******************************************************************************/ + +import javax.swing.JLabel; +import javax.swing.JPanel; + +import net.miginfocom.swing.MigLayout; + +public class AirspyInformationPanel extends JPanel +{ + private static final long serialVersionUID = 1L; + + private AirspyTunerController mController; + + /** + * Displays Airspy firmware, serial number and part identifier + */ + public AirspyInformationPanel( AirspyTunerController controller ) + { + mController = controller; + + initGUI(); + } + + private void initGUI() + { + setLayout( new MigLayout( "fill,wrap 2", + "[right,grow][grow]", + "[][][][][grow]" ) ); + + AirspyDeviceInformation info = mController.getDeviceInfo(); + + add( new JLabel( "Serial:" ) ); + add( new JLabel( info.getSerialNumber() ) ); + add( new JLabel( "Firmware:" ) ); + + //Split the firmware on spaces and use first value -- expose full + //string in a tool tip + String[] firmware = info.getVersion().split( " " ); + + JLabel firmwareLabel; + + if( firmware.length > 1 ) + { + firmwareLabel = new JLabel( firmware[ 0 ] ); + } + else + { + firmwareLabel = new JLabel( info.getVersion() ); + } + + firmwareLabel.setToolTipText( info.getVersion() ); + + add( firmwareLabel ); + add( new JLabel( "Part:" ) ); + add( new JLabel( info.getPartNumber() ) ); + add( new JLabel( "Board ID:" ) ); + add( new JLabel( info.getBoardID().getLabel() ) ); + } +} diff --git a/src/source/tuner/airspy/AirspySampleAdapter.java b/src/source/tuner/airspy/AirspySampleAdapter.java new file mode 100644 index 00000000..91a67395 --- /dev/null +++ b/src/source/tuner/airspy/AirspySampleAdapter.java @@ -0,0 +1,149 @@ +package source.tuner.airspy; + +/******************************************************************************* + * 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 + ******************************************************************************/ + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.adapter.ISampleAdapter; + +public class AirspySampleAdapter implements ISampleAdapter +{ + private final static Logger mLog = LoggerFactory.getLogger( AirspySampleAdapter.class ); + + private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f; + + private boolean mSamplePacking = false; + + /** + * Adapter to translate byte buffers received from the airspy tuner into + * float buffers for processing. + */ + public AirspySampleAdapter() + { + } + + /** + * 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; + } + + @Override + public float[] convert( byte[] samples ) + { + if( mSamplePacking ) + { + return convertPacked( samples ); + } + else + { + return convertUnpacked( samples ); + } + } + + /** + * Converts the byte array containing unsigned 12-bit short values into + * signed float values in the range -1 to 1; + * + * @param data - byte array of unsigned 16-bit values + * @return converted float values + */ + private float[] convertUnpacked( byte[] data ) + { + float[] samples = new float[ data.length / 2 ]; + + int pointer = 0; + + for( int x = 0; x < data.length; x += 2 ) + { + samples[ pointer++ ] = scale( ( data[ x ] & 0xFF ) | + ( data[ x + 1 ] << 8 ) ); + } + + return samples; + } + + /** + * 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 data - byte array of unsigned 12-bit values + * @return converted float values + */ + private float[] convertPacked( byte[] data1 ) + { + byte[] data = new byte[ data1.length ]; + + //Convert big-endian to little-endian + for( int x = 0; x < data1.length; x += 4 ) + { + data[ x ] = data1[ x + 3 ]; + data[ x + 1 ] = data1[ x + 2 ]; + data[ x + 2 ] = data1[ x + 1 ]; + data[ x + 3 ] = data1[ x ]; + } + + int count = (int)( (float)data.length / 1.5f ); + + /* Ensure we have an even number of samples */ + if( count % 2 == 1 ) + { + count--; + } + + int bytes = (int)((float)count * 1.5f); + + float[] samples = new float[ count ]; + + int pointer = 0; + + int first; + int second; + + for( int x = 0; x < bytes; x += 3 ) + { + first = ( ( data[ x ] << 4 ) & 0xFF0 ) | + ( ( data[ x + 1 ] >> 4 ) & 0xF ); + + samples[ pointer++ ] = scale( first ); + + second = ( ( data[ x + 1 ] << 8 ) & 0xF00 ) | + ( data[ x + 2 ] & 0xFF ); + + samples[ pointer++ ] = scale( second ); + } + + return samples; + } + + /** + * 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/source/tuner/airspy/AirspyTuner.java b/src/source/tuner/airspy/AirspyTuner.java new file mode 100644 index 00000000..c88c0237 --- /dev/null +++ b/src/source/tuner/airspy/AirspyTuner.java @@ -0,0 +1,168 @@ +package source.tuner.airspy; + +import java.util.concurrent.RejectedExecutionException; + +import javax.swing.JPanel; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.Listener; +import sample.complex.ComplexBuffer; +import source.SourceException; +import source.tuner.Tuner; +import source.tuner.TunerChannel; +import source.tuner.TunerChannelSource; +import source.tuner.TunerClass; +import source.tuner.TunerConfiguration; +import source.tuner.TunerType; +import source.tuner.airspy.AirspyTunerController.BoardID; +import controller.ResourceManager; +import controller.ThreadPoolManager; + +/******************************************************************************* + * 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 + ******************************************************************************/ +public class AirspyTuner extends Tuner +{ + private final static Logger mLog = LoggerFactory.getLogger( AirspyTuner.class ); + + AirspyTunerController mController; + + public AirspyTuner( AirspyTunerController controller ) + { + super( "Airspy" ); + + mController = controller; + + /* Register for frequency/sample rate changes so that we can rebroadcast + * to any registered listeners */ + mController.addListener( this ); + } + + @Override + public JPanel getEditor( ResourceManager resourceManager ) + { + return new AirspyTunerEditorPanel( this, resourceManager ); + } + + public AirspyTunerController getController() + { + return mController; + } + + @Override + public void apply( TunerConfiguration config ) throws SourceException + { + mController.apply( config ); + } + + @Override + public String getUniqueID() + { + try + { + return mController.getDeviceInfo().getSerialNumber(); + } + catch( Exception e ) + { + mLog.error( "error gettting serial number", e ); + } + + return BoardID.AIRSPY.getLabel(); + } + + @Override + public TunerClass getTunerClass() + { + return TunerClass.AIRSPY; + } + + @Override + public TunerType getTunerType() + { + return TunerType.AIRSPY_R820T; + } + + @Override + public int getSampleRate() + { + int rate = 0; + + try + { + rate = mController.getCurrentSampleRate(); + } + catch ( SourceException e ) + { + mLog.debug( "Error while getting sample rate from controller", e ); + } + + return rate; + } + + @Override + public double getSampleSize() + { + return 12.0; + } + + @Override + public long getFrequency() throws SourceException + { + return mController.getFrequency(); + } + + @Override + public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager, + TunerChannel channel ) throws RejectedExecutionException, SourceException + { + //TODO: this channel has a decimated sample rate of: + // 10.0 MSps = 10,000,000 / 208 = 48076.923 + // 5.0 MSps = 5,000,000 / 104 = 48076.923 + // 2.5 MSps = 2,500,000 / 52 = 48076.923 + //Consider implementing a fractional resampler to get a correct 48 kHz + //output sample rate + + return mController.getChannel( threadPoolManager, this, channel ); + } + + @Override + public void releaseChannel( TunerChannelSource source ) + { + /* Unregister for receiving samples */ + removeListener( (Listener)source ); + + /* Tell the controller to release the channel and cleanup */ + if( source != null ) + { + mController.releaseChannel( source ); + } + } + + @Override + public void addListener( Listener listener ) + { + mController.addListener( listener ); + } + + @Override + public void removeListener( Listener listener ) + { + mController.removeListener( listener ); + } +} diff --git a/src/source/tuner/airspy/AirspyTunerConfiguration.java b/src/source/tuner/airspy/AirspyTunerConfiguration.java new file mode 100644 index 00000000..b21c0dd6 --- /dev/null +++ b/src/source/tuner/airspy/AirspyTunerConfiguration.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * 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 source.tuner.airspy; + +import javax.xml.bind.annotation.XmlAttribute; + +import source.tuner.TunerConfiguration; +import source.tuner.TunerType; + +public class AirspyTunerConfiguration extends TunerConfiguration +{ + private int mSampleRate = AirspyTunerController.DEFAULT_SAMPLE_RATE.getRate(); + private int mIFGain = AirspyTunerController.IF_GAIN_DEFAULT; + private int mMixerGain = AirspyTunerController.MIXER_GAIN_DEFAULT; + private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT; + + private boolean mMixerAGC = false; + private boolean mLNAAGC = false; + + /** + * Default constructor for JAXB + */ + public AirspyTunerConfiguration() + { + this( "Default" ); + } + + public AirspyTunerConfiguration( String name ) + { + super( name ); + } + + @Override + public TunerType getTunerType() + { + return TunerType.AIRSPY_R820T; + } + + @XmlAttribute( name = "sample_rate" ) + public int getSampleRate() + { + return mSampleRate; + } + + public void setSampleRate( int sampleRate ) + { + mSampleRate = sampleRate; + } + + @XmlAttribute( name = "if_gain" ) + public int getIFGain() + { + return mIFGain; + } + + public void setIFGain( int gain ) + { + mIFGain = gain; + } + + @XmlAttribute( name = "mixer_gain" ) + public int getMixerGain() + { + return mMixerGain; + } + + public void setMixerGain( int gain ) + { + mMixerGain = gain; + } + + @XmlAttribute( name = "lna_gain" ) + public int getLNAGain() + { + return mLNAGain; + } + + public void setLNAGain( int gain ) + { + mLNAGain = gain; + } + + @XmlAttribute( name = "mixer_agc" ) + public boolean isMixerAGC() + { + return mMixerAGC; + } + + public void setMixerAGC( boolean enabled ) + { + mMixerAGC = enabled; + } + + @XmlAttribute( name = "lna_agc" ) + public boolean isLNAAGC() + { + return mLNAAGC; + } + + public void setLNAAGC( boolean enabled ) + { + mLNAAGC = enabled; + } +} diff --git a/src/source/tuner/airspy/AirspyTunerConfigurationPanel.java b/src/source/tuner/airspy/AirspyTunerConfigurationPanel.java new file mode 100644 index 00000000..36faa33b --- /dev/null +++ b/src/source/tuner/airspy/AirspyTunerConfigurationPanel.java @@ -0,0 +1,587 @@ +/******************************************************************************* + * 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 source.tuner.airspy; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JSlider; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.usb.UsbException; + +import net.miginfocom.swing.MigLayout; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.usb4java.LibUsbException; + +import source.SourceException; +import source.tuner.TunerConfiguration; +import source.tuner.TunerConfigurationAssignment; +import source.tuner.TunerType; +import controller.ResourceManager; + +public class AirspyTunerConfigurationPanel extends JPanel +{ + private final static Logger mLog = + LoggerFactory.getLogger( AirspyTunerConfigurationPanel.class ); + + private static final long serialVersionUID = 1L; + + private ResourceManager mResourceManager; + private AirspyTunerController mController; + private AirspyTunerConfiguration mSelectedConfig; + + private JButton mNewConfiguration; + private JButton mDeleteConfiguration; + private JComboBox mComboConfigurations; + + private JTextField mName; + + private JComboBox mSampleRateCombo; + private JComboBox mDecimationCombo; + private JLabel mEffectiveRate; + + private JLabel mIFGainLabel; + private JSlider mIFGain; + private JLabel mLNAGainLabel; + private JSlider mLNAGain; + private JLabel mMixerGainLabel; + private JSlider mMixerGain; + + private JCheckBox mMixerAGC; + private JCheckBox mLNAAGC; + + public AirspyTunerConfigurationPanel( ResourceManager resourceManager, + AirspyTunerController controller ) + { + mResourceManager = resourceManager; + mController = controller; + + init(); + } + + /** + * Initializes the gui components + */ + private void init() + { + setLayout( new MigLayout( "fill,wrap 4", "[left][grow][grow][right]", + "[][][][][][][grow]" ) ); + + /** + * Tuner configuration selector + */ + mComboConfigurations = new JComboBox(); + + mComboConfigurations.setModel( getModel() ); + + /* Determine which tuner configuration should be selected/displayed */ + TunerConfigurationAssignment savedConfig = null; + String serial = null; + + serial = mController.getDeviceInfo().getSerialNumber(); + + if( serial != null ) + { + savedConfig = mResourceManager + .getSettingsManager().getSelectedTunerConfiguration( + TunerType.AIRSPY_R820T, serial ); + } + + if( savedConfig != null ) + { + mSelectedConfig = getNamedConfiguration( + savedConfig.getTunerConfigurationName() ); + } + + /* If we couldn't determine the saved/selected config, use the first one */ + if( mSelectedConfig == null ) + { + mSelectedConfig = mComboConfigurations.getItemAt( 0 ); + + //Store this config as the default for this tuner at this address + + if( serial != null ) + { + mResourceManager.getSettingsManager() + .setSelectedTunerConfiguration( TunerType.AIRSPY_R820T, + serial, mSelectedConfig ); + } + } + + mComboConfigurations.setSelectedItem( mSelectedConfig ); + + mComboConfigurations.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + AirspyTunerConfiguration selected = + (AirspyTunerConfiguration)mComboConfigurations + .getItemAt( mComboConfigurations.getSelectedIndex() ); + + if( selected != null ) + { + update( selected ); + } + } + }); + + add( new JLabel( "Config:" ) ); + add( mComboConfigurations, "span,growx,push" ); + + add( new JSeparator(), "span,growx,push" ); + + + /** + * Tuner Configuration Name + */ + mName = new JTextField(); + mName.setText( mSelectedConfig.getName() ); + + mName.addFocusListener( new FocusListener() + { + @Override + public void focusLost( FocusEvent e ) + { + mSelectedConfig.setName( mName.getText() ); + save(); + } + @Override + public void focusGained( FocusEvent e ) {} + } ); + + add( new JLabel( "Name:" ) ); + add( mName, "span,growx,push" ); + + /** + * Sample Rate + */ + add( new JLabel( "Sample Rate:" ) ); + + List rates = mController.getSampleRates(); + + mSampleRateCombo = new JComboBox( + new DefaultComboBoxModel( rates.toArray( + new AirspySampleRate[ rates.size() ] ) ) ); + + mSampleRateCombo.setSelectedItem( mController.getSampleRate() ); + + mSampleRateCombo.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + AirspySampleRate rate = (AirspySampleRate)mSampleRateCombo.getSelectedItem(); + + try + { + mController.setSampleRate( rate ); + mSelectedConfig.setSampleRate( rate.getRate() ); + save(); + + mEffectiveRate.setText( rate.getLabel() ); + } + catch ( LibUsbException | UsbException e1 ) + { + JOptionPane.showMessageDialog( AirspyTunerConfigurationPanel.this, + "Couldn't set sample rate to " + rate.getLabel() ); + + mLog.error( "Error setting airspy sample rate", e1 ); + } + } + } ); + + add( mSampleRateCombo, "span 3,growx" ); + + /** + * Decimation + */ + add( new JLabel( "Decimation:" ) ); + mDecimationCombo = new JComboBox(); + add( mDecimationCombo, "span 3,growx" ); + mDecimationCombo.setEnabled( false ); + + /** + * Effective Rate + */ + add( new JLabel( "Effective Rate:" ) ); + mEffectiveRate = new JLabel( "10,000,000" ); + add( mEffectiveRate, "span 3,growx" ); + + add( new JSeparator(), "span,growx,push" ); + + /** + * IF/VGA Gain + */ + add( new JLabel( "IF Gain" ) ); + add( new JLabel( "" ), "span 2" ); + + mIFGainLabel = new JLabel( String.valueOf( mSelectedConfig.getIFGain() ) ); + add( mIFGainLabel, "wrap" ); + + mIFGain = new JSlider( JSlider.HORIZONTAL, + AirspyTunerController.IF_GAIN_MIN, + AirspyTunerController.IF_GAIN_MAX, + mSelectedConfig.getIFGain() ); + + mIFGain.setMajorTickSpacing( 1 ); + mIFGain.setPaintTicks( true ); + + mIFGain.addChangeListener( new ChangeListener() + { + @Override + public void stateChanged( ChangeEvent event ) + { + int gain = mIFGain.getValue(); + + try + { + mController.setIFGain( gain ); + mSelectedConfig.setIFGain( gain ); + save(); + mIFGainLabel.setText( String.valueOf( gain ) ); + } + catch ( Exception e ) + { + mLog.error( "Couldn't set airspy IF gain to:" + gain, e ); + + JOptionPane.showMessageDialog( mIFGain, + "Couldn't set IF gain value to " + gain ); + + mIFGain.setValue( mSelectedConfig.getIFGain() ); + } + } + } ); + + add( mIFGain, "span,grow" ); + + add( new JLabel( "Mixer Gain" ) ); + + mMixerAGC = new JCheckBox( "AGC" ); + + mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() ); + + mMixerAGC.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + try + { + mController.setMixerAGC( !mSelectedConfig.isMixerAGC() ); + mSelectedConfig.setMixerAGC( !mSelectedConfig.isMixerAGC() ); + save(); + } + catch ( Exception e1 ) + { + mLog.error( "Error setting Mixer AGC Enabled:" + !mSelectedConfig.isMixerAGC() ); + mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() ); + } + } + } ); + + + add( mMixerAGC, "span 2,center" ); + + mMixerGainLabel = new JLabel( String.valueOf( mSelectedConfig.getMixerGain() ) ); + add( mMixerGainLabel, "wrap" ); + + mMixerGain = new JSlider( JSlider.HORIZONTAL, + AirspyTunerController.MIXER_GAIN_MIN, + AirspyTunerController.MIXER_GAIN_MAX, + mSelectedConfig.getMixerGain() ); + + mMixerGain.setMajorTickSpacing( 1 ); + mMixerGain.setPaintTicks( true ); + + mMixerGain.addChangeListener( new ChangeListener() + { + @Override + public void stateChanged( ChangeEvent event ) + { + int gain = mMixerGain.getValue(); + + try + { + mController.setMixerGain( gain ); + mSelectedConfig.setMixerGain( gain ); + save(); + mMixerGainLabel.setText( String.valueOf( gain ) ); + } + catch ( Exception e ) + { + mLog.error( "Couldn't set airspy Mixer gain to:" + gain, e ); + + JOptionPane.showMessageDialog( mIFGain, "Couldn't set Mixer gain value to " + gain ); + + mMixerGain.setValue( mSelectedConfig.getMixerGain() ); + } + } + } ); + + add( mMixerGain, "span,grow" ); + + add( new JLabel( "LNA Gain" ) ); + + mLNAAGC = new JCheckBox( "AGC" ); + + mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() ); + + mLNAAGC.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + try + { + mController.setLNAAGC( !mSelectedConfig.isLNAAGC() ); + mSelectedConfig.setLNAAGC( !mSelectedConfig.isLNAAGC() ); + save(); + } + catch ( Exception e1 ) + { + mLog.error( "Error setting LNA AGC Enabled:" + !mSelectedConfig.isLNAAGC() ); + mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() ); + } + + } + } ); + + add( mLNAAGC, "span 2,center" ); + + mLNAGainLabel = new JLabel( String.valueOf( mSelectedConfig.getLNAGain() ) ); + add( mLNAGainLabel, "wrap" ); + + mLNAGain = new JSlider( JSlider.HORIZONTAL, + AirspyTunerController.LNA_GAIN_MIN, + AirspyTunerController.LNA_GAIN_MAX, + mSelectedConfig.getLNAGain() ); + + mLNAGain.setMajorTickSpacing( 1 ); + mLNAGain.setPaintTicks( true ); + + mLNAGain.addChangeListener( new ChangeListener() + { + @Override + public void stateChanged( ChangeEvent event ) + { + int gain = mLNAGain.getValue(); + + try + { + mController.setLNAGain( gain ); + mSelectedConfig.setLNAGain( gain ); + save(); + mLNAGainLabel.setText( String.valueOf( gain ) ); + } + catch ( Exception e ) + { + mLog.error( "Couldn't set airspy LNA gain to:" + gain, e ); + + JOptionPane.showMessageDialog( mIFGain, "Couldn't set LNA gain value to " + gain ); + + mLNAGain.setValue( mSelectedConfig.getLNAGain() ); + } + } + } ); + + add( mLNAGain, "span,grow" ); + + /** + * Create a new configuration + */ + mNewConfiguration = new JButton( "New" ); + + mNewConfiguration.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + TunerConfiguration config = + mResourceManager.getSettingsManager() + .addNewTunerConfiguration( + TunerType.AIRSPY_R820T, + "New Configuration" ); + + mComboConfigurations.setModel( getModel() ); + + mComboConfigurations.setSelectedItem( config ); + + repaint(); + } + } ); + + add( mNewConfiguration, "span 2,growx,push" ); + + /** + * Delete the currently selected configuration + */ + mDeleteConfiguration = new JButton( "Delete" ); + + mDeleteConfiguration.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + AirspyTunerConfiguration selected = + (AirspyTunerConfiguration)mComboConfigurations + .getItemAt( mComboConfigurations.getSelectedIndex() ); + + if( selected != null ) + { + int n = JOptionPane.showConfirmDialog( + AirspyTunerConfigurationPanel.this, + "Are you sure you want to delete '" + + selected.getName() + "'?", + "Are you sure?", + JOptionPane.YES_NO_OPTION ); + + if( n == JOptionPane.YES_OPTION ) + { + mResourceManager.getSettingsManager() + .deleteTunerConfiguration( selected ); + + mComboConfigurations.setModel( getModel() ); + + mComboConfigurations.setSelectedIndex( 0 ); + + repaint(); + } + } + } + } ); + + add( mDeleteConfiguration, "span 2,growx,push" ); + } + + /** + * Updates gui controls with the values from the tuner configuration + * @param config - tuner configuration + */ + private void update( AirspyTunerConfiguration config ) + { + mSelectedConfig = config; + + try + { + mController.apply( config ); + + mName.setText( config.getName() ); + + AirspySampleRate rate = mController.getSampleRate( + mSelectedConfig.getSampleRate() ); + + if( rate != null ) + { + mSampleRateCombo.setSelectedItem( rate ); + } + + //TODO: set decimation and effective rate here + + mIFGain.setValue( mSelectedConfig.getIFGain() ); + mMixerGain.setValue( mSelectedConfig.getMixerGain() ); + mLNAGain.setValue( mSelectedConfig.getLNAGain() ); + + mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() ); + mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() ); + + mResourceManager.getSettingsManager().setSelectedTunerConfiguration( + TunerType.AIRSPY_R820T, mController.getDeviceInfo().getSerialNumber(), config ); + + save(); + } + catch ( /*UsbException |*/ SourceException e1 ) + { + JOptionPane.showMessageDialog( + AirspyTunerConfigurationPanel.this, + "Airspy Tuner Controller - couldn't " + + "apply the tuner configuration settings - " + + e1.getLocalizedMessage() ); + + mLog.error( "Airspy Tuner Controller - couldn't apply " + + "config [" + config.getName() + "]", e1 ); + } + } + + /** + * Constructs a combo box model for the tuner configuration combo component + * by assembling a list of the tuner configurations appropriate for this + * specific tuner + */ + private ComboBoxModel getModel() + { + ArrayList configs = + mResourceManager.getSettingsManager() + .getTunerConfigurations( TunerType.AIRSPY_R820T ); + + DefaultComboBoxModel model = + new DefaultComboBoxModel(); + + for( TunerConfiguration config: configs ) + { + model.addElement( (AirspyTunerConfiguration)config ); + } + + return model; + } + + /** + * Retrieves a specific (named) tuner configuration + */ + private AirspyTunerConfiguration getNamedConfiguration( String name ) + { + ArrayList configs = + mResourceManager.getSettingsManager() + .getTunerConfigurations( TunerType.AIRSPY_R820T ); + + for( TunerConfiguration config: configs ) + { + if( config.getName().contentEquals( name ) ) + { + return (AirspyTunerConfiguration)config; + } + } + + return null; + } + + /** + * Saves current tuner configuration settings + */ + private void save() + { + mResourceManager.getSettingsManager().save(); + } +} diff --git a/src/source/tuner/airspy/AirspyTunerController.java b/src/source/tuner/airspy/AirspyTunerController.java new file mode 100644 index 00000000..eb2d6eb6 --- /dev/null +++ b/src/source/tuner/airspy/AirspyTunerController.java @@ -0,0 +1,1257 @@ +package source.tuner.airspy; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.LinkedTransferQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.usb.UsbException; + +import org.apache.commons.io.EndianUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.usb4java.Device; +import org.usb4java.DeviceHandle; +import org.usb4java.LibUsb; +import org.usb4java.LibUsbException; +import org.usb4java.Transfer; +import org.usb4java.TransferCallback; + +import sample.Broadcaster; +import sample.Listener; +import sample.complex.ComplexBuffer; +import source.SourceException; +import source.tuner.TunerConfiguration; +import source.tuner.TunerController; +import controller.ThreadPoolManager; +import controller.ThreadPoolManager.ThreadType; +import dsp.filter.dc.DCRemovalFilter_RB; +import dsp.filter.hilbert.HilbertTransform; + +/** + * SDR Trunk + * Copyright (C) 2015 Dennis Sheirer + * + * Ported from libairspy at: + * https://github.com/airspy/host/tree/master/libairspy + * ----------------------------------------------------------------------------- + * Copyright (c) 2013, Michael Ossmann + * Copyright (c) 2012, Jared Boone + * Copyright (c) 2014, Youssef Touil + * Copyright (c) 2014, Benjamin Vernoux + * Copyright (c) 2015, Ian Gilmour + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 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. + * Neither the name of AirSpy nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER 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. + */ + +public class AirspyTunerController extends TunerController +{ + public static final int LNA_GAIN_MIN = 0; + public static final int LNA_GAIN_MAX = 14; + public static final int LNA_GAIN_DEFAULT = 1; + public static final int MIXER_GAIN_MIN = 0; + public static final int MIXER_GAIN_MAX = 15; + public static final int MIXER_GAIN_DEFAULT = 5; + public static final int IF_GAIN_MIN = 0; + public static final int IF_GAIN_MAX = 15; + public static final int IF_GAIN_DEFAULT = 5; + public static final long FREQUENCY_MIN = 24000000l; + public static final long FREQUENCY_MAX = 1800000000l; + public static final long FREQUENCY_DEFAULT = 101100000; + public static final AirspySampleRate DEFAULT_SAMPLE_RATE = + new AirspySampleRate( 0, 10000000, "10.00 MHz" ); + + public static final long USB_TIMEOUT_MS = 2000l; //milliseconds + public static final byte USB_ENDPOINT = (byte)0x81; + public static final byte USB_INTERFACE = (byte)0x0; + public static final int TRANSFER_BUFFER_POOL_SIZE = 16; + + public static final byte USB_REQUEST_IN = + (byte)( LibUsb.ENDPOINT_IN | + LibUsb.REQUEST_TYPE_VENDOR | + LibUsb.RECIPIENT_DEVICE ); + + public static final byte USB_REQUEST_OUT = + (byte)( LibUsb.ENDPOINT_OUT | + LibUsb.REQUEST_TYPE_VENDOR | + LibUsb.RECIPIENT_DEVICE ); + + public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat( "#.00 MHz" ); + + private final static Logger mLog = + LoggerFactory.getLogger( AirspyTunerController.class ); + + private Device mDevice; + private DeviceHandle mDeviceHandle; + + private ThreadPoolManager mThreadPoolManager; + private LinkedTransferQueue mFilledBuffers = + new LinkedTransferQueue(); + private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>(); + private int mBufferSize = 262144; + private BufferProcessor mBufferProcessor = new BufferProcessor(); + private AirspySampleAdapter mSampleAdapter = new AirspySampleAdapter(); + private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB( 0.01f ); + private HilbertTransform mHilbertTransform = new HilbertTransform(); + + private AirspyDeviceInformation mDeviceInfo; + private List mSampleRates = new ArrayList<>(); + private int mSampleRate = 0; + + public AirspyTunerController( Device device, ThreadPoolManager threadPoolManager ) + throws SourceException + { + super( FREQUENCY_MIN, FREQUENCY_MAX ); + + mDevice = device; + mThreadPoolManager = threadPoolManager; + } + + public void init() throws SourceException + { + mDeviceHandle = new DeviceHandle(); + + int result = LibUsb.open( mDevice, mDeviceHandle ); + + if( result != 0 ) + { + if( result == LibUsb.ERROR_ACCESS ) + { + mLog.error( "Unable to access Airspy - insufficient permissions." + + " If you are running a Linux OS, have you installed the " + + "airspy rules file in \\etc\\udev\\rules.d ??" ); + } + + throw new SourceException( "Couldn't open airspy device - " + + LibUsb.strError( result ) ); + } + + try + { + claimInterface(); + } + catch( Exception e ) + { + throw new SourceException( "Airspy Tuner Controller - error while " + + "setting USB configuration, claiming USB interface or " + + "reset the kernel mode driver", e ); + } + + try + { + setSamplePacking( false ); + } + catch( LibUsbException | UsbException | UnsupportedOperationException e ) + { + mLog.info( "Sample packing is not supported by airspy firmware" ); + } + + try + { + setReceiverMode( true ); + } + catch( Exception e ) + { + mLog.error( "Couldn't enable airspy receiver mode", e ); + } + + setFrequency( FREQUENCY_DEFAULT ); + + try + { + determineAvailableSampleRates(); + } + catch ( LibUsbException | UsbException e ) + { + mLog.error( "Error identifying available samples rates", e ); + } + + try + { + setSampleRate( DEFAULT_SAMPLE_RATE ); + } + catch ( IllegalArgumentException | LibUsbException | UsbException e ) + { + mLog.error( "Setting sample rate is not supported by firmware", e ); + } + } + + /** + * Claims the USB interface. If another application currently has + * the interface claimed, the USB_FORCE_CLAIM_INTERFACE setting + * will dictate if the interface is forcibly claimed from the other + * application + */ + private void claimInterface() throws SourceException + { + if( mDeviceHandle != null ) + { + int result = LibUsb.kernelDriverActive( mDeviceHandle, USB_INTERFACE ); + + if( result == 1 ) + { + + result = LibUsb.detachKernelDriver( mDeviceHandle, USB_INTERFACE ); + + if( result != LibUsb.SUCCESS ) + { + mLog.error( "failed attempt to detach kernel driver [" + + LibUsb.errorName( result ) + "]" ); + + throw new SourceException( "couldn't detach kernel driver " + + "from device" ); + } + } + + result = LibUsb.setConfiguration( mDeviceHandle, 1 ); + + if( result != LibUsb.SUCCESS ) + { + throw new SourceException( "couldn't set USB configuration 1 [" + + LibUsb.errorName( result ) + "]" ); + } + + result = LibUsb.claimInterface( mDeviceHandle, USB_INTERFACE ); + + if( result != LibUsb.SUCCESS ) + { + throw new SourceException( "couldn't claim usb interface [" + + LibUsb.errorName( result ) + "]" ); + } + } + else + { + throw new SourceException( "couldn't claim usb interface - no " + + "device handle" ); + } + } + + @Override + public void apply( TunerConfiguration config ) throws SourceException + { + if( config instanceof AirspyTunerConfiguration ) + { + AirspyTunerConfiguration airspy = (AirspyTunerConfiguration)config; + + int sampleRate = airspy.getSampleRate(); + + AirspySampleRate rate = getSampleRate( sampleRate ); + + if( rate == null ) + { + rate = DEFAULT_SAMPLE_RATE; + } + + try + { + setSampleRate( rate ); + } + catch( UsbException e ) + { + throw new SourceException( "Couldn't set sample rate [" + + rate.toString() + "]", e ); + } + + try + { + setIFGain( airspy.getIFGain() ); + setMixerGain( airspy.getMixerGain() ); + setLNAGain( airspy.getLNAGain() ); + + setMixerAGC( airspy.isMixerAGC() ); + setLNAAGC( airspy.isLNAAGC() ); + } + catch( Exception e ) + { + throw new SourceException( "Couldn't apply gain settings from " + + "airspy config", e ); + } + + } + else + { + throw new IllegalArgumentException( "Invalid tuner config:" + + config.getClass() ); + } + } + + @Override + public long getTunedFrequency() throws SourceException + { + return mFrequencyController.getTunedFrequency(); + } + + @Override + public void setTunedFrequency( long frequency ) throws SourceException + { + if( FREQUENCY_MIN <= frequency && frequency <= FREQUENCY_MAX ) + { + ByteBuffer buffer = ByteBuffer.allocateDirect( 4 ); + + buffer.order( ByteOrder.LITTLE_ENDIAN ); + + buffer.putInt( (int)frequency ); + + buffer.rewind(); + + try + { + write( Command.SET_FREQUENCY, 0, 0, buffer ); + } + catch( UsbException e ) + { + mLog.error( "error setting frequency [" + frequency + "]", e ); + + throw new SourceException( "error setting frequency [" + + frequency + "]", e ); + } + } + else + { + throw new SourceException( "Frequency [" + frequency + "] outside " + + "of tunable range " + FREQUENCY_MIN + "-" + FREQUENCY_MAX ); + } + } + + @Override + public int getCurrentSampleRate() throws SourceException + { + return mSampleRate; + } + + /** + * Sets the sample rate to the rate specified by the index value in the + * available sample rates map + * + * @param index to a sample rate in the available samples rates map. + * + * @throws IllegalArgumentException if index is not a valid rate index + * @throws LibUsbException if there was a read error or if this operation + * is not supported by the current firmware + * + * @throws UsbException if there was a USB error + */ + public void setSampleRate( AirspySampleRate rate ) throws + LibUsbException, UsbException + { + if( rate.getRate() != mSampleRate ) + { + int result = readByte( Command.SET_SAMPLE_RATE, 0, rate.getIndex(), true ); + + if( result != 1 ) + { + throw new UsbException( "Error setting sample rate [" + + rate + "] rate - return value [" + result + "]" ); + } + else + { + mSampleRate = rate.getRate(); + mFrequencyController.setSampleRate( mSampleRate ); + } + } + } + + /** + * Returns a list of sample rates supported by the firmware version + */ + public List getSampleRates() + { + return mSampleRates; + } + + /** + * Airspy sample rate object that matches the current sample rate setting. + */ + public AirspySampleRate getSampleRate() + { + return getSampleRate( mSampleRate ); + } + + /** + * Airspy sample rate object that matches the specified rate in hertz, or + * null if there are no available sample rates for the tuner that match the + * argument value. + */ + public AirspySampleRate getSampleRate( int rate ) + { + for( AirspySampleRate sampleRate: mSampleRates ) + { + if( sampleRate.getRate() == rate ) + { + return sampleRate; + } + } + + //We should never get to here ... + return null; + } + + /** + * Enables/Disables sample packing to allow two 12-bit samples to be packed + * into 3 bytes (enabled) or 4 bytes (disabled). + * + * @param enabled + * + * @throws UsbException if sample packing is not supported by the current + * device firmware or if there were usb communication issues + */ + public void setSamplePacking( boolean enabled ) + throws LibUsbException, UsbException + { + int result = readByte( Command.SET_PACKING, 0, ( enabled ? 1 : 0 ), true ); + + if( result != 1 ) + { + throw new UsbException( "Couldnt set sample packing enabled: " + enabled ); + } + + /* If we didn't throw an exception above, then update the sample adapter + * to process samples accordingly */ + mSampleAdapter.setSamplePacking( enabled ); + } + + /** + * Enables/disables the mixer automatic gain setting + * + * @param enabled + * + * @throws LibUsbException on unsuccessful read operation + * @throws UsbException on USB error + */ + public void setMixerAGC( boolean enabled ) + throws LibUsbException, UsbException + { + int result = readByte( Command.SET_MIXER_AGC, 0, ( enabled ? 1 : 0 ), true ); + + if( result != LibUsb.SUCCESS ) + { + throw new UsbException( "Couldnt set mixer AGC enabled: " + enabled ); + } + } + + /** + * Enables/disables the low noise amplifier automatic gain setting + * + * @param enabled + * + * @throws LibUsbException on unsuccessful read operation + * @throws UsbException on USB error + */ + public void setLNAAGC( boolean enabled ) throws LibUsbException, UsbException + { + int result = readByte( Command.SET_LNA_AGC, 0, ( enabled ? 1 : 0 ), true ); + + if( result != LibUsb.SUCCESS ) + { + throw new UsbException( "Couldnt set LNA AGC enabled: " + enabled ); + } + } + + /** + * Sets LNA gain + * + * @param gain - value within range of LNA_GAIN_MIN to LNA_GAIN_MAX + * + * @throws LibUsbException on error in java USB wrapper + * @throws UsbException on error in USB transfer + * @throws IllegalArgumentException if gain value is invalid + */ + public void setLNAGain( int gain ) + throws LibUsbException, UsbException, IllegalArgumentException + { + if( LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX ) + { + int result = readByte( Command.SET_LNA_GAIN, 0, gain, true ); + + if( result != LibUsb.SUCCESS ) + { + throw new UsbException( "Couldnt set LNA gain to: " + gain ); + } + } + else + { + throw new IllegalArgumentException( "LNA gain value [" + gain + + "] is outside value range: " + LNA_GAIN_MIN + "-" + LNA_GAIN_MAX ); + } + } + + /** + * Sets Mixer gain + * + * @param gain - value within range of MIXER_GAIN_MIN to MIXER_GAIN_MAX + * + * @throws LibUsbException on error in java USB wrapper + * @throws UsbException on error in USB transfer + * @throws IllegalArgumentException if gain value is invalid + */ + public void setMixerGain( int gain ) + throws LibUsbException, UsbException, IllegalArgumentException + { + if( MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX ) + { + int result = readByte( Command.SET_MIXER_GAIN, 0, gain, true ); + + if( result != LibUsb.SUCCESS ) + { + throw new UsbException( "Couldnt set mixer gain to: " + gain ); + } + } + else + { + throw new IllegalArgumentException( "Mixer gain value [" + gain + + "] is outside value range: " + MIXER_GAIN_MIN + "-" + MIXER_GAIN_MAX ); + } + } + + /** + * Sets IF (VGA) gain + * + * @param gain - value within range of VGA_GAIN_MIN to VGA_GAIN_MAX + * + * @throws LibUsbException on error in java USB wrapper + * @throws UsbException on error in USB transfer + * @throws IllegalArgumentException if gain value is invalid + */ + public void setIFGain( int gain ) + throws LibUsbException, UsbException, IllegalArgumentException + { + if( IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX ) + { + int result = readByte( Command.SET_VGA_GAIN, 0, gain, true ); + + if( result != LibUsb.SUCCESS ) + { + throw new UsbException( "Couldnt set VGA gain to: " + gain ); + } + } + else + { + throw new IllegalArgumentException( "VGA gain value [" + gain + + "] is outside value range: " + IF_GAIN_MIN + "-" + IF_GAIN_MAX ); + } + } + + public void setReceiverMode( boolean enabled ) throws LibUsbException, UsbException + { + //Empty buffer to throw away + ByteBuffer buffer = ByteBuffer.allocateDirect( 0 ); + + write( Command.RECEIVER_MODE, ( enabled ? 1 : 0 ), 0, buffer ); + } + + /** + * Queries the device for available sample rates. Will always provide at + * least the default 10 MHz sample rate. + */ + private void determineAvailableSampleRates() + throws LibUsbException, UsbException + { + mSampleRates.clear(); + + mSampleRates.add( DEFAULT_SAMPLE_RATE ); + + //Get a count of available sample rates. If we get an exception, then + //we're using an older firmware revision and only the default 10 MHz + //rate is supported + try + { + byte[] rawCount = readArray( Command.GET_SAMPLE_RATES, 0, 0, 4 ); + + + if( rawCount != null ) + { + int count = EndianUtils.readSwappedInteger( rawCount, 0 ); + + mLog.debug( "There are [" + count + "] sample rates available" ); + + byte[] rawRates = readArray( Command.GET_SAMPLE_RATES, 0, + count, ( count * 4 ) ); + + for( int x = 0; x < count; x++ ) + { + int rate = EndianUtils.readSwappedInteger( rawRates, ( x * 4 ) ); + + if( rate != DEFAULT_SAMPLE_RATE.getRate() ) + { + mSampleRates.add( new AirspySampleRate( x, rate, + formatSampleRate( rate ) ) ); + } + } + } + } + catch( LibUsbException e ) + { + //Press on, nothing else to do here .. + } + } + + /** + * Formats the rate in hertz for display as megahertz + */ + private static String formatSampleRate( int rate ) + { + return MHZ_FORMATTER.format( (double)rate / 1E6d ); + } + + /** + * Device information + */ + public AirspyDeviceInformation getDeviceInfo() + { + //Lazy initialization + if( mDeviceInfo == null ) + { + readDeviceInfo(); + } + + return mDeviceInfo; + } + + /** + * Reads version information from the device and populates the info object + */ + private void readDeviceInfo() + { + if( mDeviceInfo == null ) + { + mDeviceInfo = new AirspyDeviceInformation(); + } + + /* Board ID */ + try + { + int boardID = readByte( Command.BOARD_ID_READ, 0, 0, true ); + + mDeviceInfo.setBoardID( boardID ); + } + catch ( LibUsbException | UsbException e ) + { + mLog.error( "Error reading airspy board ID", e ); + } + + /* Version String */ + try + { + //NOTE: libairspy is internally reading 127 bytes, however airspy_info + //script is telling it to read 255 bytes ... things that make you go hmmmm + byte[] version = readArray( Command.VERSION_STRING_READ, 0, 0, 127 ); + + mDeviceInfo.setVersion( version ); + } + catch ( LibUsbException | UsbException e ) + { + mLog.error( "Error reading airspy version string", e ); + } + + /* Part ID and Serial Number */ + try + { + //Read 6 x 32-bit integers = 24 bytes + byte[] serial = readArray( + Command.BOARD_PART_ID_SERIAL_NUMBER_READ, 0, 0, 24 ); + + mDeviceInfo.setPartAndSerialNumber( serial ); + } + catch ( LibUsbException | UsbException e ) + { + mLog.error( "Error reading airspy version string", e ); + } + } + + /** + * Reads a single byte value from the device. + * + * @param command - airspy command + * + * @param value - value field for usb setup packet + * @param index - index field for usb setup packet + * + * @return - byte value as an integer + * + * @throws LibUsbException if the operation is unsuccesful + * @throws UsbException on any usb errors + */ + private int readByte( Command command, int value, int index, boolean signed ) + throws LibUsbException, UsbException + { + if( mDeviceHandle != null ) + { + ByteBuffer buffer = ByteBuffer.allocateDirect( 1 ); + + int transferred = LibUsb.controlTransfer( mDeviceHandle, + USB_REQUEST_IN, + command.getValue(), + (short)value, + (short)index, + buffer, + USB_TIMEOUT_MS ); + + if( transferred < 0 ) + { + throw new LibUsbException( "read error", transferred ); + } + + byte result = buffer.get( 0 ); + + if( signed ) + { + return ( result & 0xFF ); + } + else + { + return result; + } + } + else + { + throw new LibUsbException( "device handle is null", + LibUsb.ERROR_NO_DEVICE ); + } + } + + /** + * Reads a multi-byte value from the device + * + * @param command - airspy command + * @param value - usb packet value + * @param index - usb packet index + * @param length - number of bytes to read + * @return - bytes read from the device + * + * @throws LibUsbException if quantity of bytes read doesn't equal the + * requested number of bytes + * @throws UsbException on error communicating with the device + */ + private byte[] readArray( Command command, int value, int index, int length ) + throws LibUsbException, UsbException + { + if( mDeviceHandle != null ) + { + ByteBuffer buffer = ByteBuffer.allocateDirect( length ); + + int transferred = LibUsb.controlTransfer( mDeviceHandle, + USB_REQUEST_IN, + command.getValue(), + (short)value, + (short)index, + buffer, + USB_TIMEOUT_MS ); + + if( transferred < 0 ) + { + throw new LibUsbException( "read error", transferred ); + } + + byte[] results = new byte[ transferred ]; + + buffer.get( results ); + + return results; + } + else + { + throw new LibUsbException( "device handle is null", + LibUsb.ERROR_NO_DEVICE ); + } + } + + /** + * Writes the buffer contents to the device + * + * @param command - airspy command + * @param value - usb packet value + * @param index - usb packet index + * @param buffer - data to write to the device + * @throws UsbException on error + */ + public void write( Command command, int value, int index, ByteBuffer buffer ) + throws UsbException + { + if( mDeviceHandle != null ) + { + int transferred = LibUsb.controlTransfer( mDeviceHandle, + USB_REQUEST_OUT, + command.getValue(), + (short)value, + (short)index, + buffer, + USB_TIMEOUT_MS ); + + if( transferred < 0 ) + { + throw new LibUsbException( "error writing byte buffer", + transferred ); + } + else if( transferred != buffer.capacity() ) + { + throw new LibUsbException( "transferred bytes [" + + transferred + "] is not what was expected [" + + buffer.capacity() + "]", transferred ); + } + } + else + { + throw new LibUsbException( "device handle is null", + LibUsb.ERROR_NO_DEVICE ); + } + } + + /** + * Airspy Board Identifier + */ + public enum BoardID + { + AIRSPY( 0, "Airspy" ), + UNKNOWN( -1, "Unknown" ); + + private int mValue; + private String mLabel; + + private BoardID( int value, String label ) + { + mValue = value; + mLabel = label; + } + + public int getValue() + { + return mValue; + } + + public String getLabel() + { + return mLabel; + } + + public static BoardID fromValue( int value ) + { + if( value == 0 ) + { + return AIRSPY; + } + + return UNKNOWN; + } + } + + /** + * Airspy Commands + */ + public enum Command + { + INVALID( 0 ), + RECEIVER_MODE( 1 ), + SI5351C_WRITE( 2 ), + SI5351C_READ( 3 ), + R820T_WRITE( 4 ), + R820T_READ( 5 ), + SPIFLASH_ERASE( 6 ), + SPIFLASH_WRITE( 7 ), + SPIFLASH_READ( 8 ), + BOARD_ID_READ( 9 ), + VERSION_STRING_READ( 10 ), + BOARD_PART_ID_SERIAL_NUMBER_READ( 11 ), + SET_SAMPLE_RATE( 12 ), + SET_FREQUENCY( 13 ), + SET_LNA_GAIN( 14 ), + SET_MIXER_GAIN( 15 ), + SET_VGA_GAIN( 16 ), + SET_LNA_AGC( 17 ), + SET_MIXER_AGC( 18 ), + MS_VENDOR_COMMAND( 19 ), + SET_RF_BIAS_COMMAND( 20 ), + GPIO_WRITE( 21 ), + GPIO_READ( 22 ), + GPIO_DIR__WRITE( 23 ), + GPIO_DIR_READ( 24 ), + GET_SAMPLE_RATES( 25 ), + SET_PACKING( 26 ); + + private int mValue; + + private Command( int value ) + { + mValue = value; + } + + public byte getValue() + { + return (byte)mValue; + } + + public static Command fromValue( int value ) + { + if( 0 <= value && value <= 25 ) + { + return Command.values()[ value ]; + } + + return INVALID; + } + } + + public enum ReceiverMode + { + OFF( 0 ), + ON( 1 ); + + private int mValue; + + private ReceiverMode( int value ) + { + mValue = value; + } + + public int getValue() + { + return mValue; + } + } + + /** + * General Purpose Input/Output Ports (accessible on the airspy board) + */ + public enum GPIOPort + { + PORT_0( 0 ), + PORT_1( 1 ), + PORT_2( 2 ), + PORT_3( 3 ), + PORT_4( 4 ), + PORT_5( 5 ), + PORT_6( 6 ), + PORT_7( 7 ); + + private int mValue; + + private GPIOPort( int value ) + { + mValue = value; + } + + public int getValue() + { + return mValue; + } + } + + /** + * General Purpose Input/Output Pins (accessible on the airspy board) + */ + public enum GPIOPin + { + PIN_0( 0 ), + PIN_1( 1 ), + PIN_2( 2 ), + PIN_3( 3 ), + PIN_4( 4 ), + PIN_5( 5 ), + PIN_6( 6 ), + PIN_7( 7 ), + PIN_8( 8 ), + PIN_9( 9 ), + PIN_10( 10 ), + PIN_11( 11 ), + PIN_12( 12 ), + PIN_13( 13 ), + PIN_14( 14 ), + PIN_15( 15 ), + PIN_16( 16 ), + PIN_17( 17 ), + PIN_18( 18 ), + PIN_19( 19 ), + PIN_20( 20 ), + PIN_21( 21 ), + PIN_22( 22 ), + PIN_23( 23 ), + PIN_24( 24 ), + PIN_25( 25 ), + PIN_26( 26 ), + PIN_27( 27 ), + PIN_28( 28 ), + PIN_29( 29 ), + PIN_30( 30 ), + PIN_31( 31 ); + + private int mValue; + + private GPIOPin( int value ) + { + mValue = value; + } + + public int getValue() + { + return mValue; + } + } + + /** + * Adds a sample listener. If the buffer processing thread is + * not currently running, starts it running in a new thread. + */ + public void addListener( Listener listener ) + { + mComplexBufferBroadcaster.addListener( listener ); + + if( mBufferProcessor == null || !mBufferProcessor.isRunning() ) + { + mBufferProcessor = new BufferProcessor(); + + Thread thread = new Thread( mBufferProcessor ); + thread.setDaemon( true ); + thread.setName( "Airspy Buffer Processor" ); + + thread.start(); + } + } + + /** + * Removes the sample listener. If this is the last registered listener, + * shuts down the buffer processing thread. + */ + public void removeListener( Listener listener ) + { + mComplexBufferBroadcaster.removeListener( listener ); + + if( !mComplexBufferBroadcaster.hasListeners() ) + { + mBufferProcessor.stop(); + } + } + + /** + * Buffer processing thread. Fetches samples from the Airspy Tuner and + * dispatches them to all registered listeners + */ + public class BufferProcessor implements Runnable, TransferCallback + { + private ScheduledFuture mSampleDispatcherTask; + private CopyOnWriteArrayList mTransfers; + private AtomicBoolean mRunning = new AtomicBoolean(); + + @Override + public void run() + { + if( mRunning.compareAndSet( false, true ) ) + { + prepareTransfers(); + + for( Transfer transfer: mTransfers ) + { + int result = LibUsb.submitTransfer( transfer ); + + if( result != LibUsb.SUCCESS ) + { + mLog.error( "error submitting transfer [" + + LibUsb.errorName( result ) + "]" ); + break; + } + } + + mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate( + ThreadType.SOURCE_SAMPLE_PROCESSING, new BufferDispatcher(), + 20, TimeUnit.MILLISECONDS ); + + while( mRunning.get() ) + { + ByteBuffer completed = ByteBuffer.allocateDirect( 4 ); + + int result = LibUsb.handleEventsTimeoutCompleted( + null, USB_TIMEOUT_MS, completed.asIntBuffer() ); + + if( result != LibUsb.SUCCESS ) + { + mLog.error( "error handling events for libusb" ); + } + } + } + } + + /** + * Stops the sample fetching thread + */ + public void stop() + { + if( mRunning.compareAndSet( true, false ) ) + { + cancel(); + + if( mSampleDispatcherTask != null ) + { + mSampleDispatcherTask.cancel( true ); + mFilledBuffers.clear(); + } + } + } + + /** + * Indicates if this thread is running + */ + public boolean isRunning() + { + return mRunning.get(); + } + + @Override + public void processTransfer( Transfer transfer ) + { + if( transfer.status() == LibUsb.TRANSFER_COMPLETED ) + { + ByteBuffer buffer = transfer.buffer(); + + byte[] data = new byte[ transfer.actualLength() ]; + + buffer.get( data ); + + buffer.rewind(); + + mFilledBuffers.add( data ); + + if( !isRunning() ) + { + LibUsb.cancelTransfer( transfer ); + } + } + + switch( transfer.status() ) + { + case LibUsb.TRANSFER_COMPLETED: + /* resubmit the transfer */ + int result = LibUsb.submitTransfer( transfer ); + + if( result != LibUsb.SUCCESS ) + { + mLog.error( "couldn't resubmit buffer transfer to tuner" ); + LibUsb.freeTransfer( transfer ); + mTransfers.remove( transfer ); + } + break; + case LibUsb.TRANSFER_CANCELLED: + /* free the transfer and remove it */ + LibUsb.freeTransfer( transfer ); + mTransfers.remove( transfer ); + break; + default: + /* unexpected error */ + mLog.error( "transfer error" ); + } + } + + private void cancel() + { + for( Transfer transfer: mTransfers ) + { + LibUsb.cancelTransfer( transfer ); + } + + ByteBuffer completed = ByteBuffer.allocateDirect( 4 ); + + int result = LibUsb.handleEventsTimeoutCompleted( + null, USB_TIMEOUT_MS, completed.asIntBuffer() ); + + if( result != LibUsb.SUCCESS ) + { + mLog.error( "error handling usb events during cancel [" + + LibUsb.errorName( result ) + "]" ); + } + } + + /** + * Prepares (allocates) a set of transfer buffers for use in + * transferring data from the tuner via the bulk interface + */ + private void prepareTransfers() throws LibUsbException + { + mTransfers = new CopyOnWriteArrayList(); + + for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ ) + { + Transfer transfer = LibUsb.allocTransfer(); + + if( transfer == null ) + { + throw new LibUsbException( "couldn't allocate transfer", + LibUsb.ERROR_NO_MEM ); + } + + final ByteBuffer buffer = + ByteBuffer.allocateDirect( mBufferSize ); + + LibUsb.fillBulkTransfer( transfer, + mDeviceHandle, + USB_ENDPOINT, + buffer, + BufferProcessor.this, + "Buffer #" + x, + USB_TIMEOUT_MS ); + + mTransfers.add( transfer ); + } + } + } + + /** + * Fetches byte[] chunks from the raw sample buffer. Converts each byte + * array and broadcasts the array to all registered listeners + */ + public class BufferDispatcher implements Runnable + { + @Override + public void run() + { + try + { + ArrayList buffers = new ArrayList<>(); + + mFilledBuffers.drainTo( buffers ); + + for( byte[] buffer: buffers ) + { + float[] realSamples = mSampleAdapter.convert( buffer ); + + realSamples = mDCFilter.filter( realSamples ); + + float[] quadratureSamples = mHilbertTransform.filter( realSamples ); + + mComplexBufferBroadcaster.broadcast( + new ComplexBuffer( quadratureSamples ) ); + } + } + catch( Exception e ) + { + mLog.error( "error during Airspy buffer dispatching", e ); + } + } + } +} diff --git a/src/source/tuner/airspy/AirspyTunerEditorPanel.java b/src/source/tuner/airspy/AirspyTunerEditorPanel.java new file mode 100644 index 00000000..08dec674 --- /dev/null +++ b/src/source/tuner/airspy/AirspyTunerEditorPanel.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * 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 source.tuner.airspy; + +import gui.control.JFrequencyControl; + +import java.awt.Color; + +import javax.swing.JLabel; +import javax.swing.JPanel; + +import net.miginfocom.swing.MigLayout; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import source.SourceException; +import source.tuner.frequency.FrequencyChangeEvent; +import source.tuner.frequency.FrequencyChangeEvent.Attribute; +import source.tuner.frequency.FrequencyChangeListener; + +import com.jidesoft.swing.JideTabbedPane; + +import controller.ResourceManager; + +public class AirspyTunerEditorPanel extends JPanel + implements FrequencyChangeListener +{ + private static final long serialVersionUID = 1L; + + private final static Logger mLog = + LoggerFactory.getLogger( AirspyTunerEditorPanel.class ); + + private JFrequencyControl mFrequencyControl; + + private AirspyTuner mTuner; + private AirspyTunerController mController; + private ResourceManager mResourceManager; + + public AirspyTunerEditorPanel( AirspyTuner tuner, + ResourceManager resourceManager ) + { + mTuner = tuner; + mController = mTuner.getController(); + mResourceManager = resourceManager; + + initGUI(); + } + + private void initGUI() + { + setLayout( new MigLayout( "fill,wrap 2", + "[right,grow][grow]", + "[][][][][][grow]" ) ); + + add( new JLabel( "Airspy Tuner" ), "span,align center" ); + + mFrequencyControl = new JFrequencyControl(); + + mFrequencyControl.addListener( this ); + + /* Add frequency control as frequency change listener. This creates a + * feedback loop, so the control does not rebroadcast the event */ + mTuner.addListener( mFrequencyControl ); + + mFrequencyControl.setFrequency( mController.getFrequency(), false ); + + add( mFrequencyControl, "span,align center" ); + + JideTabbedPane tabs = new JideTabbedPane(); + tabs.setFont( this.getFont() ); + tabs.setForeground( Color.BLACK ); + + add( tabs, "span,grow,push" ); + + tabs.add( "Config", new AirspyTunerConfigurationPanel( + mResourceManager, mTuner.getController() ) ); + + tabs.add( "Info", new AirspyInformationPanel( mTuner.getController() ) ); + } + + @Override + public void frequencyChanged( FrequencyChangeEvent event ) + { + if( event.getAttribute() == Attribute.FREQUENCY ) + { + try + { + mController.setFrequency( event.getValue().longValue() ); + } + catch ( SourceException e ) + { + mLog.error( "error setting frequency [" + + event.getValue().longValue() + "]", e ); + } + } + } +} diff --git a/src/source/tuner/ettus/B100Tuner.java b/src/source/tuner/ettus/B100Tuner.java index d80190bb..9ec27054 100644 --- a/src/source/tuner/ettus/B100Tuner.java +++ b/src/source/tuner/ettus/B100Tuner.java @@ -86,6 +86,12 @@ public class B100Tuner extends Tuner return 0; } + @Override + public double getSampleSize() + { + return 16.0; + } + @Override public long getFrequency() throws SourceException { diff --git a/src/source/tuner/fcd/FCDTuner.java b/src/source/tuner/fcd/FCDTuner.java index 1aadbba6..ea84063c 100644 --- a/src/source/tuner/fcd/FCDTuner.java +++ b/src/source/tuner/fcd/FCDTuner.java @@ -99,6 +99,12 @@ public class FCDTuner extends MixerTuner return (int)mMixerTunerType.getAudioFormat().getSampleRate(); } + @Override + public double getSampleSize() + { + return 16.0; + } + @Override public long getFrequency() throws SourceException { diff --git a/src/source/tuner/hackrf/HackRFTuner.java b/src/source/tuner/hackrf/HackRFTuner.java index ee13c649..deb6a479 100644 --- a/src/source/tuner/hackrf/HackRFTuner.java +++ b/src/source/tuner/hackrf/HackRFTuner.java @@ -20,7 +20,6 @@ package source.tuner.hackrf; import java.util.concurrent.RejectedExecutionException; import javax.swing.JPanel; -import javax.usb.UsbException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +34,6 @@ import source.tuner.TunerClass; import source.tuner.TunerConfiguration; import source.tuner.TunerType; import source.tuner.hackrf.HackRFTunerController.BoardID; -import source.tuner.hackrf.HackRFTunerController.Serial; import controller.ResourceManager; import controller.ThreadPoolManager; @@ -53,23 +51,6 @@ public class HackRFTuner extends Tuner /* Register for frequency/sample rate changes */ mController.addListener( this ); - - try - { - BoardID id = mController.getBoardID(); - super.setName( id.getLabel() ); - - String version = mController.getFirmwareVersion(); - mLog.info( "HackRF firmware: " + version ); - - Serial serial = mController.getSerial(); - mLog.info( "HackRF Part ID: " + serial.getPartID() ); - mLog.info( "HackRF Serial Number: " + serial.getSerialNumber() ); - } - catch( UsbException e ) - { - mLog.error( "couldn't read HackRF board id", e ); - } } public void dispose() @@ -112,6 +93,12 @@ public class HackRFTuner extends Tuner return mController.getSampleRate(); } + @Override + public double getSampleSize() + { + return 8.0; + } + @Override public long getFrequency() throws SourceException { diff --git a/src/source/tuner/hackrf/HackRFTunerController.java b/src/source/tuner/hackrf/HackRFTunerController.java index 2b1c746a..f161cdbd 100644 --- a/src/source/tuner/hackrf/HackRFTunerController.java +++ b/src/source/tuner/hackrf/HackRFTunerController.java @@ -142,8 +142,21 @@ public class HackRFTunerController extends TunerController { mDeviceHandle = new DeviceHandle(); - LibUsb.open( mDevice, mDeviceHandle ); + int result = LibUsb.open( mDevice, mDeviceHandle ); + if( result != 0 ) + { + if( result == LibUsb.ERROR_ACCESS ) + { + mLog.error( "Unable to access HackRF - insufficient permissions. " + + "If you are running a Linux OS, have you installed the " + + "hackRF rules file in \\etc\\udev\\rules.d ??" ); + } + + throw new SourceException( "Couldn't open hackrf device - " + + LibUsb.strError( result ) ); + } + try { claimInterface(); @@ -298,7 +311,7 @@ public class HackRFTunerController extends TunerController */ public long getTunedFrequency() throws SourceException { - return 0; + return mFrequencyController.getTunedFrequency(); } @Override diff --git a/src/source/tuner/rtl/RTL2832Tuner.java b/src/source/tuner/rtl/RTL2832Tuner.java index 2f82e760..a86b263a 100644 --- a/src/source/tuner/rtl/RTL2832Tuner.java +++ b/src/source/tuner/rtl/RTL2832Tuner.java @@ -127,6 +127,12 @@ public class RTL2832Tuner extends Tuner + "sample rate", e ); } } + + @Override + public double getSampleSize() + { + return 8.0; + } @Override public long getFrequency() throws SourceException diff --git a/src/spectrum/ChannelSpectrumPanel.java b/src/spectrum/ChannelSpectrumPanel.java index d57870fe..c78ddad1 100644 --- a/src/spectrum/ChannelSpectrumPanel.java +++ b/src/spectrum/ChannelSpectrumPanel.java @@ -13,8 +13,12 @@ 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import net.miginfocom.swing.MigLayout; import sample.Listener; import sample.SampleType; @@ -31,10 +35,11 @@ import source.tuner.frequency.FrequencyChangeEvent.Attribute; import spectrum.converter.DFTResultsConverter; import spectrum.converter.RealDecibelConverter; import spectrum.menu.AveragingItem; -import spectrum.menu.DBScaleItem; import spectrum.menu.FFTWidthItem; import spectrum.menu.FFTWindowTypeItem; import spectrum.menu.FrameRateItem; +import spectrum.menu.SmoothingItem; +import spectrum.menu.SmoothingTypeItem; import controller.ResourceManager; import controller.channel.Channel; import controller.channel.ChannelEvent; @@ -42,6 +47,9 @@ import controller.channel.ChannelEventListener; import dsp.filter.Filters; import dsp.filter.FloatHalfBandFilter; import dsp.filter.Window.WindowType; +import dsp.filter.halfband.real.HalfBandFilter_RB_RB; +import dsp.filter.hilbert.HilbertTransform; +import dsp.filter.smoothing.SmoothingFilter.SmoothingType; public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener, @@ -49,9 +57,9 @@ public class ChannelSpectrumPanel extends JPanel SettingChangeListener, SpectralDisplayAdjuster { - private static final long serialVersionUID = 1L; - private static final String CHANNEL_SPECTRUM_AVERAGING_SIZE = - "channel_spectrum_averaging_size"; + private static final Logger mLog = LoggerFactory.getLogger( ChannelSpectrumPanel.class ); + + private static final long serialVersionUID = 1L; private ResourceManager mResourceManager; private DFTProcessor mDFTProcessor = new DFTProcessor( SampleType.REAL ); @@ -62,13 +70,13 @@ public class ChannelSpectrumPanel extends JPanel private Channel mCurrentChannel; - private int mSpectrumAveragingSize; private int mSampleBufferSize = 2400; private float[] mSamples = new float[ mSampleBufferSize ]; private int mSamplePointer = 0; - private DecimatingSampleAssembler mDecimatingSampleAssembler; - + private HalfBandFilter_RB_RB mDecimatingFilter = new HalfBandFilter_RB_RB( + Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true ); + private AtomicBoolean mEnabled = new AtomicBoolean(); public ChannelSpectrumPanel( ResourceManager resourceManager ) @@ -76,20 +84,12 @@ public class ChannelSpectrumPanel extends JPanel mResourceManager = resourceManager; mResourceManager.getSettingsManager().addListener( this ); mSpectrumPanel = new SpectrumPanel( mResourceManager ); + mSpectrumPanel.setAveraging( 1 ); + mOverlayPanel = new ChannelOverlayPanel( mResourceManager ); - - - mSpectrumAveragingSize = 10; -// mSpectrumAveragingSize = mResourceManager.getSettingsManager() -// .getIntegerSetting( CHANNEL_SPECTRUM_AVERAGING_SIZE, 10 ).getValue(); - - mSpectrumPanel.setAveraging( mSpectrumAveragingSize ); - mDFTProcessor.addConverter( mDFTConverter ); mDFTConverter.addListener( mSpectrumPanel ); - mDecimatingSampleAssembler = new DecimatingSampleAssembler( mDFTProcessor ); - /* Set the DFTProcessor to the decimated 24kHz sample rate */ mDFTProcessor.frequencyChanged( new FrequencyChangeEvent( Attribute.SAMPLE_RATE, 24000 ) ); @@ -254,10 +254,11 @@ public class ChannelSpectrumPanel extends JPanel @Override public void receive( RealBuffer buffer ) { - for( float sample: buffer.getSamples() ) - { - mDecimatingSampleAssembler.receive( sample ); - } + RealBuffer decimated = mDecimatingFilter.filter( buffer ); + + //Hack: we're placing real samples in a complex buffer that the DFT + //processor is expecting. + mDFTProcessor.receive( new ComplexBuffer( decimated.getSamples() ) ); } /** @@ -367,17 +368,9 @@ public class ChannelSpectrumPanel extends JPanel */ JMenu averagingMenu = new JMenu( "Averaging" ); averagingMenu.add( - new AveragingItem( ChannelSpectrumPanel.this, 4 ) ); + new AveragingItem( ChannelSpectrumPanel.this, 2 ) ); displayMenu.add( averagingMenu ); - /** - * Baseline menu - */ - JMenu baselineMenu = new JMenu( "Baseline" ); - baselineMenu.add( - new DBScaleItem( ChannelSpectrumPanel.this, 50 ) ); - displayMenu.add( baselineMenu ); - /** * FFT width */ @@ -416,6 +409,24 @@ public class ChannelSpectrumPanel extends JPanel 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, @@ -443,37 +454,35 @@ public class ChannelSpectrumPanel extends JPanel mSpectrumPanel.setAveraging( averaging ); } - @Override - public int getDBScale() - { - return mSpectrumPanel.getDBScale(); - } - - @Override - public void setDBScale( int baseline ) - { - mSpectrumPanel.setDBScale( baseline ); - } - - public class DecimatingSampleAssembler + public void setSampleSize( double sampleSize ) { - private FloatHalfBandFilter mDecimationFilter = new FloatHalfBandFilter( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002f ); - - private SampleAssembler mSampleAssembler; - - public DecimatingSampleAssembler( Listener listener ) - { - mSampleAssembler = new SampleAssembler( listener ); - mDecimationFilter.setListener( mSampleAssembler ); - } - - public void receive( float sample ) - { - mDecimationFilter.receive( sample ); - } + 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 ); + } + public class SampleAssembler implements RealSampleListener { private Listener mListener; diff --git a/src/spectrum/DFTProcessor.java b/src/spectrum/DFTProcessor.java index 4d841033..063df756 100644 --- a/src/spectrum/DFTProcessor.java +++ b/src/spectrum/DFTProcessor.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer + * 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 @@ -30,13 +30,14 @@ import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import controller.NamingThreadFactory; +import sample.Buffer; import sample.Listener; import sample.SampleType; import sample.complex.ComplexBuffer; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeListener; import spectrum.converter.DFTResultsConverter; +import controller.NamingThreadFactory; import dsp.filter.Window; import dsp.filter.Window.WindowType; @@ -44,8 +45,7 @@ import dsp.filter.Window.WindowType; * 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, - FrequencyChangeListener +public class DFTProcessor implements Listener, FrequencyChangeListener { private final static Logger mLog = LoggerFactory.getLogger( DFTProcessor.class ); @@ -54,7 +54,7 @@ public class DFTProcessor implements Listener, new CopyOnWriteArrayList(); private ArrayBlockingQueue mQueue = - new ArrayBlockingQueue( 200 ); + new ArrayBlockingQueue( 200 ); private ScheduledExecutorService mScheduler = Executors .newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) ); @@ -74,7 +74,6 @@ public class DFTProcessor implements Listener, private int mSampleRate; private int mFFTFloatsPerFrame; private float mNewFloatsPerFrame; - private float mNewFloatsPerFrameToConsume; private float mNewFloatResidual; private float[] mPreviousFrame = new float[ 8192 ]; @@ -231,7 +230,7 @@ public class DFTProcessor implements Listener, if( !mQueue.offer( sampleBuffer ) ) { mLog.error( "DFTProcessor - [" + mSampleType.toString() - + "]queue is full, purging queue, " + + "] queue is full, purging queue, " + "samples[" + sampleBuffer + "]" ); mQueue.clear(); @@ -245,7 +244,7 @@ public class DFTProcessor implements Listener, try { - ComplexBuffer buffer = mQueue.take(); + Buffer buffer = mQueue.take(); mCurrentBuffer = buffer.getSamples(); } catch ( InterruptedException e ) @@ -500,14 +499,5 @@ public class DFTProcessor implements Listener, mFFTFloatsPerFrame = ( mSampleType == SampleType.COMPLEX ? mFFTWidth.getWidth() * 2 : mFFTWidth.getWidth() ); - - if( mFFTFloatsPerFrame < mNewFloatsPerFrame ) - { - mNewFloatsPerFrameToConsume = mFFTFloatsPerFrame; - } - else - { - mNewFloatsPerFrameToConsume = mNewFloatsPerFrame; - } } } diff --git a/src/spectrum/SpectralDisplayAdjuster.java b/src/spectrum/SpectralDisplayAdjuster.java index b26ef324..1d288086 100644 --- a/src/spectrum/SpectralDisplayAdjuster.java +++ b/src/spectrum/SpectralDisplayAdjuster.java @@ -1,35 +1,30 @@ package spectrum; +import dsp.filter.smoothing.SmoothingFilter.SmoothingType; + public interface SpectralDisplayAdjuster { -/** + /** * Gets the averaging value indicating the number of FFT results that are * averaged to produce each FFT results output. * * @return averaging value ( 2 - 50 ) */ public int getAveraging(); - - /** - * Sets the averaging value indicating the number of FFT results to average - * when producing an FFT output result. - * @param averaging value ( 2 - 50 ) - */ public void setAveraging( int averaging ); /** - * Gets the dBm scale used when plotting the FFT bin results in the FFT - * spectral display. This value determines the smallest displayed power - * spectrum value that is shown at the bottom of the display. + * Sets the smoothing filter averaging window width. * - * @return averaging value ( 1 - 50 ) + * Valid values are odd in the range 3 - 29 */ - public int getDBScale(); - + public int getSmoothing(); + public void setSmoothing( int smoothing ); + /** - * Sets the dBm scale from 0 (top of display) to the scale value (bottom). - * - * @param baseline value ( -160 to -30 dBm ) + * Sets the type of smoothing filter to use. */ - public void setDBScale( int baseline ); + public SmoothingType getSmoothingType(); + public void setSmoothingType( SmoothingType type ); + } diff --git a/src/spectrum/SpectralDisplayPanel.java b/src/spectrum/SpectralDisplayPanel.java index 85dba08b..de410708 100644 --- a/src/spectrum/SpectralDisplayPanel.java +++ b/src/spectrum/SpectralDisplayPanel.java @@ -66,10 +66,11 @@ import spectrum.OverlayPanel.ChannelDisplay; import spectrum.converter.ComplexDecibelConverter; import spectrum.converter.DFTResultsConverter; import spectrum.menu.AveragingItem; -import spectrum.menu.DBScaleItem; import spectrum.menu.FFTWidthItem; import spectrum.menu.FFTWindowTypeItem; import spectrum.menu.FrameRateItem; +import spectrum.menu.SmoothingItem; +import spectrum.menu.SmoothingTypeItem; import com.jidesoft.swing.JideSplitPane; @@ -79,6 +80,7 @@ import controller.channel.ChannelEvent; import controller.channel.ChannelEventListener; import controller.channel.ChannelUtils; import dsp.filter.Window.WindowType; +import dsp.filter.smoothing.SmoothingFilter.SmoothingType; /** * Spectral Display Panel - comprises a DFTProcessor coupled with a waterfall @@ -387,6 +389,9 @@ public class SpectralDisplayPanel extends JPanel //Register the dft processor to receive samples from the tuner mTuner.addListener( (Listener)mDFTProcessor ); + mDFTConverter.setSampleSize( mTuner.getSampleSize() ); + mSpectrumPanel.setSampleSize( mTuner.getSampleSize() ); + //Fire frequency and sample rate change events so that everyone //can init try @@ -597,14 +602,6 @@ public class SpectralDisplayPanel extends JPanel if( event.getComponent() != mWaterfallPanel ) { - /** - * DB Scale menu - */ - JMenu dbScaleMenu = new JMenu( "Scale: 0 to dBm" ); - dbScaleMenu.add( - new DBScaleItem( mSpectrumPanel, -100 ) ); - displayMenu.add( dbScaleMenu ); - /** * Averaging menu */ @@ -666,6 +663,26 @@ public class SpectralDisplayPanel extends JPanel fftWindowType.add( new FFTWindowTypeItem( mDFTProcessor, type ) ); } + + if( event.getComponent() != mWaterfallPanel ) + { + /** + * Smoothing menu + */ + JMenu smoothingMenu = new JMenu( "Smoothing" ); + + if( mSpectrumPanel.getSmoothingType() != SmoothingType.NONE ) + { + smoothingMenu.add( new SmoothingItem( mSpectrumPanel, 5 ) ); + smoothingMenu.add( new JSeparator() ); + } + smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.GAUSSIAN ) ); + smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.TRIANGLE ) ); + smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.RECTANGLE ) ); + smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.NONE ) ); + + displayMenu.add( smoothingMenu ); + } if( contextMenu != null ) { diff --git a/src/spectrum/SpectrumPanel.java b/src/spectrum/SpectrumPanel.java index 1ac5464d..b273d60d 100644 --- a/src/spectrum/SpectrumPanel.java +++ b/src/spectrum/SpectrumPanel.java @@ -36,8 +36,13 @@ import settings.ColorSetting; import settings.ColorSetting.ColorSettingName; import settings.Setting; import settings.SettingChangeListener; -import buffer.FloatArrayCircularAveragingBuffer; import controller.ResourceManager; +import dsp.filter.smoothing.GaussianSmoothingFilter; +import dsp.filter.smoothing.NoSmoothingFilter; +import dsp.filter.smoothing.RectangularSmoothingFilter; +import dsp.filter.smoothing.SmoothingFilter; +import dsp.filter.smoothing.SmoothingFilter.SmoothingType; +import dsp.filter.smoothing.TriangularSmoothingFilter; public class SpectrumPanel extends JPanel implements DFTResultsListener, @@ -51,9 +56,9 @@ public class SpectrumPanel extends JPanel /* Set display bins size to 1, so that we're guaranteed a reset to the * correct width once the first sample set arrives */ private float[] mDisplayFFTBins = new float[ 1 ]; - private FloatArrayCircularAveragingBuffer mFFTAveragingBuffer; private int mAveraging = 4; - private int mDBScale = -100; //dBm + + private float mDBScale; /** * Spectral Display Color Settings @@ -65,6 +70,11 @@ public class SpectrumPanel extends JPanel private static final String SPECTRUM_AVERAGING_SIZE = "spectrum_averaging_size"; private static final int SPECTRUM_TRANSLUCENCY = 128; + private static final RenderingHints RENDERING_HINTS = + new RenderingHints( RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON ); + + private Color mColorSpectrumBackground; private Color mColorSpectrumGradientTop; @@ -73,11 +83,20 @@ public class SpectrumPanel extends JPanel private float mSpectrumInset = 20.0f; + private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter(); + private ResourceManager mResourceManager; public SpectrumPanel( ResourceManager resourceManager ) { mResourceManager = resourceManager; + + RENDERING_HINTS.put( RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY ); + + setSampleSize( 16.0 ); + + mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT ); getSettings(); @@ -89,12 +108,16 @@ public class SpectrumPanel extends JPanel mResourceManager = null; } + public void setSampleSize( double sampleSize ) + { + assert( 2.0 <= sampleSize && sampleSize <= 32.0 ); + + mDBScale = -(float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize ) ) ); + } + public void setAveraging( int size ) { mAveraging = size; - - mFFTAveragingBuffer = - new FloatArrayCircularAveragingBuffer( mAveraging ); } public int getAveraging() @@ -102,19 +125,6 @@ public class SpectrumPanel extends JPanel return mAveraging; } - /** - * Sets the lowest decibel scale value to show on the display - */ - public void setDBScale( int dbScale ) - { - mDBScale = dbScale; - } - - public int getDBScale() - { - return mDBScale; - } - public void clearSpectrum() { mDisplayFFTBins = null; @@ -180,18 +190,36 @@ public class SpectrumPanel extends JPanel */ public void receive( float[] currentFFTBins ) { + if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) ) + { + currentFFTBins = new float[ currentFFTBins.length ]; + } + //Construct and/or resize our fft results variables if( mDisplayFFTBins == null || mDisplayFFTBins.length != currentFFTBins.length ) { - mDisplayFFTBins = new float[ currentFFTBins.length ]; + mDisplayFFTBins = currentFFTBins; } - /** - * Store the new FFT bins in the buffer and get the average of the FFT - * bin buffer contents into the display fft bins variable - */ - mDisplayFFTBins = mFFTAveragingBuffer.get( currentFFTBins ); + //Smooth across the bins + float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins ); + + //Average bins over multiple frames + if( mAveraging > 1 ) + { + float gain = 1.0f / (float)mAveraging; + + for( int x = 0; x < mDisplayFFTBins.length; x++ ) + { + mDisplayFFTBins[ x ] += ( smoothedBins[ x ] - mDisplayFFTBins[ x ] ) * gain; + } + } + else + { + mDisplayFFTBins = smoothedBins; + } + repaint(); } @@ -204,14 +232,8 @@ public class SpectrumPanel extends JPanel Graphics2D graphics = (Graphics2D) g; graphics.setBackground( mColorSpectrumBackground ); - RenderingHints renderHints = - new RenderingHints( RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON ); - renderHints.put( RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_QUALITY ); - - graphics.setRenderingHints( renderHints ); + graphics.setRenderingHints( RENDERING_HINTS ); drawSpectrum( graphics ); } @@ -231,7 +253,7 @@ public class SpectrumPanel extends JPanel //Define the gradient GradientPaint gradient = new GradientPaint( 0, - 0, + getSize().height / 4, mColorSpectrumGradientTop, 0, getSize().height, @@ -253,7 +275,7 @@ public class SpectrumPanel extends JPanel { float insideHeight = size.height - mSpectrumInset; - float scalor = insideHeight / ( -mDBScale ); + float scalor = insideHeight / mDBScale; float insideWidth = size.width; @@ -271,7 +293,7 @@ public class SpectrumPanel extends JPanel } else { - height = (float)( Math.abs( mDisplayFFTBins[ x ] ) * scalor ); + height = mDisplayFFTBins[ x ] * scalor; if( height > insideHeight ) { @@ -316,5 +338,54 @@ public class SpectrumPanel extends JPanel } @Override - public void settingDeleted( Setting setting ) {} + public void settingDeleted( Setting setting ) { /* not implemented */ } + + @Override + public int getSmoothing() + { + return mSmoothingFilter.getPointSize(); + } + + @Override + public void setSmoothing( int smoothing ) + { + mSmoothingFilter.setPointSize( smoothing ); + } + + @Override + public SmoothingType getSmoothingType() + { + return mSmoothingFilter.getSmoothingType(); + } + + @Override + public void setSmoothingType( SmoothingType type ) + { + if( mSmoothingFilter.getSmoothingType() != type ) + { + int pointSize = getSmoothing(); + + synchronized ( mSmoothingFilter ) + { + switch( type ) + { + case GAUSSIAN: + mSmoothingFilter = new GaussianSmoothingFilter(); + break; + case RECTANGLE: + mSmoothingFilter = new RectangularSmoothingFilter(); + break; + case TRIANGLE: + mSmoothingFilter = new TriangularSmoothingFilter(); + break; + case NONE: + default: + mSmoothingFilter = new NoSmoothingFilter(); + break; + } + } + + setSmoothing( pointSize ); + } + } } diff --git a/src/spectrum/converter/ComplexDecibelConverter.java b/src/spectrum/converter/ComplexDecibelConverter.java index 24b3c855..3c819c6c 100644 --- a/src/spectrum/converter/ComplexDecibelConverter.java +++ b/src/spectrum/converter/ComplexDecibelConverter.java @@ -1,3 +1,20 @@ +/******************************************************************************* + * 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 spectrum.converter; import org.slf4j.Logger; @@ -6,52 +23,60 @@ import org.slf4j.LoggerFactory; public class ComplexDecibelConverter extends DFTResultsConverter { private final static Logger mLog = - LoggerFactory.getLogger( DFTResultsConverter.class ); + LoggerFactory.getLogger( ComplexDecibelConverter.class ); + + private float mDynamicRangeReference = (float)Math.pow( 2.0, 16.0 ); /** * Converts the output of the JTransforms FloatFFT_1D.complexForward() - * calculation into a normalized power spectrum in decibels, per description - * in Lyons, Understanding Digital Signal Processing, 3e, page 141. + * calculation into the power spectrum in decibels, normalized to the + * sample bit depth. */ + public ComplexDecibelConverter() + { + } + + /** + * Specifies the bit depth to establish the maximum dynamic range. All + * FFT bin values will be scaled according to this value. + */ + @Override + public void setSampleSize( double size ) + { + assert( 2.0 <= size && size <= 32.0 ); + + mDynamicRangeReference = (float)Math.pow( 2.0d, size ); + } + @Override public void receive( float[] results ) { float[] processed = new float[ results.length / 2 ]; - /* Output from JTransforms 3.0 has the lower and upper halves of the - * spectrum swapped. */ int half = processed.length / 2; - + for( int x = 0; x < results.length; x += 2 ) { - float magnitude = (float)Math.sqrt( results[ x ] * - results[ x ] + results[ x + 1 ] * results[ x + 1 ] ); - - /* Place upper half of magnitudes in lower half of results, vice-versa */ + //Calculate the magnitude squared value from each bin's real and + //imaginary value, scale it to the available dynamic range and + //convert to dB. + float normalizedMagnitude = 10.0f * (float)Math.log10( + ( ( results[ x ] * results[ x ] ) + + ( results[ x + 1 ] * results[ x + 1 ] ) ) / + mDynamicRangeReference ); + + // We have to swap the upper and lower halves of the JTransforms + // DFT results for correct display if( x / 2 >= half ) { - processed[ x / 2 - half ] = magnitude; + processed[ x / 2 - half ] = normalizedMagnitude; } else { - processed[ x / 2 + half ] = magnitude; + processed[ x / 2 + half ] = normalizedMagnitude; } } - - /* Use the DC component as the largest magnitude value to keep the - * display from jumping when a strong bursting signal exists */ - float largestMagnitude = processed[ half ]; - if( largestMagnitude > 0 ) - { - /* Convert magnitudes to normized power spectrum dBm */ - for( int i = 0; i < processed.length; i++ ) - { - processed[ i ] = 20.0f * - (float)Math.log10( processed[ i ] / largestMagnitude ); - } - - dispatch( processed ); - } + dispatch( processed ); } } diff --git a/src/spectrum/converter/DFTResultsConverter.java b/src/spectrum/converter/DFTResultsConverter.java index 82eada92..962bc7cc 100644 --- a/src/spectrum/converter/DFTResultsConverter.java +++ b/src/spectrum/converter/DFTResultsConverter.java @@ -18,6 +18,12 @@ public abstract class DFTResultsConverter public DFTResultsConverter() { } + + /** + * Specifies the sample size in bits to establish the maximum dynamic range. + * All FFT bin values will be scaled according to this value. + */ + public abstract void setSampleSize( double size ); public void dispose() { diff --git a/src/spectrum/converter/RealDecibelConverter.java b/src/spectrum/converter/RealDecibelConverter.java index 69fc9762..ad89c4f5 100644 --- a/src/spectrum/converter/RealDecibelConverter.java +++ b/src/spectrum/converter/RealDecibelConverter.java @@ -1,7 +1,31 @@ package spectrum.converter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class RealDecibelConverter extends DFTResultsConverter { + private static final Logger mLog = LoggerFactory.getLogger( RealDecibelConverter.class ); + + private float mDynamicRangeReference; + + public RealDecibelConverter() + { + setSampleSize( 24.0 ); + } + + /** + * Specifies the bit depth to establish the maximum dynamic range. All + * FFT bin values will be scaled according to this value. + */ + @Override + public void setSampleSize( double size ) + { + assert( 2.0 <= size && size <= 32.0 ); + + mDynamicRangeReference = (float)Math.pow( 2.0d, size ); + } + /** * Converts the output of the JTransforms FloatFFT_1D.realForward() * calculation into a normalized power spectrum in decibels, per description @@ -10,29 +34,22 @@ public class RealDecibelConverter extends DFTResultsConverter @Override public void receive( float[] results ) { - float dc = Math.abs( results[ 0 ] ); + float[] processed = new float[ results.length / 4 ]; - /* Ensure we have power in the DC power bin (bin 0), otherwise don't - * dispatch the results */ - if( dc != 0 ) + int index = 0; + + for( int x = 0; x < processed.length; x ++ ) { - /* Note: we're only processing 1/4 of the output samples */ - float[] processed = new float[ results.length / 4 + 1 ]; + index = x * 2; - for( int x = 0; x < processed.length - 2; x ++ ) - { - int index = ( x + 1 ) * 2; + float normalizedMagnitude = 10.0f * (float)Math.log10( + ( ( results[ index ] * results[ index ] ) + + ( results[ index + 1 ] * results[ index + 1 ] ) ) / + mDynamicRangeReference ); - double magnitude = ( ( results[ index ] * results[ index ] ) + - ( results[ index + 1 ] * results[ index + 1 ] ) ); - - processed[ x ] = 10.0f * - (float)( Math.log10( magnitude / dc ) ); - } - - processed[ processed.length - 1 ] = processed[ 0 ]; - - dispatch( processed ); + processed[ x ] = normalizedMagnitude; } + + dispatch( processed ); } } diff --git a/src/spectrum/menu/AveragingItem.java b/src/spectrum/menu/AveragingItem.java index a3a531e4..c971e465 100644 --- a/src/spectrum/menu/AveragingItem.java +++ b/src/spectrum/menu/AveragingItem.java @@ -18,14 +18,15 @@ public class AveragingItem extends JSlider implements ChangeListener public AveragingItem( SpectralDisplayAdjuster adjuster, int defaultValue ) { - super( JSlider.HORIZONTAL, 2, 50, adjuster.getAveraging() ); + super( JSlider.HORIZONTAL, 1, 20, adjuster.getAveraging() ); mDefaultValue = defaultValue; mAdjuster = adjuster; - setMajorTickSpacing( 10 ); - setMinorTickSpacing( 5 ); + setMajorTickSpacing( 5 ); + setMinorTickSpacing( 1 ); setPaintTicks( true ); + setPaintLabels( true ); addChangeListener( this ); diff --git a/src/spectrum/menu/DBScaleItem.java b/src/spectrum/menu/SmoothingItem.java similarity index 51% rename from src/spectrum/menu/DBScaleItem.java rename to src/spectrum/menu/SmoothingItem.java index 15b84e9f..cef171ab 100644 --- a/src/spectrum/menu/DBScaleItem.java +++ b/src/spectrum/menu/SmoothingItem.java @@ -2,34 +2,50 @@ package spectrum.menu; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.util.Hashtable; +import javax.swing.JLabel; import javax.swing.JSlider; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import spectrum.SpectralDisplayAdjuster; +import dsp.filter.smoothing.SmoothingFilter; -public class DBScaleItem extends JSlider implements ChangeListener +public class SmoothingItem extends JSlider implements ChangeListener { private static final long serialVersionUID = 1L; private SpectralDisplayAdjuster mAdjuster; private int mDefaultValue; - public DBScaleItem( SpectralDisplayAdjuster adjuster, int defaultValue ) + public SmoothingItem( SpectralDisplayAdjuster adjuster, int defaultValue ) { - super( JSlider.HORIZONTAL, -160, -30, adjuster.getDBScale() ); - mDefaultValue = defaultValue; + super( JSlider.HORIZONTAL, + SmoothingFilter.SMOOTHING_MINIMUM, + SmoothingFilter.SMOOTHING_MAXIMUM, + adjuster.getSmoothing() ); + mDefaultValue = defaultValue; mAdjuster = adjuster; - setMajorTickSpacing( 40 ); - setMinorTickSpacing( 10 ); + setSnapToTicks( true ); + setMajorTickSpacing( 6 ); + setMinorTickSpacing( 2 ); setPaintTicks( true ); setPaintLabels( true ); - addChangeListener( this ); + Hashtable labels = new Hashtable<>(); + labels.put( new Integer( 3 ), new JLabel( "3" ) ); + labels.put( new Integer( 9 ), new JLabel( "9" ) ); + labels.put( new Integer( 15 ), new JLabel( "15" ) ); + labels.put( new Integer( 21 ), new JLabel( "21" ) ); + labels.put( new Integer( 27 ), new JLabel( "27" ) ); + setLabelTable( labels ); + + addChangeListener( this ); + addMouseListener( new MouseListener() { @Override @@ -37,7 +53,7 @@ public class DBScaleItem extends JSlider implements ChangeListener { if( event.getClickCount() == 2 ) { - DBScaleItem.this.setValue( mDefaultValue ); + SmoothingItem.this.setValue( mDefaultValue ); } } @@ -52,7 +68,10 @@ public class DBScaleItem extends JSlider implements ChangeListener public void stateChanged( ChangeEvent event ) { int value = ((JSlider)event.getSource()).getValue(); - - mAdjuster.setDBScale( value ); + + if( value % 2 == 1 ) + { + mAdjuster.setSmoothing( value ); + } } } diff --git a/src/spectrum/menu/SmoothingTypeItem.java b/src/spectrum/menu/SmoothingTypeItem.java new file mode 100644 index 00000000..312b2123 --- /dev/null +++ b/src/spectrum/menu/SmoothingTypeItem.java @@ -0,0 +1,36 @@ +package spectrum.menu; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBoxMenuItem; + +import spectrum.SpectralDisplayAdjuster; +import dsp.filter.smoothing.SmoothingFilter.SmoothingType; + +public class SmoothingTypeItem extends JCheckBoxMenuItem +{ + private static final long serialVersionUID = 1L; + + private SpectralDisplayAdjuster mAdjuster; + private SmoothingType mSmoothingType; + + public SmoothingTypeItem( SpectralDisplayAdjuster adjuster, SmoothingType type ) + { + super( type.name() ); + + mAdjuster = adjuster; + mSmoothingType = type; + + setSelected( mAdjuster.getSmoothingType() == type ); + + addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + mAdjuster.setSmoothingType( mSmoothingType ); + } + } ); + } +}