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