29 Commits

Author SHA1 Message Date
87da47f53d added new files 2023-03-27 01:50:48 +02:00
75050347de noise reduction work 2023-03-27 01:50:34 +02:00
a9f882e5b1 Fixed halfed volume with HPF on 2023-03-25 21:22:40 +01:00
3420808f3a added high-pass filter in NFM mode 2023-03-25 19:09:27 +01:00
d3d245992d Merge pull request #1021 from sergeyvfx/fix_start_crash
Fix crash in LimeSDR start when the device is disconnected
2023-03-23 23:20:49 +01:00
9a3414b847 Made the code more useful to the user
maybe just yeeting out of the start function with no indication to the user is a bad idea?
2023-03-23 23:19:05 +01:00
90c26f8c1b Fix crash in LimeSDR start when the device is disconnected
This fixes crash when a device which was enumerated on the
application startup gets disconnected prior to the attempt
to start the radio.

The check is done after the existing work around for the
LimeSuite bug. The idea is to give it the best possible
chance for the radio to start.

Possibly, the check needs needs to happen in the second
LMS_Open() but ideally this needs to be verified against
the original workaround scenario.
2023-03-23 22:36:44 +01:00
ec4dc6cc9e fix 2023-03-22 20:47:05 +01:00
93b28d1495 M17 decoder fix 2023-03-22 18:38:15 +01:00
84291deaf6 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2023-03-22 18:20:38 +01:00
21e0696917 readme update 2023-03-22 18:20:34 +01:00
19247ef4f2 Merge pull request #1013 from sergeyvfx/fix_empty_limesdr_crash
Fix crash starting LimeSDR source without device
2023-03-14 23:00:13 +01:00
1e5601e773 Better check
Need to check if a device is actually selected, not just if there is one to select
2023-03-14 22:17:47 +01:00
ae1fd87f02 Fix crash starting LimeSDR source without device
This change fixes crash when a LimeSDR source is started without
an actual device selected.
2023-03-14 21:37:22 +01:00
ab2aee316c Merge pull request #1012 from Bastiti/patch-1
Belgian bandplan
2023-03-14 11:33:44 +01:00
109374277e Belgian bandplan
Added the amateur radio band as specified in the 2019
https://www.ibpt.be/file/cc73d96153bbd5448a56f19d925d05b1379c7f21/821514ac30e14d85dfdbe6747d1b82af9fd4da4c/2019-05-24_ram-decision.pdf

Broadcast band added from BIPT/IBPT Bandplanning.

According to Belgian laws, you're not allowed to receive anything execpt Ham Radio, CB, PMR446 or Broadcasting.
2023-03-14 09:08:43 +01:00
692436f6e4 Merge pull request #1010 from hb9fxq/patch-3
Update readme.md
2023-03-13 18:13:17 +01:00
eccb715d0c Update readme.md 2023-03-13 17:55:44 +01:00
f6f074e0c7 added missing patrons 2023-03-13 17:29:37 +01:00
50a77a7e60 Added a PR template 2023-03-11 13:52:37 +01:00
a4f3c92a03 added missing include to legacy network lib 2023-03-10 17:59:13 +01:00
37920b6476 adjustments 2023-03-10 00:45:56 +01:00
314b8bf72d Const correctness fix for optionlist 2023-03-09 15:14:33 +01:00
5f0858bab2 Removed logging from the DSP lib 2023-03-09 08:56:48 +01:00
007761a027 made it a bit cleaner 2023-03-08 19:49:23 +01:00
801f1be6b2 Hopefully a fix for the rfspace SDR-IP 2023-03-08 19:46:14 +01:00
9cc793e328 bugfix 2023-03-08 19:39:15 +01:00
4283cacae6 fixed build for volk 1.x 2023-03-02 15:19:16 +01:00
6f9dacdd53 Added back 'ignore silence' option in the recorder 2023-03-02 14:47:02 +01:00
43 changed files with 1181 additions and 738 deletions

7
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,7 @@
# Important
Only minor bug fixes and bandplans are accepted.
Pull requests adding features or any bug fix that requires significant code changes will be automatically rejected.
Open an issue requesting a feature or discussing a possible bugfix instead.

View File

@ -13,6 +13,7 @@ endif (USE_BUNDLE_DEFAULTS)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
add_definitions(-DSDRPP_IS_CORE) add_definitions(-DSDRPP_IS_CORE)
add_definitions(-DFLOG_ANDROID_TAG="SDR++")
if (MSVC) if (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif () endif ()

View File

@ -55,24 +55,33 @@ namespace sdrpp_credits {
"Dale L Puckett (K0HYD)", "Dale L Puckett (K0HYD)",
"Daniele D'Agnelli", "Daniele D'Agnelli",
"D. Jones", "D. Jones",
"Dexruus",
"EB3FRN", "EB3FRN",
"Eric Johnson", "Eric Johnson",
"Ernest Murphy (NH7L)", "Ernest Murphy (NH7L)",
"Flinger Films", "Flinger Films",
"Frank Werner (HB9FXQ)",
"gringogrigio", "gringogrigio",
"Jeff Moe",
"Joe Cupano", "Joe Cupano",
"KD1SQ",
"Kezza", "Kezza",
"Krys Kamieniecki", "Krys Kamieniecki",
"Lee Donaghy", "Lee Donaghy",
"Lee KD1SQ", "Lee KD1SQ",
".lozenge. (Hank Hill)", ".lozenge. (Hank Hill)",
"Martin Herren (HB9FXX)",
"ON4MU", "ON4MU",
"Passion-Radio.com", "Passion-Radio.com",
"Paul Maine", "Paul Maine",
"Peter Betz",
"Scanner School", "Scanner School",
"Scott Palmer",
"SignalsEverywhere", "SignalsEverywhere",
"Syne Ardwin (WI9SYN)", "Syne Ardwin (WI9SYN)",
"W4IPA", "W4IPA",
"William Arcand (W1WRA)",
"Yves Rougy",
"Zipper" "Zipper"
}; };

View File

@ -67,10 +67,6 @@ namespace dsp::buffer {
sizes[writeCur] = count; sizes[writeCur] = count;
writeCur++; writeCur++;
writeCur = ((writeCur) % TEST_BUFFER_SIZE); writeCur = ((writeCur) % TEST_BUFFER_SIZE);
// if (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) >= (TEST_BUFFER_SIZE-2)) {
// flog::warn("Overflow");
// }
} }
cnd.notify_all(); cnd.notify_all();
_in->flush(); _in->flush();

View File

@ -9,7 +9,7 @@ namespace dsp::convert {
StereoToMono(stream<stereo_t>* in) { base_type::init(in); } StereoToMono(stream<stereo_t>* in) { base_type::init(in); }
inline int process(int count, const stereo_t* in, float* out) { static inline int process(int count, const stereo_t* in, float* out) {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
out[i] = (in[i].l + in[i].r) / 2.0f; out[i] = (in[i].l + in[i].r) / 2.0f;
} }

View File

@ -3,6 +3,8 @@
#include "quadrature.h" #include "quadrature.h"
#include "../filter/fir.h" #include "../filter/fir.h"
#include "../taps/low_pass.h" #include "../taps/low_pass.h"
#include "../taps/high_pass.h"
#include "../taps/band_pass.h"
#include "../convert/mono_to_stereo.h" #include "../convert/mono_to_stereo.h"
namespace dsp::demod { namespace dsp::demod {
@ -17,22 +19,26 @@ namespace dsp::demod {
~FM() { ~FM() {
if (!base_type::_block_init) { return; } if (!base_type::_block_init) { return; }
base_type::stop(); base_type::stop();
dsp::taps::free(lpfTaps); dsp::taps::free(filterTaps);
} }
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) { void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass, bool highPass) {
_samplerate = samplerate; _samplerate = samplerate;
_bandwidth = bandwidth; _bandwidth = bandwidth;
_lowPass = lowPass; _lowPass = lowPass;
_highPass = highPass;
demod.init(NULL, bandwidth / 2.0, _samplerate); demod.init(NULL, bandwidth / 2.0, _samplerate);
lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate); loadDummyTaps();
lpf.init(NULL, lpfTaps); fir.init(NULL, filterTaps);
// Initialize taps
updateFilter(lowPass, highPass);
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
demod.out.free(); demod.out.free();
} }
lpf.out.free(); fir.out.free();
base_type::init(in); base_type::init(in);
} }
@ -43,9 +49,7 @@ namespace dsp::demod {
base_type::tempStop(); base_type::tempStop();
_samplerate = samplerate; _samplerate = samplerate;
demod.setDeviation(_bandwidth / 2.0, _samplerate); demod.setDeviation(_bandwidth / 2.0, _samplerate);
dsp::taps::free(lpfTaps); updateFilter(_lowPass, _highPass);
lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
lpf.setTaps(lpfTaps);
base_type::tempStart(); base_type::tempStart();
} }
@ -54,19 +58,20 @@ namespace dsp::demod {
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
if (bandwidth == _bandwidth) { return; } if (bandwidth == _bandwidth) { return; }
_bandwidth = bandwidth; _bandwidth = bandwidth;
std::lock_guard<std::mutex> lck2(lpfMtx);
demod.setDeviation(_bandwidth / 2.0, _samplerate); demod.setDeviation(_bandwidth / 2.0, _samplerate);
dsp::taps::free(lpfTaps); updateFilter(_lowPass, _highPass);
lpfTaps = dsp::taps::lowPass(_bandwidth / 2, (_bandwidth / 2) * 0.1, _samplerate);
lpf.setTaps(lpfTaps);
} }
void setLowPass(bool lowPass) { void setLowPass(bool lowPass) {
assert(base_type::_block_init); assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
std::lock_guard<std::mutex> lck2(lpfMtx); updateFilter(lowPass, _highPass);
_lowPass = lowPass; }
lpf.reset();
void setHighPass(bool highPass) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
updateFilter(_lowPass, highPass);
} }
void reset() { void reset() {
@ -74,23 +79,23 @@ namespace dsp::demod {
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop(); base_type::tempStop();
demod.reset(); demod.reset();
lpf.reset(); fir.reset();
base_type::tempStart(); base_type::tempStart();
} }
inline int process(int count, dsp::complex_t* in, T* out) { inline int process(int count, dsp::complex_t* in, T* out) {
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
demod.process(count, in, out); demod.process(count, in, out);
if (_lowPass) { if (filtering) {
std::lock_guard<std::mutex> lck(lpfMtx); std::lock_guard<std::mutex> lck(filterMtx);
lpf.process(count, out, out); fir.process(count, out, out);
} }
} }
if constexpr (std::is_same_v<T, stereo_t>) { if constexpr (std::is_same_v<T, stereo_t>) {
demod.process(count, in, demod.out.writeBuf); demod.process(count, in, demod.out.writeBuf);
if (_lowPass) { if (filtering) {
std::lock_guard<std::mutex> lck(lpfMtx); std::lock_guard<std::mutex> lck(filterMtx);
lpf.process(count, demod.out.writeBuf, demod.out.writeBuf); fir.process(count, demod.out.writeBuf, demod.out.writeBuf);
} }
convert::MonoToStereo::process(count, demod.out.writeBuf, out); convert::MonoToStereo::process(count, demod.out.writeBuf, out);
} }
@ -109,13 +114,50 @@ namespace dsp::demod {
} }
private: private:
void updateFilter(bool lowPass, bool highPass) {
std::lock_guard<std::mutex> lck(filterMtx);
// Update values
_lowPass = lowPass;
_highPass = highPass;
filtering = (lowPass || highPass);
// Free filter taps
dsp::taps::free(filterTaps);
// Generate filter depending on low and high pass settings
if (_lowPass && _highPass) {
filterTaps = dsp::taps::bandPass<float>(300.0, _bandwidth / 2.0, 100.0, _samplerate);
}
else if (_highPass) {
filterTaps = dsp::taps::highPass(300.0, 100.0, _samplerate);
}
else if (_lowPass) {
filterTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
}
else {
loadDummyTaps();
}
// Set filter to use new taps
fir.setTaps(filterTaps);
fir.reset();
}
void loadDummyTaps() {
float dummyTap = 1.0f;
filterTaps = dsp::taps::fromArray<float>(1, &dummyTap);
}
double _samplerate; double _samplerate;
double _bandwidth; double _bandwidth;
bool _lowPass; bool _lowPass;
bool _highPass;
bool filtering;
Quadrature demod; Quadrature demod;
tap<float> lpfTaps; tap<float> filterTaps;
filter::FIR<float, float> lpf; filter::FIR<float, float> fir;
std::mutex lpfMtx; std::mutex filterMtx;
}; };
} }

View File

@ -2,5 +2,6 @@
#include "../multirate/rrc_interpolator.h" #include "../multirate/rrc_interpolator.h"
namespace dsp::mod { namespace dsp::mod {
// TODO: Check if resample before RRC is better than using the RRC taps as a filter (bandwidth probably not correct for alias-free resampling)
typedef multirate::RRCInterpolator<complex_t> PSK; typedef multirate::RRCInterpolator<complex_t> PSK;
} }

View File

@ -5,7 +5,7 @@
#include "../math/hz_to_rads.h" #include "../math/hz_to_rads.h"
namespace dsp::mod { namespace dsp::mod {
class Quadrature : Processor<float, complex_t> { class Quadrature : public Processor<float, complex_t> {
using base_type = Processor<float, complex_t>; using base_type = Processor<float, complex_t>;
public: public:
Quadrature() {} Quadrature() {}

View File

@ -83,8 +83,6 @@ namespace dsp::multirate {
int interp = OutSR / gcd; int interp = OutSR / gcd;
int decim = InSR / gcd; int decim = InSR / gcd;
flog::warn("interp: {0}, decim: {1}", interp, decim);
// Configure resampler // Configure resampler
double tapSamplerate = _symbolrate * (double)interp; double tapSamplerate = _symbolrate * (double)interp;
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount * interp, _rrcBeta, _symbolrate, tapSamplerate); rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount * interp, _rrcBeta, _symbolrate, tapSamplerate);

View File

@ -0,0 +1,183 @@
#pragma once
#include "../processor.h"
#include "../window/nuttall.h"
#include <fftw3.h>
#include "../convert/stereo_to_mono.h"
namespace dsp::noise_reduction {
class Audio : public Processor<stereo_t, stereo_t> {
using base_type = Processor<stereo_t, stereo_t>;
public:
Audio() {}
Audio(stream<stereo_t>* in, int bins) { init(in, bins); }
~Audio() {
if (!base_type::_block_init) { return; }
base_type::stop();
destroyBuffers();
}
void init(stream<stereo_t>* in, int bins) {
_bins = bins;
complexBins = (bins / 2) + 1;
normFactor = 1.0f / (float)_bins;
initBuffers();
base_type::init(in);
}
void setBins(int bins) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_bins = bins;
complexBins = (bins / 2) + 1;
normFactor = 1.0f / (float)_bins;
destroyBuffers();
initBuffers();
base_type::tempStart();
}
void setLevel(float level) {
_level = powf(10.0f, level * 0.1f);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
buffer::clear(buffer, _bins - 1);
buffer::clear(backFFTIn, _bins);
buffer::clear(noisePrint, _bins);
base_type::tempStart();
}
int process(int count, const stereo_t* in, stereo_t* out) {
// Write new input data to buffer
convert::StereoToMono::process(count, in, bufferStart);
// Iterate the FFT
for (int i = 0; i < count; i++) {
// Apply windows
volk_32f_x2_multiply_32f(forwFFTIn, &buffer[i], fftWin, _bins);
// Do forward FFT
fftwf_execute(forwardPlan);
// Get bin amplitude and square to get power
volk_32fc_magnitude_32f(ampBuf, (lv_32fc_t*)forwFFTOut, complexBins);
// Update noise print using a running average
volk_32f_s32f_multiply_32f(scaledAmps, ampBuf, alpha, complexBins);
volk_32f_s32f_multiply_32f(noisePrint, noisePrint, beta, complexBins);
volk_32f_x2_add_32f(noisePrint, noisePrint, scaledAmps, complexBins);
// Clamp amplitudes
volk_32f_x2_max_32f(ampBuf, ampBuf, noisePrint, complexBins);
// Compute Wiener (funny) filter
volk_32f_x2_subtract_32f(scaledAmps, ampBuf, noisePrint, complexBins);
volk_32f_x2_divide_32f(scaledAmps, scaledAmps, ampBuf, complexBins);
// Apply wiener filter to bins
volk_32fc_32f_multiply_32fc((lv_32fc_t*)backFFTIn, (lv_32fc_t*)forwFFTOut, scaledAmps, complexBins);
// Do reverse FFT and get first element
fftwf_execute(backwardPlan);
out[i].l = backFFTOut[_bins / 2];
out[i].r = backFFTOut[_bins / 2];
}
// Correct amplitude
volk_32f_s32f_multiply_32f((float*)out, (float*)out, normFactor, count*2);
// Move buffer buffer
memmove(buffer, &buffer[count], (_bins - 1) * sizeof(float));
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
void initBuffers() {
// Allocate FFT buffers
forwFFTIn = (float*)fftwf_malloc(_bins * sizeof(float));
forwFFTOut = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t));
backFFTIn = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t));
backFFTOut = (float*)fftwf_malloc(_bins * sizeof(float));
// Allocate and clear delay buffer
buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + 64000);
bufferStart = &buffer[_bins - 1];
buffer::clear(buffer, _bins - 1);
// Clear backward FFT input
buffer::clear(backFFTIn, _bins);
// Allocate amplitude buffer
ampBuf = buffer::alloc<float>(_bins);
scaledAmps = buffer::alloc<float>(_bins);
noisePrint = buffer::alloc<float>(_bins);
buffer::clear(noisePrint, _bins);
// Allocate and generate Window
fftWin = buffer::alloc<float>(_bins);
for (int i = 0; i < _bins; i++) { fftWin[i] = window::nuttall(i, _bins - 1); }
// Plan FFTs
forwardPlan = fftwf_plan_dft_r2c_1d(_bins, forwFFTIn, (fftwf_complex*)forwFFTOut, FFTW_ESTIMATE);
backwardPlan = fftwf_plan_dft_c2r_1d(_bins, (fftwf_complex*)backFFTIn, backFFTOut, FFTW_ESTIMATE);
}
void destroyBuffers() {
fftwf_destroy_plan(forwardPlan);
fftwf_destroy_plan(backwardPlan);
fftwf_free(forwFFTIn);
fftwf_free(forwFFTOut);
fftwf_free(backFFTIn);
fftwf_free(backFFTOut);
buffer::free(buffer);
buffer::free(ampBuf);
buffer::free(scaledAmps);
buffer::free(noisePrint);
buffer::free(fftWin);
}
float _level = 0.0f;
float* forwFFTIn;
complex_t* forwFFTOut;
complex_t* backFFTIn;
float* backFFTOut;
fftwf_plan forwardPlan;
fftwf_plan backwardPlan;
float* buffer;
float* bufferStart;
float* fftWin;
float* ampBuf;
float* scaledAmps;
float* noisePrint;
int _bins;
int complexBins;
float normFactor = 1.0f;
float alpha = 0.0001f;
float beta = 0.9999f;
};
}

View File

@ -37,21 +37,17 @@ namespace dsp::noise_reduction {
inline int process(int count, complex_t* in, complex_t* out) { inline int process(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
// Get signal amplitude // Get signal amplitude and pass value if null
float inAmp = in[i].amplitude(); float inAmp = in[i].amplitude();
if (!inAmp) {
// Update average amplitude out[i] = in[i];
float gain = 1.0f;
if (inAmp != 0.0f) {
amp = (amp * _invRate) + (inAmp * _rate);
float excess = inAmp / amp;
if (excess > _level) {
gain = 1.0f / excess;
}
} }
// Scale output by gain // Update running average of amplitude
out[i] = in[i] * gain; amp = (_rate*inAmp) + (_invRate*amp);
// Null out if spike (Note: ideally, it should try to guess the real data)
out[i] = (inAmp > _level*amp) ? complex_t{0.0f,0.0f} : in[i];
} }
return count; return count;
} }

View File

@ -15,7 +15,7 @@ namespace dsp::taps {
if (oddTapCount && !(count % 2)) { count++; } if (oddTapCount && !(count % 2)) { count++; }
return windowedSinc<T>(count, (bandStop - bandStart) / 2.0, sampleRate, [=](double n, double N) { return windowedSinc<T>(count, (bandStop - bandStart) / 2.0, sampleRate, [=](double n, double N) {
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
return cosf(offsetOmega * (float)n) * window::nuttall(n, N); return 2.0f * cosf(offsetOmega * (float)n) * window::nuttall(n, N);
} }
if constexpr (std::is_same_v<T, complex_t>) { if constexpr (std::is_same_v<T, complex_t>) {
// The offset is negative to flip the taps. Complex bandpass are asymetric // The offset is negative to flip the taps. Complex bandpass are asymetric

View File

@ -15,6 +15,10 @@ namespace sourcemenu {
bool iqCorrection = false; bool iqCorrection = false;
bool invertIQ = false; bool invertIQ = false;
EventHandler<std::string> sourceRegisteredHandler;
EventHandler<std::string> sourceUnregisterHandler;
EventHandler<std::string> sourceUnregisteredHandler;
std::vector<std::string> sourceNames; std::vector<std::string> sourceNames;
std::string sourceNamesTxt; std::string sourceNamesTxt;
std::string selectedSource; std::string selectedSource;
@ -95,10 +99,10 @@ namespace sourcemenu {
} }
sourceId = std::distance(sourceNames.begin(), it); sourceId = std::distance(sourceNames.begin(), it);
selectedSource = sourceNames[sourceId]; selectedSource = sourceNames[sourceId];
sigpath::sourceManager.select(sourceNames[sourceId]); sigpath::sourceManager.selectSource(sourceNames[sourceId]);
} }
void onSourceRegistered(std::string name) { void onSourceRegistered(std::string name, void* ctx) {
refreshSources(); refreshSources();
if (selectedSource.empty()) { if (selectedSource.empty()) {
@ -110,13 +114,13 @@ namespace sourcemenu {
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
} }
void onSourceUnregister(std::string name) { void onSourceUnregister(std::string name, void* ctx) {
if (name != selectedSource) { return; } if (name != selectedSource) { return; }
// TODO: Stop everything // TODO: Stop everything
} }
void onSourceUnregistered(std::string name) { void onSourceUnregistered(std::string name, void* ctx) {
refreshSources(); refreshSources();
if (sourceNames.empty()) { if (sourceNames.empty()) {
@ -149,9 +153,12 @@ namespace sourcemenu {
selectSource(selected); selectSource(selected);
sigpath::iqFrontEnd.setDecimation(1 << decimationPower); sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
sigpath::sourceManager.onSourceRegistered.bind(onSourceRegistered); sourceRegisteredHandler.handler = onSourceRegistered;
sigpath::sourceManager.onSourceUnregister.bind(onSourceUnregister); sourceUnregisterHandler.handler = onSourceUnregister;
sigpath::sourceManager.onSourceUnregistered.bind(onSourceUnregistered); sourceUnregisteredHandler.handler = onSourceUnregistered;
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler);
sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler);
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler);
core::configManager.release(); core::configManager.release();
} }
@ -172,7 +179,7 @@ namespace sourcemenu {
if (running) { style::endDisabled(); } if (running) { style::endDisabled(); }
sigpath::sourceManager.showMenu(); sigpath::sourceManager.showSelectedMenu();
if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) { if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) {
sigpath::iqFrontEnd.setDCBlocking(iqCorrection); sigpath::iqFrontEnd.setDCBlocking(iqCorrection);

View File

@ -146,7 +146,7 @@ namespace server {
// Load sourceId from config // Load sourceId from config
sourceId = 0; sourceId = 0;
if (sourceList.keyExists(sourceName)) { sourceId = sourceList.keyId(sourceName); } if (sourceList.keyExists(sourceName)) { sourceId = sourceList.keyId(sourceName); }
sigpath::sourceManager.select(sourceList[sourceId]); sigpath::sourceManager.selectSource(sourceList[sourceId]);
// TODO: Use command line option // TODO: Use command line option
std::string host = (std::string)core::args["addr"]; std::string host = (std::string)core::args["addr"];
@ -280,7 +280,8 @@ namespace server {
} }
} }
else if (cmd == COMMAND_START) { else if (cmd == COMMAND_START) {
running = sigpath::sourceManager.start(); sigpath::sourceManager.start();
running = true;
} }
else if (cmd == COMMAND_STOP) { else if (cmd == COMMAND_STOP) {
sigpath::sourceManager.stop(); sigpath::sourceManager.stop();
@ -308,14 +309,14 @@ namespace server {
SmGui::FillWidth(); SmGui::FillWidth();
SmGui::ForceSync(); SmGui::ForceSync();
if (SmGui::Combo("##sdrpp_server_src_sel", &sourceId, sourceList.txt)) { if (SmGui::Combo("##sdrpp_server_src_sel", &sourceId, sourceList.txt)) {
sigpath::sourceManager.select(sourceList[sourceId]); sigpath::sourceManager.selectSource(sourceList[sourceId]);
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["source"] = sourceList.key(sourceId); core::configManager.conf["source"] = sourceList.key(sourceId);
core::configManager.release(true); core::configManager.release(true);
} }
if (running) { SmGui::EndDisabled(); } if (running) { SmGui::EndDisabled(); }
sigpath::sourceManager.showMenu(); sigpath::sourceManager.showSelectedMenu();
} }
void renderUI(SmGui::DrawList* dl, std::string diffId, SmGui::DrawListElem diffValue) { void renderUI(SmGui::DrawList* dl, std::string diffId, SmGui::DrawListElem diffValue) {

View File

@ -1,186 +1,106 @@
#include "source.h" #include <server.h>
#include <signal_path/source.h>
#include <utils/flog.h> #include <utils/flog.h>
#include <signal_path/signal_path.h>
#include <core.h>
void SourceManager::registerSource(const std::string& name, Source* source) { SourceManager::SourceManager() {
std::lock_guard<std::recursive_mutex> lck(mtx); }
// Check arguments void SourceManager::registerSource(std::string name, SourceHandler* handler) {
if (source || name.empty()) {
flog::error("Invalid argument to register source", name);
return;
}
// Check that a source with that name doesn't already exist
if (sources.find(name) != sources.end()) { if (sources.find(name) != sources.end()) {
flog::error("Tried to register source with existing name: {}", name); flog::error("Tried to register new source with existing name: {0}", name);
return; return;
} }
sources[name] = handler;
// Add source to map onSourceRegistered.emit(name);
sources[name] = source;
// Add source to lists
sourceNames.push_back(name);
onSourceRegistered(name);
} }
void SourceManager::unregisterSource(const std::string& name) { void SourceManager::unregisterSource(std::string name) {
std::lock_guard<std::recursive_mutex> lck(mtx);
// Check that a source with that name exists
if (sources.find(name) == sources.end()) { if (sources.find(name) == sources.end()) {
flog::error("Tried to unregister a non-existent source: {}", name); flog::error("Tried to unregister non existent source: {0}", name);
return; return;
} }
onSourceUnregister.emit(name);
// Notify event listeners of the imminent deletion if (name == selectedName) {
onSourceUnregister(name); if (selectedHandler != NULL) {
sources[selectedName]->deselectHandler(sources[selectedName]->ctx);
// Delete from lists }
sourceNames.erase(std::find(sourceNames.begin(), sourceNames.end(), name)); sigpath::iqFrontEnd.setInput(&nullSource);
selectedHandler = NULL;
}
sources.erase(name); sources.erase(name);
onSourceUnregistered.emit(name);
// Notify event listeners of the deletion
onSourceUnregistered(name);
} }
const std::vector<std::string>& SourceManager::getSourceNames() { std::vector<std::string> SourceManager::getSourceNames() {
std::lock_guard<std::recursive_mutex> lck(mtx); std::vector<std::string> names;
return sourceNames; for (auto const& [name, src] : sources) { names.push_back(name); }
return names;
} }
void SourceManager::select(const std::string& name) { void SourceManager::selectSource(std::string name) {
std::lock_guard<std::recursive_mutex> lck(mtx);
// make sure that source isn't currently selected
if (selectedSourceName == name) { return; }
// Deselect current source
deselect();
// Check that a source with that name exists
if (sources.find(name) == sources.end()) { if (sources.find(name) == sources.end()) {
flog::error("Tried to select a non-existent source: {}", name); flog::error("Tried to select non existent source: {0}", name);
return; return;
} }
if (selectedHandler != NULL) {
// Select the source sources[selectedName]->deselectHandler(sources[selectedName]->ctx);
selectedSourceName = name; }
selectedSource = sources[name]; selectedHandler = sources[name];
selectedHandler->selectHandler(selectedHandler->ctx);
// Call the selected source selectedName = name;
selectedSource->select(); if (core::args["server"].b()) {
server::setInput(selectedHandler->stream);
// Retune to make sure the source has the latest frequency }
tune(frequency); else {
sigpath::iqFrontEnd.setInput(selectedHandler->stream);
}
// Set server input here
} }
const std::string& SourceManager::getSelected() { void SourceManager::showSelectedMenu() {
std::lock_guard<std::recursive_mutex> lck(mtx); if (selectedHandler == NULL) {
return selectedSourceName; return;
}
selectedHandler->menuHandler(selectedHandler->ctx);
} }
bool SourceManager::start() { void SourceManager::start() {
std::lock_guard<std::recursive_mutex> lck(mtx); if (selectedHandler == NULL) {
return;
// Check if not already running }
if (running) { return true; } selectedHandler->startHandler(selectedHandler->ctx);
// Call source if selected and save if started
running = (!selectedSource) ? false : selectedSource->start();
return running;
} }
void SourceManager::stop() { void SourceManager::stop() {
std::lock_guard<std::recursive_mutex> lck(mtx); if (selectedHandler == NULL) {
return;
// Check if running }
if (!running) { return; } selectedHandler->stopHandler(selectedHandler->ctx);
// Call source if selected and save state
if (selectedSource) { selectedSource->stop(); }
running = false;
}
bool SourceManager::isRunning() {
std::lock_guard<std::recursive_mutex> lck(mtx);
return running;
} }
void SourceManager::tune(double freq) { void SourceManager::tune(double freq) {
std::lock_guard<std::recursive_mutex> lck(mtx); if (selectedHandler == NULL) {
return;
// Save frequency
frequency = freq;
// Call source if selected
if (selectedSource) {
selectedSource->tune(((mode == TUNING_MODE_NORMAL) ? freq : ifFrequency) + offset);
} }
// TODO: No need to always retune the hardware in panadpter mode
selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx);
onRetune.emit(freq);
currentFreq = freq;
} }
void SourceManager::showMenu() {
std::lock_guard<std::recursive_mutex> lck(mtx);
// Call source if selected
if (selectedSource) { selectedSource->showMenu(); }
}
double SourceManager::getSamplerate() {
std::lock_guard<std::recursive_mutex> lck(mtx);
return samplerate;
}
// =========== TODO: These functions should not happen in this class ===========
void SourceManager::setTuningOffset(double offset) { void SourceManager::setTuningOffset(double offset) {
std::lock_guard<std::recursive_mutex> lck(mtx); tuneOffset = offset;
tune(currentFreq);
// Update offset
this->offset = offset;
// Retune to take affect
tune(frequency);
} }
void SourceManager::setTuningMode(TuningMode mode) { void SourceManager::setTuningMode(TuningMode mode) {
std::lock_guard<std::recursive_mutex> lck(mtx); tuneMode = mode;
tune(currentFreq);
// Update mode
this->mode = mode;
// Retune to take affect
tune(frequency);
} }
void SourceManager::setPanadpterIF(double freq) { void SourceManager::setPanadpterIF(double freq) {
std::lock_guard<std::recursive_mutex> lck(mtx); ifFreq = freq;
tune(currentFreq);
// Update offset
ifFrequency = freq;
// Return to take affect if in panadapter mode
if (mode == TUNING_MODE_PANADAPTER) { tune(frequency); }
}
// =============================================================================
void SourceManager::deselect() {
std::lock_guard<std::recursive_mutex> lck(mtx);
// Call source if selected
if (selectedSource) { selectedSource->deselect(); }
// Mark as deselected
selectedSourceName.clear();
selectedSource = NULL;
}
void SourceManager::setSamplerate(double samplerate) {
std::lock_guard<std::recursive_mutex> lck(mtx);
// Save samplerate and emit event
this->samplerate = samplerate;
onSamplerateChanged(samplerate);
} }

View File

@ -1,153 +1,56 @@
#pragma once #pragma once
#include <string> #include <string>
#include <functional> #include <vector>
#include <map> #include <map>
#include <mutex>
#include <dsp/types.h>
#include <dsp/stream.h> #include <dsp/stream.h>
#include <dsp/types.h>
#include <utils/event.h> #include <utils/event.h>
enum TuningMode {
TUNING_MODE_NORMAL,
TUNING_MODE_PANADAPTER
};
class Source;
class SourceManager { class SourceManager {
friend Source;
public: public:
/** SourceManager();
* Register a source.
* @param name Name of the source.
* @param source Pointer to the source instance.
*/
void registerSource(const std::string& name, Source* source);
/** struct SourceHandler {
* Unregister a source. dsp::stream<dsp::complex_t>* stream;
* @param name Name of the source. void (*menuHandler)(void* ctx);
*/ void (*selectHandler)(void* ctx);
void unregisterSource(const std::string& name); void (*deselectHandler)(void* ctx);
void (*startHandler)(void* ctx);
void (*stopHandler)(void* ctx);
void (*tuneHandler)(double freq, void* ctx);
void* ctx;
};
/** enum TuningMode {
* Get a list of source names. NORMAL,
* @return List of source names. PANADAPTER
*/ };
const std::vector<std::string>& getSourceNames();
/** void registerSource(std::string name, SourceHandler* handler);
* Select a source. void unregisterSource(std::string name);
* @param name Name of the source. void selectSource(std::string name);
*/ void showSelectedMenu();
void select(const std::string& name); void start();
/**
* Get the name of the currently selected source.
* @return Name of the source or empty if no source is selected.
*/
const std::string& getSelected();
/**
* Start the radio.
* @return True if the radio started successfully, false if not.
*/
bool start();
/**
* Stop the radio.
*/
void stop(); void stop();
/**
* Check if the radio is running.
* @return True if the radio is running, false if not.
*/
bool isRunning();
/**
* Tune the radio.
* @param freq Frequency in Hz.
*/
void tune(double freq); void tune(double freq);
/**
* Tune the radio.
* @param freq Frequency to tune the radio to.
*/
void showMenu();
/**
* Get the current samplerate of the radio.
* @return Samplerate in Hz.
*/
double getSamplerate();
// =========== TODO: These functions should not happen in this class ===========
/**
* Set offset to add to the tuned frequency.
* @param offset Offset in Hz.
*/
void setTuningOffset(double offset); void setTuningOffset(double offset);
/**
* Set tuning mode.
* @param mode Tuning mode.
*/
void setTuningMode(TuningMode mode); void setTuningMode(TuningMode mode);
/**
* Set panadapter mode IF frequency.
* @param freq IF frequency in Hz.
*/
void setPanadpterIF(double freq); void setPanadpterIF(double freq);
// ============================================================================= std::vector<std::string> getSourceNames();
// Emitted after a new source has been registered.
Event<std::string> onSourceRegistered; Event<std::string> onSourceRegistered;
// Emitted when a source is about to be unregistered.
Event<std::string> onSourceUnregister; Event<std::string> onSourceUnregister;
// Emitted after a source has been unregistered.
Event<std::string> onSourceUnregistered; Event<std::string> onSourceUnregistered;
// Emitted when the samplerate of the incoming IQ has changed.
Event<double> onSamplerateChanged;
// Emitted when the source manager is instructed to tune the radio.
Event<double> onRetune; Event<double> onRetune;
private: private:
void deselect(); std::map<std::string, SourceHandler*> sources;
void setSamplerate(double samplerate); std::string selectedName;
SourceHandler* selectedHandler = NULL;
std::vector<std::string> sourceNames; double tuneOffset;
std::map<std::string, Source*> sources; double currentFreq;
double ifFreq = 0.0;
std::string selectedSourceName = ""; TuningMode tuneMode = TuningMode::NORMAL;
Source* selectedSource = NULL; dsp::stream<dsp::complex_t> nullSource;
bool running = false;
double samplerate = 1e6;
double frequency = 100e6;
double offset = 0;
double ifFrequency = 8.830e6;
TuningMode mode = TUNING_MODE_NORMAL;
std::recursive_mutex mtx;
};
class Source {
public:
virtual void showMenu() {}
virtual void select() = 0;
virtual void deselect() {}
virtual bool start() = 0;
virtual void stop() = 0;
virtual void tune(double freq) {}
dsp::stream<dsp::complex_t> stream;
}; };

View File

@ -1,51 +1,43 @@
#pragma once #pragma once
#include <functional> #include <vector>
#include <stdexcept> #include <utils/flog.h>
#include <mutex>
#include <map>
typedef int HandlerID; template <class T>
struct EventHandler {
EventHandler() {}
EventHandler(void (*handler)(T, void*), void* ctx) {
this->handler = handler;
this->ctx = ctx;
}
template <typename... Args> void (*handler)(T, void*);
void* ctx;
};
template <class T>
class Event { class Event {
using Handler = std::function<void(Args...)>;
public: public:
HandlerID bind(Handler handler) { Event() {}
std::lock_guard<std::mutex> lck(mtx); ~Event() {}
HandlerID id = genID();
handlers[id] = handler;
return id;
}
template<typename MHandler, class T> void emit(T value) {
HandlerID bind(MHandler handler, T* ctx) { for (auto const& handler : handlers) {
return bind([=](Args... args){ handler->handler(value, handler->ctx);
(ctx->*handler)(args...);
});
}
void unbind(HandlerID id) {
std::lock_guard<std::mutex> lck(mtx);
if (handlers.find(id) == handlers.end()) {
throw std::runtime_error("Could not unbind handler, unknown ID");
} }
handlers.erase(id);
} }
void operator()(Args... args) { void bindHandler(EventHandler<T>* handler) {
std::lock_guard<std::mutex> lck(mtx); handlers.push_back(handler);
for (const auto& [desc, handler] : handlers) { }
handler(args...);
void unbindHandler(EventHandler<T>* handler) {
if (std::find(handlers.begin(), handlers.end(), handler) == handlers.end()) {
flog::error("Tried to remove a non-existent event handler");
return;
} }
handlers.erase(std::remove(handlers.begin(), handlers.end(), handler), handlers.end());
} }
private: private:
HandlerID genID() { std::vector<EventHandler<T>*> handlers;
int id;
for (id = 1; handlers.find(id) != handlers.end(); id++);
return id;
}
std::map<HandlerID, Handler> handlers;
std::mutex mtx;
}; };

View File

@ -169,7 +169,7 @@ namespace flog {
fprintf(outStream, "] %s\n", out.c_str()); fprintf(outStream, "] %s\n", out.c_str());
#elif defined(__ANDROID__) #elif defined(__ANDROID__)
// Print format string // Print format string
__android_log_buf_print(LOG_ID_DEFAULT, TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n", __android_log_print(TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n",
nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str()); nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str());
#else #else
// Print format string // Print format string

View File

@ -288,6 +288,7 @@ namespace net {
// Save data // Save data
for (auto iface = addresses; iface; iface = iface->ifa_next) { for (auto iface = addresses; iface; iface = iface->ifa_next) {
if (!iface->ifa_addr || !iface->ifa_netmask) { continue; }
if (iface->ifa_addr->sa_family != AF_INET) { continue; } if (iface->ifa_addr->sa_family != AF_INET) { continue; }
InterfaceInfo info; InterfaceInfo info;
info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]); info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]);

View File

@ -1,6 +1,7 @@
#include <utils/networking.h> #include <utils/networking.h>
#include <assert.h> #include <assert.h>
#include <utils/flog.h> #include <utils/flog.h>
#include <stdexcept>
namespace net { namespace net {

View File

@ -8,7 +8,7 @@ class OptionList {
public: public:
OptionList() { updateText(); } OptionList() { updateText(); }
void define(K key, std::string name, T value) { void define(const K& key, const std::string& name, const T& value) {
if (keyExists(key)) { throw std::runtime_error("Key already exists"); } if (keyExists(key)) { throw std::runtime_error("Key already exists"); }
if (nameExists(name)) { throw std::runtime_error("Name already exists"); } if (nameExists(name)) { throw std::runtime_error("Name already exists"); }
if (valueExists(value)) { throw std::runtime_error("Value already exists"); } if (valueExists(value)) { throw std::runtime_error("Value already exists"); }
@ -18,27 +18,27 @@ public:
updateText(); updateText();
} }
void define(std::string name, T value) { void define(const std::string& name, const T& value) {
define(name, name, value); define(name, name, value);
} }
void undefined(int id) { void undefine(int id) {
keys.erase(keys.begin() + id); keys.erase(keys.begin() + id);
names.erase(names.begin() + id); names.erase(names.begin() + id);
values.erase(values.begin() + id); values.erase(values.begin() + id);
updateText(); updateText();
} }
void undefineKey(K key) { void undefineKey(const K& key) {
undefined(keyId(key)); undefine(keyId(key));
} }
void undefineName(std::string name) { void undefineName(const std::string& name) {
undefined(nameId(name)); undefine(nameId(name));
} }
void undefineValue(T value) { void undefineValue(const T& value) {
undefined(valueId(value)); undefine(valueId(value));
} }
void clear() { void clear() {
@ -48,61 +48,61 @@ public:
updateText(); updateText();
} }
int size() { int size() const {
return keys.size(); return keys.size();
} }
bool empty() { bool empty() const {
return keys.empty(); return keys.empty();
} }
bool keyExists(K key) { bool keyExists(const K& key) const {
if (std::find(keys.begin(), keys.end(), key) != keys.end()) { return true; } if (std::find(keys.begin(), keys.end(), key) != keys.end()) { return true; }
return false; return false;
} }
bool nameExists(std::string name) { bool nameExists(const std::string& name) const {
if (std::find(names.begin(), names.end(), name) != names.end()) { return true; } if (std::find(names.begin(), names.end(), name) != names.end()) { return true; }
return false; return false;
} }
bool valueExists(T value) { bool valueExists(const T& value) const {
if (std::find(values.begin(), values.end(), value) != values.end()) { return true; } if (std::find(values.begin(), values.end(), value) != values.end()) { return true; }
return false; return false;
} }
int keyId(K key) { int keyId(const K& key) const {
auto it = std::find(keys.begin(), keys.end(), key); auto it = std::find(keys.begin(), keys.end(), key);
if (it == keys.end()) { throw std::runtime_error("Key doesn't exists"); } if (it == keys.end()) { throw std::runtime_error("Key doesn't exists"); }
return std::distance(keys.begin(), it); return std::distance(keys.begin(), it);
} }
int nameId(std::string name) { int nameId(const std::string& name) const {
auto it = std::find(names.begin(), names.end(), name); auto it = std::find(names.begin(), names.end(), name);
if (it == names.end()) { throw std::runtime_error("Name doesn't exists"); } if (it == names.end()) { throw std::runtime_error("Name doesn't exists"); }
return std::distance(names.begin(), it); return std::distance(names.begin(), it);
} }
int valueId(T value) { int valueId(const T& value) const {
auto it = std::find(values.begin(), values.end(), value); auto it = std::find(values.begin(), values.end(), value);
if (it == values.end()) { throw std::runtime_error("Value doesn't exists"); } if (it == values.end()) { throw std::runtime_error("Value doesn't exists"); }
return std::distance(values.begin(), it); return std::distance(values.begin(), it);
} }
K key(int id) { inline const K& key(int id) const {
return keys[id]; return keys[id];
} }
std::string name(int id) { inline const std::string& name(int id) const {
return names[id]; return names[id];
} }
T value(int id) { inline const T& value(int id) const {
return values[id]; return values[id];
} }
T operator[](int& id) { inline const T& operator[](int& id) const {
return value(id); return values[id];
} }
const char* txt = NULL; const char* txt = NULL;

View File

@ -214,10 +214,10 @@ private:
if (ImGui::Checkbox(CONCAT("Show Reference Lines##m17_showlines_", _this->name), &_this->showLines)) { if (ImGui::Checkbox(CONCAT("Show Reference Lines##m17_showlines_", _this->name), &_this->showLines)) {
if (_this->showLines) { if (_this->showLines) {
_this->diag.lines.push_back(-0.75f); _this->diag.lines.push_back(-1.0);
_this->diag.lines.push_back(-0.25f); _this->diag.lines.push_back(-1.0/3.0);
_this->diag.lines.push_back(0.25f); _this->diag.lines.push_back(1.0/3.0);
_this->diag.lines.push_back(0.75f); _this->diag.lines.push_back(1.0);
} }
else { else {
_this->diag.lines.clear(); _this->diag.lines.clear();

View File

@ -45,6 +45,7 @@ namespace demod {
virtual int getDefaultDeemphasisMode() = 0; virtual int getDefaultDeemphasisMode() = 0;
virtual bool getFMIFNRAllowed() = 0; virtual bool getFMIFNRAllowed() = 0;
virtual bool getNBAllowed() = 0; virtual bool getNBAllowed() = 0;
virtual bool getAFNRAllowed() = 0;
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0; virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
}; };
} }

View File

@ -86,6 +86,7 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return false; } bool getNBAllowed() { return false; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@ -92,6 +92,7 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return false; } bool getNBAllowed() { return false; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@ -79,6 +79,7 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; } bool getNBAllowed() { return true; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@ -79,6 +79,7 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; } bool getNBAllowed() { return true; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@ -19,15 +19,17 @@ namespace demod {
// Load config // Load config
_config->acquire(); _config->acquire();
bool modified = false;
if (config->conf[name][getName()].contains("lowPass")) { if (config->conf[name][getName()].contains("lowPass")) {
_lowPass = config->conf[name][getName()]["lowPass"]; _lowPass = config->conf[name][getName()]["lowPass"];
} }
_config->release(modified); if (config->conf[name][getName()].contains("highPass")) {
_highPass = config->conf[name][getName()]["highPass"];
}
_config->release();
// Define structure // Define structure
demod.init(input, getIFSampleRate(), bandwidth, _lowPass); demod.init(input, getIFSampleRate(), bandwidth, _lowPass, _highPass);
} }
void start() { demod.start(); } void start() { demod.start(); }
@ -41,6 +43,12 @@ namespace demod {
_config->conf[name][getName()]["lowPass"] = _lowPass; _config->conf[name][getName()]["lowPass"] = _lowPass;
_config->release(true); _config->release(true);
} }
if (ImGui::Checkbox(("High Pass##_radio_wfm_highpass_" + name).c_str(), &_highPass)) {
demod.setHighPass(_highPass);
_config->acquire();
_config->conf[name][getName()]["highPass"] = _highPass;
_config->release(true);
}
} }
void setBandwidth(double bandwidth) { void setBandwidth(double bandwidth) {
@ -67,6 +75,7 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return true; } bool getFMIFNRAllowed() { return true; }
bool getNBAllowed() { return false; } bool getNBAllowed() { return false; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:
@ -75,6 +84,7 @@ namespace demod {
ConfigManager* _config = NULL; ConfigManager* _config = NULL;
bool _lowPass = true; bool _lowPass = true;
bool _highPass = false;
std::string name; std::string name;
}; };

View File

@ -59,6 +59,7 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; } bool getNBAllowed() { return true; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
private: private:

View File

@ -80,6 +80,7 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; } bool getNBAllowed() { return true; }
bool getAFNRAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@ -130,6 +130,7 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; } int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
bool getFMIFNRAllowed() { return true; } bool getFMIFNRAllowed() { return true; }
bool getNBAllowed() { return false; } bool getNBAllowed() { return false; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
// ============= DEDICATED FUNCTIONS ============= // ============= DEDICATED FUNCTIONS =============

View File

@ -9,6 +9,7 @@
#include <dsp/noise_reduction/noise_blanker.h> #include <dsp/noise_reduction/noise_blanker.h>
#include <dsp/noise_reduction/fm_if.h> #include <dsp/noise_reduction/fm_if.h>
#include <dsp/noise_reduction/squelch.h> #include <dsp/noise_reduction/squelch.h>
#include <dsp/noise_reduction/audio.h>
#include <dsp/multirate/rational_resampler.h> #include <dsp/multirate/rational_resampler.h>
#include <dsp/filter/deephasis.h> #include <dsp/filter/deephasis.h>
#include <core.h> #include <core.h>
@ -83,9 +84,11 @@ public:
resamp.init(NULL, 250000.0, 48000.0); resamp.init(NULL, 250000.0, 48000.0);
deemp.init(NULL, 50e-6, 48000.0); deemp.init(NULL, 50e-6, 48000.0);
afNR.init(NULL, 1024);
afChain.addBlock(&resamp, true); afChain.addBlock(&resamp, true);
afChain.addBlock(&deemp, false); afChain.addBlock(&deemp, false);
afChain.addBlock(&afNR, false);
// Initialize the sink // Initialize the sink
srChangeHandler.ctx = this; srChangeHandler.ctx = this;
@ -247,6 +250,12 @@ private:
if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); } if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); }
} }
// Noise reduction
if (_this->afNRAllowed) {
if (ImGui::Checkbox(("Audio Noise Reduction##_radio_afnr_ena_" + _this->name).c_str(), &_this->afNREnabled)) {
_this->setAFNREnabled(_this->afNREnabled);
}
}
// Squelch // Squelch
if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) { if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) {
@ -370,6 +379,8 @@ private:
fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE); fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE);
nbAllowed = selectedDemod->getNBAllowed(); nbAllowed = selectedDemod->getNBAllowed();
nbEnabled = false; nbEnabled = false;
afNRAllowed = selectedDemod->getAFNRAllowed();
afNREnabled = false;
nbLevel = 0.0f; nbLevel = 0.0f;
double ifSamplerate = selectedDemod->getIFSampleRate(); double ifSamplerate = selectedDemod->getIFSampleRate();
config.acquire(); config.acquire();
@ -411,6 +422,9 @@ private:
if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerLevel")) { if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerLevel")) {
nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"]; nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"];
} }
if (config.conf[name][selectedDemod->getName()].contains("audioNoiseReductionEnabled")) {
nbEnabled = config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"];
}
config.release(); config.release();
// Configure VFO // Configure VFO
@ -446,7 +460,10 @@ private:
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Configure deemphasis // Configure deemphasis
setDeemphasisMode(deempModes[deempId]); setDeemphasisMode(deempAllowed ? deempModes[deempId] : DEEMP_MODE_NONE);
// Configure AF NR
setAFNREnabled(afNRAllowed && afNREnabled);
} }
else { else {
// Disable everything if post processing is disabled // Disable everything if post processing is disabled
@ -508,6 +525,17 @@ private:
config.release(true); config.release(true);
} }
void setAFNREnabled(bool enable) {
afNREnabled = enable;
if (!postProcEnabled || !selectedDemod) { return; }
afChain.setBlockEnabled(&afNR, afNREnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Save config
config.acquire();
config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"] = nbEnabled;
config.release(true);
}
void setNBEnabled(bool enable) { void setNBEnabled(bool enable) {
nbEnabled = enable; nbEnabled = enable;
if (!selectedDemod) { return; } if (!selectedDemod) { return; }
@ -660,6 +688,7 @@ private:
dsp::chain<dsp::stereo_t> afChain; dsp::chain<dsp::stereo_t> afChain;
dsp::multirate::RationalResampler<dsp::stereo_t> resamp; dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
dsp::filter::Deemphasis<dsp::stereo_t> deemp; dsp::filter::Deemphasis<dsp::stereo_t> deemp;
dsp::noise_reduction::Audio afNR;
SinkManager::Stream stream; SinkManager::Stream stream;
@ -683,6 +712,9 @@ private:
int deempId = 0; int deempId = 0;
bool deempAllowed; bool deempAllowed;
bool afNREnabled = false;
bool afNRAllowed;
bool FMIFNRAllowed; bool FMIFNRAllowed;
bool FMIFNREnabled = false; bool FMIFNREnabled = false;
int fmIFPresetId; int fmIFPresetId;

View File

@ -23,7 +23,8 @@
#include <utils/wav.h> #include <utils/wav.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str()) #define CONCAT(a, b) ((std::string(a) + b).c_str())
#define SILENCE_LVL 10e-20
#define SILENCE_LVL 10e-6
SDRPP_MOD_INFO{ SDRPP_MOD_INFO{
/* Name: */ "recorder", /* Name: */ "recorder",
@ -315,11 +316,11 @@ private:
} }
if (_this->recording) { style::endDisabled(); } if (_this->recording) { style::endDisabled(); }
// if (ImGui::Checkbox(CONCAT("Ignore silence##_recorder_ignore_silence_", _this->name), &_this->ignoreSilence)) { if (ImGui::Checkbox(CONCAT("Ignore silence##_recorder_ignore_silence_", _this->name), &_this->ignoreSilence)) {
// config.acquire(); config.acquire();
// config.conf[_this->name]["ignoreSilence"] = _this->ignoreSilence; config.conf[_this->name]["ignoreSilence"] = _this->ignoreSilence;
// config.release(true); config.release(true);
// } }
} }
// Record button // Record button
@ -338,7 +339,13 @@ private:
uint64_t seconds = _this->writer.getSamplesWritten() / _this->samplerate; uint64_t seconds = _this->writer.getSamplesWritten() / _this->samplerate;
time_t diff = seconds; time_t diff = seconds;
tm* dtm = gmtime(&diff); tm* dtm = gmtime(&diff);
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec);
if (_this->ignoreSilence && _this->ignoringSilence) {
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Paused %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec);
}
else {
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec);
}
} }
} }
@ -480,13 +487,31 @@ private:
static void stereoHandler(dsp::stereo_t* data, int count, void* ctx) { static void stereoHandler(dsp::stereo_t* data, int count, void* ctx) {
RecorderModule* _this = (RecorderModule*)ctx; RecorderModule* _this = (RecorderModule*)ctx;
// TODO: Ignore silence if (_this->ignoreSilence) {
float absMax = 0.0f;
float* _data = (float*)data;
int _count = count * 2;
for (int i = 0; i < _count; i++) {
float val = fabsf(_data[i]);
if (val > absMax) { absMax = val; }
}
_this->ignoringSilence = (absMax < SILENCE_LVL);
if (_this->ignoringSilence) { return; }
}
_this->writer.write((float*)data, count); _this->writer.write((float*)data, count);
} }
static void monoHandler(float* data, int count, void* ctx) { static void monoHandler(float* data, int count, void* ctx) {
RecorderModule* _this = (RecorderModule*)ctx; RecorderModule* _this = (RecorderModule*)ctx;
// TODO: Ignore silence if (_this->ignoreSilence) {
float absMax = 0.0f;
for (int i = 0; i < count; i++) {
float val = fabsf(data[i]);
if (val > absMax) { absMax = val; }
}
_this->ignoringSilence = (absMax < SILENCE_LVL);
if (_this->ignoringSilence) { return; }
}
_this->writer.write(data, count); _this->writer.write(data, count);
} }
@ -529,6 +554,7 @@ private:
dsp::stereo_t audioLvl = { -100.0f, -100.0f }; dsp::stereo_t audioLvl = { -100.0f, -100.0f };
bool recording = false; bool recording = false;
bool ignoringSilence = false;
wav::Writer writer; wav::Writer writer;
std::recursive_mutex recMtx; std::recursive_mutex recMtx;
dsp::stream<dsp::complex_t>* basebandStream; dsp::stream<dsp::complex_t>* basebandStream;

View File

@ -45,6 +45,9 @@ public:
} }
config.release(); config.release();
_retuneHandler.ctx = this;
_retuneHandler.handler = retuneHandler;
gui::menu.registerEntry(name, menuHandler, this, NULL); gui::menu.registerEntry(name, menuHandler, this, NULL);
} }
@ -84,8 +87,8 @@ public:
// Switch source to panadapter mode // Switch source to panadapter mode
sigpath::sourceManager.setPanadpterIF(ifFreq); sigpath::sourceManager.setPanadpterIF(ifFreq);
sigpath::sourceManager.setTuningMode(TUNING_MODE_PANADAPTER); sigpath::sourceManager.setTuningMode(SourceManager::TuningMode::PANADAPTER);
retuneHandlerId = sigpath::sourceManager.onRetune.bind(retuneHandler, this); sigpath::sourceManager.onRetune.bindHandler(&_retuneHandler);
running = true; running = true;
} }
@ -95,8 +98,8 @@ public:
if (!running) { return; } if (!running) { return; }
// Switch source back to normal mode // Switch source back to normal mode
sigpath::sourceManager.onRetune.unbind(retuneHandlerId); sigpath::sourceManager.onRetune.unbindHandler(&_retuneHandler);
sigpath::sourceManager.setTuningMode(TUNING_MODE_NORMAL); sigpath::sourceManager.setTuningMode(SourceManager::TuningMode::NORMAL);
// Disconnect from rigctl server // Disconnect from rigctl server
client->close(); client->close();
@ -156,9 +159,10 @@ private:
} }
} }
void retuneHandler(double freq) { static void retuneHandler(double freq, void* ctx) {
if (!client || !client->isOpen()) { return; } RigctlClientModule* _this = (RigctlClientModule*)ctx;
if (client->setFreq(freq)) { if (!_this->client || !_this->client->isOpen()) { return; }
if (_this->client->setFreq(freq)) {
flog::error("Could not set frequency"); flog::error("Could not set frequency");
} }
} }
@ -174,7 +178,7 @@ private:
double ifFreq = 8830000.0; double ifFreq = 8830000.0;
HandlerID retuneHandlerId; EventHandler<double> _retuneHandler;
}; };
MOD_EXPORT void _INIT_() { MOD_EXPORT void _INIT_() {

View File

@ -429,25 +429,34 @@ I will soon publish a contributing.md listing the code style to use.
* Dale L Puckett (K0HYD) * Dale L Puckett (K0HYD)
* [Daniele D'Agnelli](https://linkedin.com/in/dagnelli) * [Daniele D'Agnelli](https://linkedin.com/in/dagnelli)
* D. Jones * D. Jones
* Dexruus
* [EB3FRN](https://www.eb3frn.net/) * [EB3FRN](https://www.eb3frn.net/)
* Eric Johnson * Eric Johnson
* Ernest Murphy (NH7L) * Ernest Murphy (NH7L)
* Flinger Films * Flinger Films
* [Frank Werner (HB9FXQ)](https://twitter.com/HB9FXQ)
* gringogrigio * gringogrigio
* Jeff Moe
* Joe Cupano * Joe Cupano
* KD1SQ
* Kezza * Kezza
* Krys Kamieniecki * Krys Kamieniecki
* Lee Donaghy * Lee Donaghy
* Lee KD1SQ * Lee KD1SQ
* .lozenge. (Hank Hill) * .lozenge. (Hank Hill)
* Martin Herren (HB9FXX)
* ON4MU * ON4MU
* [Passion-Radio.com](https://passion-radio.com/) * [Passion-Radio.com](https://passion-radio.com/)
* Paul Maine * Paul Maine
* Peter Betz
* [Scanner School](https://scannerschool.com/) * [Scanner School](https://scannerschool.com/)
* Scott Palmer
* [SignalsEverywhere](https://signalseverywhere.com/) * [SignalsEverywhere](https://signalseverywhere.com/)
* Syne Ardwin (WI9SYN) * Syne Ardwin (WI9SYN)
* [W4IPA](https://twitter.com/W4IPAstroke5) * [W4IPA](https://twitter.com/W4IPAstroke5)
* [Zipper](github.com/reppiZ) * William Arcand (W1WRA)
* [Yves Rougy](https://www.twitch.tv/yorzian)
* [Zipper](https://github.com/reppiZ)
## Contributors ## Contributors

View File

@ -0,0 +1,274 @@
{
"name": "Belgium",
"country_name": "Belgium",
"country_code": "BE",
"author_name": "Bastien Cabay - ON4BCY",
"author_url": "https://qrz.com/db/ON4BCY",
"bands": [
{
"name": "2200m - Amateur",
"type": "amateur",
"start": 135700,
"end": 137800
},
{
"name": "630m - Amateur",
"type": "amateur",
"start": 472000,
"end": 479000
},
{
"name": "600m - Amateur",
"type": "amateur",
"start": 501000,
"end": 504000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 526500,
"end": 1606500
},
{
"name": "160m - Amateur",
"type": "amateur",
"start": 1810000,
"end": 2000000
},
{
"name": "80m - Amateur",
"type": "amateur",
"start": 3500000,
"end": 3800000
},
{
"name": "60m - Amateur",
"type": "amateur",
"start": 5351500,
"end": 5366500
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 5950000,
"end": 6200000
},
{
"name": "40m - Amateur",
"type": "amateur",
"start": 7000000,
"end": 7200000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 7200000,
"end": 7300000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 9500000,
"end": 9900000
},
{
"name": "30m - Amateur",
"type": "amateur",
"start": 10100000,
"end": 10150000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 11650000,
"end": 12050000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 13600000,
"end": 13800000
},
{
"name": "20m - Amateur",
"type": "amateur",
"start": 14000000,
"end": 14350000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 15100000,
"end": 15600000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 17550000,
"end": 17900000
},
{
"name": "17m - Amateur",
"type": "amateur",
"start": 18068000,
"end": 18168000
},
{
"name": "15m - Amateur",
"type": "amateur",
"start": 21000000,
"end": 21450000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 21450000,
"end": 21850000
},
{
"name": "12m - Amateur",
"type": "amateur",
"start": 24890000,
"end": 24990000
},
{
"name": "AM Broadcast",
"type": "broadcast",
"start": 25670000,
"end": 26100000
},
{
"name": "11m - Citizen Band",
"type": "amateur",
"start": 26960000,
"end": 27410000
},
{
"name": "10m - Amateur",
"type": "amateur",
"start": 28000000,
"end": 29700000
},
{
"name": "6m - Amateur",
"type": "amateur",
"start": 50000000,
"end": 52000000
},
{
"name": "4m - Amateur",
"type": "amateur",
"start": 69945000,
"end": 69955000
},
{
"name": "4m - Amateur",
"type": "amateur",
"start": 70190000,
"end": 70412500
},
{
"name": "FM Broadcast",
"type": "broadcast",
"start": 87500000,
"end": 108000000
},
{
"name": "Space Exploration / Meteorology Sat. / S-PCS",
"type": "satellite",
"start": 137000000,
"end": 138000000
},
{
"name": "2m - Amateur",
"type": "amateur",
"start": 144000000,
"end": 146000000
},
{
"name": "T-DAB Broadcast",
"type": "broadcast",
"start": 174000000,
"end": 223000000
},
{
"name": "70cm - Amateur",
"type": "amateur",
"start": 430000000,
"end": 440000000
},
{
"name": "PMR446",
"type": "amateur",
"start": 446000000,
"end": 446200000
},
{
"name": "DVB-T - Broadcast",
"type": "broadcast",
"start": 470000000,
"end": 790000000
},
{
"name": "23cm - Amateur",
"type": "amateur",
"start": 1240000000,
"end": 1300000000
},
{
"name": "13cm - Amateur",
"type": "amateur",
"start": 2300000000,
"end": 2450000000
},
{
"name": "6cm - Amateur",
"type": "amateur",
"start": 5650000000,
"end": 5850000000
},
{
"name": "3cm - Amateur",
"type": "amateur",
"start": 10000000000,
"end": 10500000000
},
{
"name": "1.25cm - Amateur",
"type": "amateur",
"start": 24000000000,
"end": 24250000000
},
{
"name": "6mm - Amateur",
"type": "amateur",
"start": 47000000000,
"end": 47200000000
},
{
"name": "4mm - Amateur",
"type": "amateur",
"start": 75500000000,
"end": 81000000000
},
{
"name": "2.5mm - Amateur",
"type": "amateur",
"start": 122250000000,
"end": 123000000000
},
{
"name": "2mm - Amateur",
"type": "amateur",
"start": 142000000000,
"end": 149000000000
},
{
"name": "1mm - Amateur",
"type": "amateur",
"start": 241000000000,
"end": 250000000000
}
]
}

View File

@ -25,7 +25,7 @@ SDRPP_MOD_INFO{
ConfigManager config; ConfigManager config;
class AirspySourceModule : public ModuleManager::Instance, public Source { class AirspySourceModule : public ModuleManager::Instance {
public: public:
AirspySourceModule(std::string name) { AirspySourceModule(std::string name) {
this->name = name; this->name = name;
@ -34,6 +34,15 @@ public:
sampleRate = 10000000.0; sampleRate = 10000000.0;
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
handler.menuHandler = menuHandler;
handler.startHandler = start;
handler.stopHandler = stop;
handler.tuneHandler = tune;
handler.stream = &stream;
refresh(); refresh();
if (sampleRateList.size() > 0) { if (sampleRateList.size() > 0) {
sampleRate = sampleRateList[0]; sampleRate = sampleRateList[0];
@ -45,11 +54,11 @@ public:
config.release(); config.release();
selectByString(devSerial); selectByString(devSerial);
sigpath::sourceManager.registerSource("Airspy", this); sigpath::sourceManager.registerSource("Airspy", &handler);
} }
~AirspySourceModule() { ~AirspySourceModule() {
stop(); stop(this);
sigpath::sourceManager.unregisterSource("Airspy"); sigpath::sourceManager.unregisterSource("Airspy");
airspy_exit(); airspy_exit();
} }
@ -222,315 +231,6 @@ public:
airspy_close(dev); airspy_close(dev);
} }
void select() {
core::setInputSampleRate(sampleRate);
flog::info("AirspySourceModule '{0}': Select!", name);
}
void deselect() {
flog::info("AirspySourceModule '{0}': Deselect!", name);
}
bool start() {
if (running) { return true; }
if (selectedSerial == 0) {
flog::error("Tried to start Airspy source with null serial");
return false;
}
#ifndef __ANDROID__
int err = airspy_open_sn(&openDev, selectedSerial);
#else
int err = airspy_open_fd(&openDev, devFd);
#endif
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, selectedSerial);
flog::error("Could not open Airspy {0}", buf);
return false;
}
airspy_set_samplerate(openDev, sampleRateList[srId]);
airspy_set_freq(openDev, freq);
if (gainMode == 0) {
airspy_set_lna_agc(openDev, 0);
airspy_set_mixer_agc(openDev, 0);
airspy_set_sensitivity_gain(openDev, sensitiveGain);
}
else if (gainMode == 1) {
airspy_set_lna_agc(openDev, 0);
airspy_set_mixer_agc(openDev, 0);
airspy_set_linearity_gain(openDev, linearGain);
}
else if (gainMode == 2) {
if (lnaAgc) {
airspy_set_lna_agc(openDev, 1);
}
else {
airspy_set_lna_agc(openDev, 0);
airspy_set_lna_gain(openDev, lnaGain);
}
if (mixerAgc) {
airspy_set_mixer_agc(openDev, 1);
}
else {
airspy_set_mixer_agc(openDev, 0);
airspy_set_mixer_gain(openDev, mixerGain);
}
airspy_set_vga_gain(openDev, vgaGain);
}
airspy_set_rf_bias(openDev, biasT);
airspy_start_rx(openDev, callback, this);
running = true;
flog::info("AirspySourceModule '{0}': Start!", name);
}
void stop() {
if (!running) { return; }
running = false;
stream.stopWriter();
airspy_close(openDev);
stream.clearWriteStop();
flog::info("AirspySourceModule '{0}': Stop!", name);
}
void tune(double freq) {
this->freq = freq;
if (running) {
airspy_set_freq(openDev, freq);
}
flog::info("AirspySourceModule '{0}': Tune: {1}!", name, freq);
}
void showMenu() {
if (running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_airspy_dev_sel_", name), &devId, devListTxt.c_str())) {
selectBySerial(devList[devId]);
core::setInputSampleRate(sampleRate);
if (selectedSerStr != "") {
config.acquire();
config.conf["device"] = selectedSerStr;
config.release(true);
}
}
if (SmGui::Combo(CONCAT("##_airspy_sr_sel_", name), &srId, sampleRateListTxt.c_str())) {
sampleRate = sampleRateList[srId];
core::setInputSampleRate(sampleRate);
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["sampleRate"] = sampleRate;
config.release(true);
}
}
SmGui::SameLine();
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_airspy_refr_", name))) {
refresh();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
selectByString(devSerial);
core::setInputSampleRate(sampleRate);
}
if (running) { SmGui::EndDisabled(); }
SmGui::BeginGroup();
SmGui::Columns(3, CONCAT("AirspyGainModeColumns##_", name), false);
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Sensitive##_airspy_gm_", name), gainMode == 0)) {
gainMode = 0;
if (running) {
airspy_set_lna_agc(openDev, 0);
airspy_set_mixer_agc(openDev, 0);
airspy_set_sensitivity_gain(openDev, sensitiveGain);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["gainMode"] = 0;
config.release(true);
}
}
SmGui::NextColumn();
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Linear##_airspy_gm_", name), gainMode == 1)) {
gainMode = 1;
if (running) {
airspy_set_lna_agc(openDev, 0);
airspy_set_mixer_agc(openDev, 0);
airspy_set_linearity_gain(openDev, linearGain);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["gainMode"] = 1;
config.release(true);
}
}
SmGui::NextColumn();
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Free##_airspy_gm_", name), gainMode == 2)) {
gainMode = 2;
if (running) {
if (lnaAgc) {
airspy_set_lna_agc(openDev, 1);
}
else {
airspy_set_lna_agc(openDev, 0);
airspy_set_lna_gain(openDev, lnaGain);
}
if (mixerAgc) {
airspy_set_mixer_agc(openDev, 1);
}
else {
airspy_set_mixer_agc(openDev, 0);
airspy_set_mixer_gain(openDev, mixerGain);
}
airspy_set_vga_gain(openDev, vgaGain);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["gainMode"] = 2;
config.release(true);
}
}
SmGui::Columns(1, CONCAT("EndAirspyGainModeColumns##_", name), false);
SmGui::EndGroup();
// Gain menus
if (gainMode == 0) {
SmGui::LeftLabel("Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_sens_gain_", name), &sensitiveGain, 0, 21)) {
if (running) {
airspy_set_sensitivity_gain(openDev, sensitiveGain);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["sensitiveGain"] = sensitiveGain;
config.release(true);
}
}
}
else if (gainMode == 1) {
SmGui::LeftLabel("Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_lin_gain_", name), &linearGain, 0, 21)) {
if (running) {
airspy_set_linearity_gain(openDev, linearGain);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["linearGain"] = linearGain;
config.release(true);
}
}
}
else if (gainMode == 2) {
// TODO: Switch to a table for alignment
if (lnaAgc) { SmGui::BeginDisabled(); }
SmGui::LeftLabel("LNA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_lna_gain_", name), &lnaGain, 0, 15)) {
if (running) {
airspy_set_lna_gain(openDev, lnaGain);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["lnaGain"] = lnaGain;
config.release(true);
}
}
if (lnaAgc) { SmGui::EndDisabled(); }
if (mixerAgc) { SmGui::BeginDisabled(); }
SmGui::LeftLabel("Mixer Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_mix_gain_", name), &mixerGain, 0, 15)) {
if (running) {
airspy_set_mixer_gain(openDev, mixerGain);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["mixerGain"] = mixerGain;
config.release(true);
}
}
if (mixerAgc) { SmGui::EndDisabled(); }
SmGui::LeftLabel("VGA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_vga_gain_", name), &vgaGain, 0, 15)) {
if (running) {
airspy_set_vga_gain(openDev, vgaGain);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["vgaGain"] = vgaGain;
config.release(true);
}
}
// AGC Control
SmGui::ForceSync();
if (SmGui::Checkbox(CONCAT("LNA AGC##_airspy_", name), &lnaAgc)) {
if (running) {
if (lnaAgc) {
airspy_set_lna_agc(openDev, 1);
}
else {
airspy_set_lna_agc(openDev, 0);
airspy_set_lna_gain(openDev, lnaGain);
}
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["lnaAgc"] = lnaAgc;
config.release(true);
}
}
SmGui::ForceSync();
if (SmGui::Checkbox(CONCAT("Mixer AGC##_airspy_", name), &mixerAgc)) {
if (running) {
if (mixerAgc) {
airspy_set_mixer_agc(openDev, 1);
}
else {
airspy_set_mixer_agc(openDev, 0);
airspy_set_mixer_gain(openDev, mixerGain);
}
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["mixerAgc"] = mixerAgc;
config.release(true);
}
}
}
// Bias T
if (SmGui::Checkbox(CONCAT("Bias T##_airspy_", name), &biasT)) {
if (running) {
airspy_set_rf_bias(openDev, biasT);
}
if (selectedSerStr != "") {
config.acquire();
config.conf["devices"][selectedSerStr]["biasT"] = biasT;
config.release(true);
}
}
}
private: private:
std::string getBandwdithScaled(double bw) { std::string getBandwdithScaled(double bw) {
char buf[1024]; char buf[1024];
@ -546,6 +246,322 @@ private:
return std::string(buf); return std::string(buf);
} }
static void menuSelected(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
flog::info("AirspySourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
flog::info("AirspySourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (_this->running) { return; }
if (_this->selectedSerial == 0) {
flog::error("Tried to start Airspy source with null serial");
return;
}
#ifndef __ANDROID__
int err = airspy_open_sn(&_this->openDev, _this->selectedSerial);
#else
int err = airspy_open_fd(&_this->openDev, _this->devFd);
#endif
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
flog::error("Could not open Airspy {0}", buf);
return;
}
airspy_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]);
airspy_set_freq(_this->openDev, _this->freq);
if (_this->gainMode == 0) {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
else if (_this->gainMode == 1) {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
}
else if (_this->gainMode == 2) {
if (_this->lnaAgc) {
airspy_set_lna_agc(_this->openDev, 1);
}
else {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->mixerAgc) {
airspy_set_mixer_agc(_this->openDev, 1);
}
else {
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
}
airspy_set_rf_bias(_this->openDev, _this->biasT);
airspy_start_rx(_this->openDev, callback, _this);
_this->running = true;
flog::info("AirspySourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (!_this->running) { return; }
_this->running = false;
_this->stream.stopWriter();
airspy_close(_this->openDev);
_this->stream.clearWriteStop();
flog::info("AirspySourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (_this->running) {
airspy_set_freq(_this->openDev, freq);
}
_this->freq = freq;
flog::info("AirspySourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_airspy_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
_this->selectBySerial(_this->devList[_this->devId]);
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["device"] = _this->selectedSerStr;
config.release(true);
}
}
if (SmGui::Combo(CONCAT("##_airspy_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) {
_this->sampleRate = _this->sampleRateList[_this->srId];
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
config.release(true);
}
}
SmGui::SameLine();
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_airspy_refr_", _this->name))) {
_this->refresh();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
_this->selectByString(devSerial);
core::setInputSampleRate(_this->sampleRate);
}
if (_this->running) { SmGui::EndDisabled(); }
SmGui::BeginGroup();
SmGui::Columns(3, CONCAT("AirspyGainModeColumns##_", _this->name), false);
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Sensitive##_airspy_gm_", _this->name), _this->gainMode == 0)) {
_this->gainMode = 0;
if (_this->running) {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0;
config.release(true);
}
}
SmGui::NextColumn();
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Linear##_airspy_gm_", _this->name), _this->gainMode == 1)) {
_this->gainMode = 1;
if (_this->running) {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1;
config.release(true);
}
}
SmGui::NextColumn();
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Free##_airspy_gm_", _this->name), _this->gainMode == 2)) {
_this->gainMode = 2;
if (_this->running) {
if (_this->lnaAgc) {
airspy_set_lna_agc(_this->openDev, 1);
}
else {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->mixerAgc) {
airspy_set_mixer_agc(_this->openDev, 1);
}
else {
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2;
config.release(true);
}
}
SmGui::Columns(1, CONCAT("EndAirspyGainModeColumns##_", _this->name), false);
SmGui::EndGroup();
// Gain menus
if (_this->gainMode == 0) {
SmGui::LeftLabel("Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_sens_gain_", _this->name), &_this->sensitiveGain, 0, 21)) {
if (_this->running) {
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain;
config.release(true);
}
}
}
else if (_this->gainMode == 1) {
SmGui::LeftLabel("Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_lin_gain_", _this->name), &_this->linearGain, 0, 21)) {
if (_this->running) {
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain;
config.release(true);
}
}
}
else if (_this->gainMode == 2) {
// TODO: Switch to a table for alignment
if (_this->lnaAgc) { SmGui::BeginDisabled(); }
SmGui::LeftLabel("LNA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) {
if (_this->running) {
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain;
config.release(true);
}
}
if (_this->lnaAgc) { SmGui::EndDisabled(); }
if (_this->mixerAgc) { SmGui::BeginDisabled(); }
SmGui::LeftLabel("Mixer Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_mix_gain_", _this->name), &_this->mixerGain, 0, 15)) {
if (_this->running) {
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain;
config.release(true);
}
}
if (_this->mixerAgc) { SmGui::EndDisabled(); }
SmGui::LeftLabel("VGA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_airspy_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) {
if (_this->running) {
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain;
config.release(true);
}
}
// AGC Control
SmGui::ForceSync();
if (SmGui::Checkbox(CONCAT("LNA AGC##_airspy_", _this->name), &_this->lnaAgc)) {
if (_this->running) {
if (_this->lnaAgc) {
airspy_set_lna_agc(_this->openDev, 1);
}
else {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc;
config.release(true);
}
}
SmGui::ForceSync();
if (SmGui::Checkbox(CONCAT("Mixer AGC##_airspy_", _this->name), &_this->mixerAgc)) {
if (_this->running) {
if (_this->mixerAgc) {
airspy_set_mixer_agc(_this->openDev, 1);
}
else {
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc;
config.release(true);
}
}
}
// Bias T
if (SmGui::Checkbox(CONCAT("Bias T##_airspy_", _this->name), &_this->biasT)) {
if (_this->running) {
airspy_set_rf_bias(_this->openDev, _this->biasT);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT;
config.release(true);
}
}
}
static int callback(airspy_transfer_t* transfer) { static int callback(airspy_transfer_t* transfer) {
AirspySourceModule* _this = (AirspySourceModule*)transfer->ctx; AirspySourceModule* _this = (AirspySourceModule*)transfer->ctx;
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t)); memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
@ -558,6 +574,7 @@ private:
bool enabled = true; bool enabled = true;
dsp::stream<dsp::complex_t> stream; dsp::stream<dsp::complex_t> stream;
double sampleRate; double sampleRate;
SourceManager::SourceHandler handler;
bool running = false; bool running = false;
double freq; double freq;
uint64_t selectedSerial = 0; uint64_t selectedSerial = 0;

View File

@ -25,7 +25,7 @@ ConfigManager config;
struct DeviceInfo { struct DeviceInfo {
RtAudio::DeviceInfo info; RtAudio::DeviceInfo info;
int id; int id;
bool operator==(const struct DeviceInfo& other) { bool operator==(const struct DeviceInfo& other) const {
return other.id == id; return other.id == id;
} }
}; };

View File

@ -205,6 +205,7 @@ namespace hermes {
} }
std::vector<Info> discover() { std::vector<Info> discover() {
// TODO: Maybe try to instead detect on each interface as a work around for 0.0.0.0 not receiving anything?
auto sock = net::openudp("0.0.0.0", 1024); auto sock = net::openudp("0.0.0.0", 1024);
// Build discovery packet // Build discovery packet

View File

@ -39,7 +39,7 @@ namespace hermes {
uint8_t gatewareVerMin; uint8_t gatewareVerMin;
BoardID boardId; BoardID boardId;
bool operator==(const Info& b) { bool operator==(const Info& b) const {
return !memcmp(mac, b.mac, 6); return !memcmp(mac, b.mac, 6);
} }
}; };

View File

@ -319,6 +319,7 @@ private:
static void start(void* ctx) { static void start(void* ctx) {
LimeSDRSourceModule* _this = (LimeSDRSourceModule*)ctx; LimeSDRSourceModule* _this = (LimeSDRSourceModule*)ctx;
if (_this->running) { return; } if (_this->running) { return; }
if (_this->selectedDevName.empty()) { return; }
// Open device // Open device
_this->openDev = NULL; _this->openDev = NULL;
@ -329,7 +330,10 @@ private:
if (err) { if (err) {
LMS_Close(_this->openDev); LMS_Close(_this->openDev);
LMS_Open(&_this->openDev, _this->devList[_this->devId], NULL); LMS_Open(&_this->openDev, _this->devList[_this->devId], NULL);
LMS_Init(_this->openDev); if (err = LMS_Init(_this->openDev)) {
flog::error("Failed to re-initialize device ({})", err);
return;
}
} }
flog::warn("Channel count: {0}", LMS_GetNumChannels(_this->openDev, false)); flog::warn("Channel count: {0}", LMS_GetNumChannels(_this->openDev, false));
@ -546,4 +550,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
MOD_EXPORT void _END_() { MOD_EXPORT void _END_() {
config.disableAutoSave(); config.disableAutoSave();
config.save(); config.save();
} }

View File

@ -108,12 +108,12 @@ namespace rfspace {
} }
break; break;
case RFSPACE_DEV_ID_NET_SDR: case RFSPACE_DEV_ID_NET_SDR:
case RFSPACE_DEV_ID_SDR_IP:
default:
for (int n = 80000000 / (4 * 25); n >= 32000; n /= 2) { for (int n = 80000000 / (4 * 25); n >= 32000; n /= 2) {
sr.push_back(n); sr.push_back(n);
} }
break; break;
default:
break;
} }
return sr; return sr;

View File

@ -503,7 +503,7 @@ private:
float refStep = 0.5; float refStep = 0.5;
struct SRCombo { struct SRCombo {
bool operator==(const SRCombo& b) { bool operator==(const SRCombo& b) const {
return baseId == b.baseId && decimId == b.decimId; return baseId == b.baseId && decimId == b.decimId;
} }