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