bugfix, new squelch system, dragon labs source

This commit is contained in:
AlexandreRouma
2026-03-21 23:00:10 -04:00
parent 65a0e11d3d
commit 2bf3faebae
21 changed files with 1103 additions and 104 deletions

2
.gitignore vendored
View File

@@ -17,7 +17,7 @@ m17_decoder/libcorrect
SDR++.app SDR++.app
android/deps android/deps
android/app/assets android/app/assets
source_modules/dragonlabs_source
source_modules/badgesdr_source source_modules/badgesdr_source
decoder_modules/mystery_decoder decoder_modules/mystery_decoder
decoder_modules/tetra_decoder decoder_modules/tetra_decoder
misc_modules/modulation_monitor

View File

@@ -14,6 +14,7 @@ option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies:
option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON) option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON)
option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF) option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF)
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF) option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
option(OPT_BUILD_DRAGONLABS_SOURCE "Build Dragon Labs Source Module (Dependencies: libdlcr)" OFF)
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON) option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF) option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF)
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON) option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
@@ -143,6 +144,10 @@ if (OPT_BUILD_BLADERF_SOURCE)
add_subdirectory("source_modules/bladerf_source") add_subdirectory("source_modules/bladerf_source")
endif (OPT_BUILD_BLADERF_SOURCE) endif (OPT_BUILD_BLADERF_SOURCE)
if (OPT_BUILD_DRAGONLABS_SOURCE)
add_subdirectory("source_modules/dragonlabs_source")
endif (OPT_BUILD_DRAGONLABS_SOURCE)
if (OPT_BUILD_FILE_SOURCE) if (OPT_BUILD_FILE_SOURCE)
add_subdirectory("source_modules/file_source") add_subdirectory("source_modules/file_source")
endif (OPT_BUILD_FILE_SOURCE) endif (OPT_BUILD_FILE_SOURCE)

View File

@@ -163,10 +163,10 @@ namespace dsp {
private: private:
Processor<T, T>* blockBefore(Processor<T, T>* block) { Processor<T, T>* blockBefore(Processor<T, T>* block) {
// TODO: This is wrong and must be fixed when I get more time Processor<T, T>* prev = NULL;
for (auto& ln : links) { for (auto& ln : links) {
if (ln == block) { return NULL; } if (ln == block) { return prev; }
if (states[ln]) { return ln; } if (states[ln]) { prev = ln; }
} }
return NULL; return NULL;
} }

View File

@@ -22,18 +22,17 @@ namespace dsp::demod {
dsp::taps::free(filterTaps); dsp::taps::free(filterTaps);
} }
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass, bool highPass) { void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) {
_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);
loadDummyTaps(); loadDummyTaps();
fir.init(NULL, filterTaps); fir.init(NULL, filterTaps);
// Initialize taps // Initialize taps
updateFilter(lowPass, highPass); updateFilter(lowPass);
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
demod.out.free(); demod.out.free();
@@ -59,19 +58,13 @@ namespace dsp::demod {
if (bandwidth == _bandwidth) { return; } if (bandwidth == _bandwidth) { return; }
_bandwidth = bandwidth; _bandwidth = bandwidth;
demod.setDeviation(_bandwidth / 2.0, _samplerate); demod.setDeviation(_bandwidth / 2.0, _samplerate);
updateFilter(_lowPass, _highPass); updateFilter(_lowPass);
} }
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);
updateFilter(lowPass, _highPass); updateFilter(lowPass);
}
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() {
@@ -86,14 +79,14 @@ namespace dsp::demod {
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 (filtering) { if (_lowPass) {
std::lock_guard<std::mutex> lck(filterMtx); std::lock_guard<std::mutex> lck(filterMtx);
fir.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 (filtering) { if (_lowPass) {
std::lock_guard<std::mutex> lck(filterMtx); std::lock_guard<std::mutex> lck(filterMtx);
fir.process(count, demod.out.writeBuf, demod.out.writeBuf); fir.process(count, demod.out.writeBuf, demod.out.writeBuf);
} }
@@ -114,25 +107,17 @@ namespace dsp::demod {
} }
private: private:
void updateFilter(bool lowPass, bool highPass) { void updateFilter(bool lowPass) {
std::lock_guard<std::mutex> lck(filterMtx); std::lock_guard<std::mutex> lck(filterMtx);
// Update values // Update values
_lowPass = lowPass; _lowPass = lowPass;
_highPass = highPass;
filtering = (lowPass || highPass);
// Free filter taps // Free filter taps
dsp::taps::free(filterTaps); dsp::taps::free(filterTaps);
// Generate filter depending on low and high pass settings // Generate filter depending on the low pass settings
if (_lowPass && _highPass) { if (_lowPass) {
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); filterTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
} }
else { else {
@@ -152,7 +137,6 @@ namespace dsp::demod {
double _samplerate; double _samplerate;
double _bandwidth; double _bandwidth;
bool _lowPass; bool _lowPass;
bool _highPass;
bool filtering; bool filtering;
Quadrature demod; Quadrature demod;

View File

@@ -0,0 +1,303 @@
#pragma once
#include "../channel/rx_vfo.h"
#include "../demod/quadrature.h"
#include "../filter/fir.h"
#include "../taps/high_pass.h"
#include <fftw3.h>
#include <map>
#include <atomic>
#define CTCSS_DECODE_SAMPLERATE 500//250.0
#define CTCSS_DECODE_BANDWIDTH 200.0
#define CTCSS_DECODE_OFFSET 160.55
namespace dsp::noise_reduction {
enum CTCSSTone {
/**
* Indicates that any valid tone will let audio through.
*/
CTCSS_TONE_ANY = -2,
/**
* Indicates that no tone is being received, or to act as a decoder only, letting audio through continuously.
*/
CTCSS_TONE_NONE = -1,
/**
* CTCSS Tone Frequency.
*/
CTCSS_TONE_67Hz,
CTCSS_TONE_69_3Hz,
CTCSS_TONE_71_9Hz,
CTCSS_TONE_74_4Hz,
CTCSS_TONE_77Hz,
CTCSS_TONE_79_7Hz,
CTCSS_TONE_82_5Hz,
CTCSS_TONE_85_4Hz,
CTCSS_TONE_88_5Hz,
CTCSS_TONE_91_5Hz,
CTCSS_TONE_94_8Hz,
CTCSS_TONE_97_4Hz,
CTCSS_TONE_100Hz,
CTCSS_TONE_103_5Hz,
CTCSS_TONE_107_2Hz,
CTCSS_TONE_110_9Hz,
CTCSS_TONE_114_8Hz,
CTCSS_TONE_118_8Hz,
CTCSS_TONE_123Hz,
CTCSS_TONE_127_3Hz,
CTCSS_TONE_131_8Hz,
CTCSS_TONE_136_5Hz,
CTCSS_TONE_141_3Hz,
CTCSS_TONE_146_2Hz,
CTCSS_TONE_150Hz,
CTCSS_TONE_151_4Hz,
CTCSS_TONE_156_7Hz,
CTCSS_TONE_159_8Hz,
CTCSS_TONE_162_2Hz,
CTCSS_TONE_165_5Hz,
CTCSS_TONE_167_9Hz,
CTCSS_TONE_171_3Hz,
CTCSS_TONE_173_8Hz,
CTCSS_TONE_177_3Hz,
CTCSS_TONE_179_9Hz,
CTCSS_TONE_183_5Hz,
CTCSS_TONE_186_2Hz,
CTCSS_TONE_189_9Hz,
CTCSS_TONE_192_8Hz,
CTCSS_TONE_196_6Hz,
CTCSS_TONE_199_5Hz,
CTCSS_TONE_203_5Hz,
CTCSS_TONE_206_5Hz,
CTCSS_TONE_210_7Hz,
CTCSS_TONE_218_1Hz,
CTCSS_TONE_225_7Hz,
CTCSS_TONE_229_1Hz,
CTCSS_TONE_233_6Hz,
CTCSS_TONE_241_8Hz,
CTCSS_TONE_250_3Hz,
CTCSS_TONE_254_1Hz,
_CTCSS_TONE_COUNT
};
const float CTCSS_TONES[_CTCSS_TONE_COUNT] = {
67.0f,
69.3f,
71.9f,
74.4f,
77.0f,
79.7f,
82.5f,
85.4f,
88.5f,
91.5f,
94.8f,
97.4f,
100.0f,
103.5f,
107.2f,
110.9f,
114.8f,
118.8f,
123.0f,
127.3f,
131.8f,
136.5f,
141.3f,
146.2f,
150.0f,
151.4f,
156.7f,
159.8f,
162.2f,
165.5f,
167.9f,
171.3f,
173.8f,
177.3f,
179.9f,
183.5f,
186.2f,
189.9f,
192.8f,
196.6f,
199.5f,
203.5f,
206.5f,
210.7f,
218.1f,
225.7f,
229.1f,
233.6f,
241.8f,
250.3f,
254.1f
};
class CTCSSSquelch : public Processor<stereo_t, stereo_t> {
using base_type = Processor<stereo_t, stereo_t>;
public:
CTCSSSquelch() {}
CTCSSSquelch(stream<stereo_t>* in, double samplerate) { init(in, samplerate); }
~CTCSSSquelch() {
// If not initialized, do nothing
if (!base_type::_block_init) { return; }
// Stop the DSP thread
base_type::stop();
}
void init(stream<stereo_t>* in, double samplerate) {
// Save settings
_samplerate = samplerate;
// Create dummy taps just for initialization
float dummy[1] = { 1.0f };
auto dummyTaps = dsp::taps::fromArray(1, dummy);
// Initialize the DDC and FM demod
ddc.init(NULL, samplerate, CTCSS_DECODE_SAMPLERATE, CTCSS_DECODE_BANDWIDTH, CTCSS_DECODE_OFFSET);
fm.init(NULL, 1.0, CTCSS_DECODE_SAMPLERATE);
// Initilize the base block class
base_type::init(in);
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_samplerate = samplerate;
ddc.setInSamplerate(samplerate);
base_type::tempStart();
}
void setRequiredTone(CTCSSTone tone) {
assert(base_type::_block_init);
requiredTone = tone;
}
CTCSSTone getCurrentTone() {
assert(base_type::_block_init);
return currentTone;
}
inline int process(int count, const stereo_t* in, stereo_t* out) {
// Shift and resample to the correct samplerate
int ddcOutCount = ddc.process(count, (complex_t*)in, ddc.out.writeBuf);
// FM Demod the CTCSS tone
fm.process(ddcOutCount, ddc.out.writeBuf, fm.out.writeBuf);
// Get the required tone
const CTCSSTone rtone = requiredTone;
// Detect the tone frequency
for (int i = 0; i < ddcOutCount; i++) {
// Compute the running mean
const float val = fm.out.writeBuf[i];
mean = 0.95f*mean + 0.05f*val;
// Compute the running variance
const float err = val - mean;
var = 0.95f*var + 0.05f*err*err;
// Run a schmitt trigger on the variance
bool nvarOk = varOk ? (var < 1100.0f) : (var < 1000.0f);
// Check if the tone has to be rematched
if (nvarOk && (!varOk || mean < minFreq || mean > maxFreq)) {
// Compute the absolute frequency
float freq = mean + CTCSS_DECODE_OFFSET;
// Check it against the known tones
if (freq < CTCSS_TONES[0] - 2.5) {
currentTone = CTCSS_TONE_NONE;
}
else if (freq > CTCSS_TONES[_CTCSS_TONE_COUNT-1] + 2.5) {
currentTone = CTCSS_TONE_NONE;
}
else if (freq < CTCSS_TONES[0]) {
currentTone = (CTCSSTone)0;
}
else if (freq > CTCSS_TONES[_CTCSS_TONE_COUNT-1]) {
currentTone = (CTCSSTone)(_CTCSS_TONE_COUNT-1);
}
else {
int a = 0;
int b = _CTCSS_TONE_COUNT-1;
while (b - a > 1) {
int c = (a + b) >> 1;
((CTCSS_TONES[c] < freq) ? a : b) = c;
}
currentTone = (CTCSSTone)((freq - CTCSS_TONES[a] < CTCSS_TONES[b] - freq) ? a : b);
}
// Update the mute status
mute = !(currentTone == rtone || (currentTone != CTCSS_TONE_NONE && rtone == CTCSS_TONE_ANY));
// Unmuted the audio if needed
// TODO
// Recompute min and max freq if a valid tone is detected
if (currentTone != CTCSS_TONE_NONE) {
float c = CTCSS_TONES[currentTone];
float l = (currentTone > CTCSS_TONE_67Hz) ? CTCSS_TONES[currentTone - 1] : c - 2.5f;
float r = (currentTone < CTCSS_TONE_254_1Hz) ? CTCSS_TONES[currentTone + 1] : c + 2.5f;
minFreq = (l+c) / 2.0f;
maxFreq = (r+c) / 2.0f;
}
}
// Check for a rising edge on the variance
if (!nvarOk && varOk) {
// Mute the audio
// TODO
mute = true;
currentTone = CTCSS_TONE_NONE;
}
// Save the new variance state
varOk = nvarOk;
}
// DEBUG ONLY
if ((rtone != CTCSS_TONE_NONE) && mute) {
memset(out, 0, count * sizeof(stereo_t));
}
else {
memcpy(out, in, count * sizeof(stereo_t));
}
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);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
private:
double _samplerate;
std::atomic<CTCSSTone> requiredTone = CTCSS_TONE_ANY;
float mean = 0.0f;
float var = 0.0f;
bool varOk = false;
float minFreq = 0.0f;
float maxFreq = 0.0f;
bool mute = true;
std::atomic<CTCSSTone> currentTone = CTCSS_TONE_NONE;
channel::RxVFO ddc;
demod::Quadrature fm;
};
}

View File

@@ -3,14 +3,14 @@
// TODO: Rewrite better!!!!! // TODO: Rewrite better!!!!!
namespace dsp::noise_reduction { namespace dsp::noise_reduction {
class Squelch : public Processor<complex_t, complex_t> { class PowerSquelch : public Processor<complex_t, complex_t> {
using base_type = Processor<complex_t, complex_t>; using base_type = Processor<complex_t, complex_t>;
public: public:
Squelch() {} PowerSquelch() {}
Squelch(stream<complex_t>* in, double level) {} PowerSquelch(stream<complex_t>* in, double level) {}
~Squelch() { ~PowerSquelch() {
if (!base_type::_block_init) { return; } if (!base_type::_block_init) { return; }
base_type::stop(); base_type::stop();
buffer::free(normBuffer); buffer::free(normBuffer);
@@ -31,8 +31,11 @@ namespace dsp::noise_reduction {
} }
inline int process(int count, const complex_t* in, complex_t* out) { inline int process(int count, const complex_t* in, complex_t* out) {
float sum; // Compute the amplitude of each sample
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)in, count); volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)in, count);
// Compute the mean amplitude
float sum = 0.0f;
volk_32f_accumulator_s32f(&sum, normBuffer, count); volk_32f_accumulator_s32f(&sum, normBuffer, count);
sum /= (float)count; sum /= (float)count;
@@ -46,8 +49,6 @@ namespace dsp::noise_reduction {
return count; return count;
} }
//DEFAULT_PROC_RUN();
int run() { int run() {
int count = base_type::_in->read(); int count = base_type::_in->read();
if (count < 0) { return -1; } if (count < 0) { return -1; }

View File

@@ -85,8 +85,8 @@ void SourceManager::tune(double freq) {
return; return;
} }
// TODO: No need to always retune the hardware in Panadapter mode // TODO: No need to always retune the hardware in Panadapter mode
selectedHandler->tuneHandler(abs(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset), selectedHandler->ctx); selectedHandler->tuneHandler(abs(((tuneMode == TuningMode::NORMAL) ? (freq + tuneOffset) : ifFreq)), selectedHandler->ctx);
onRetune.emit(freq); onRetune.emit(freq + tuneOffset);
currentFreq = freq; currentFreq = freq;
} }

View File

@@ -1,3 +1,3 @@
#pragma once #pragma once
#define VERSION_STR "1.2.1" #define VERSION_STR "1.3.0"

View File

@@ -20,6 +20,16 @@ enum IFNRPreset {
IFNR_PRESET_BROADCAST IFNR_PRESET_BROADCAST
}; };
enum SquelchMode {
SQUELCH_MODE_OFF,
SQUELCH_MODE_POWER,
SQUELCH_MODE_SNR,
SQUELCH_MODE_CTCSS_MUTE,
SQUELCH_MODE_CTCSS_DECODE,
SQUELCH_MODE_DCS_MUTE,
SQUELCH_MODE_DCS_DECODE,
};
namespace demod { namespace demod {
class Demodulator { class Demodulator {
public: public:
@@ -45,6 +55,8 @@ 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 getHighPassAllowed() = 0;
virtual bool getSquelchAllowed() = 0;
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0; virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
}; };
} }

View File

@@ -40,6 +40,12 @@ namespace demod {
void showMenu() { void showMenu() {
float menuWidth = ImGui::GetContentRegionAvail().x; float menuWidth = ImGui::GetContentRegionAvail().x;
if (ImGui::Checkbox(("Carrier AGC##_radio_am_carrier_agc_" + name).c_str(), &carrierAgc)) {
demod.setAGCMode(carrierAgc ? dsp::demod::AM<dsp::stereo_t>::AGCMode::CARRIER : dsp::demod::AM<dsp::stereo_t>::AGCMode::AUDIO);
_config->acquire();
_config->conf[name][getName()]["carrierAgc"] = carrierAgc;
_config->release(true);
}
ImGui::LeftLabel("AGC Attack"); ImGui::LeftLabel("AGC Attack");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_am_agc_attack_" + name).c_str(), &agcAttack, 1.0f, 200.0f)) { if (ImGui::SliderFloat(("##_radio_am_agc_attack_" + name).c_str(), &agcAttack, 1.0f, 200.0f)) {
@@ -56,12 +62,6 @@ namespace demod {
_config->conf[name][getName()]["agcDecay"] = agcDecay; _config->conf[name][getName()]["agcDecay"] = agcDecay;
_config->release(true); _config->release(true);
} }
if (ImGui::Checkbox(("Carrier AGC##_radio_am_carrier_agc_" + name).c_str(), &carrierAgc)) {
demod.setAGCMode(carrierAgc ? dsp::demod::AM<dsp::stereo_t>::AGCMode::CARRIER : dsp::demod::AM<dsp::stereo_t>::AGCMode::AUDIO);
_config->acquire();
_config->conf[name][getName()]["carrierAgc"] = carrierAgc;
_config->release(true);
}
} }
void setBandwidth(double bandwidth) { demod.setBandwidth(bandwidth); } void setBandwidth(double bandwidth) { demod.setBandwidth(bandwidth); }
@@ -86,6 +86,8 @@ 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 getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
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,8 @@ 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 getHighPassAllowed() { return false; }
bool getSquelchAllowed() { 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,8 @@ 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 getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
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,8 @@ 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 getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@@ -22,14 +22,11 @@ namespace demod {
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"];
} }
if (config->conf[name][getName()].contains("highPass")) {
_highPass = config->conf[name][getName()]["highPass"];
}
_config->release(); _config->release();
// Define structure // Define structure
demod.init(input, getIFSampleRate(), bandwidth, _lowPass, _highPass); demod.init(input, getIFSampleRate(), bandwidth, _lowPass);
} }
void start() { demod.start(); } void start() { demod.start(); }
@@ -43,12 +40,6 @@ 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) {
@@ -75,6 +66,8 @@ 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 getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:
@@ -83,7 +76,6 @@ 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,8 @@ 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 getHighPassAllowed() { return false; }
bool getSquelchAllowed() { 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,8 @@ 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 getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@@ -100,18 +100,18 @@ namespace demod {
} }
void showMenu() { void showMenu() {
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + name).c_str(), &_stereo)) {
setStereo(_stereo);
_config->acquire();
_config->conf[name][getName()]["stereo"] = _stereo;
_config->release(true);
}
if (ImGui::Checkbox(("Low Pass##_radio_wfm_lowpass_" + name).c_str(), &_lowPass)) { if (ImGui::Checkbox(("Low Pass##_radio_wfm_lowpass_" + name).c_str(), &_lowPass)) {
demod.setLowPass(_lowPass); demod.setLowPass(_lowPass);
_config->acquire(); _config->acquire();
_config->conf[name][getName()]["lowPass"] = _lowPass; _config->conf[name][getName()]["lowPass"] = _lowPass;
_config->release(true); _config->release(true);
} }
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + name).c_str(), &_stereo)) {
setStereo(_stereo);
_config->acquire();
_config->conf[name][getName()]["stereo"] = _stereo;
_config->release(true);
}
if (ImGui::Checkbox(("Decode RDS##_radio_wfm_rds_" + name).c_str(), &_rds)) { if (ImGui::Checkbox(("Decode RDS##_radio_wfm_rds_" + name).c_str(), &_rds)) {
demod.setRDSOut(_rds); demod.setRDSOut(_rds);
_config->acquire(); _config->acquire();
@@ -270,6 +270,8 @@ 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 getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
// ============= DEDICATED FUNCTIONS ============= // ============= DEDICATED FUNCTIONS =============

View File

@@ -5,10 +5,14 @@ enum {
RADIO_IFACE_CMD_SET_MODE, RADIO_IFACE_CMD_SET_MODE,
RADIO_IFACE_CMD_GET_BANDWIDTH, RADIO_IFACE_CMD_GET_BANDWIDTH,
RADIO_IFACE_CMD_SET_BANDWIDTH, RADIO_IFACE_CMD_SET_BANDWIDTH,
RADIO_IFACE_CMD_GET_SQUELCH_ENABLED, RADIO_IFACE_CMD_GET_SQUELCH_MODE,
RADIO_IFACE_CMD_SET_SQUELCH_ENABLED, RADIO_IFACE_CMD_SET_SQUELCH_MODE,
RADIO_IFACE_CMD_GET_SQUELCH_LEVEL, RADIO_IFACE_CMD_GET_SQUELCH_LEVEL,
RADIO_IFACE_CMD_SET_SQUELCH_LEVEL, RADIO_IFACE_CMD_SET_SQUELCH_LEVEL,
RADIO_IFACE_CMD_GET_CTCSS_TONE,
RADIO_IFACE_CMD_SET_CTCSS_TONE,
RADIO_IFACE_CMD_GET_HIGHPASS,
RADIO_IFACE_CMD_SET_HIGHPASS
}; };
enum { enum {

View File

@@ -8,7 +8,8 @@
#include <dsp/chain.h> #include <dsp/chain.h>
#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/power_squelch.h>
#include <dsp/noise_reduction/ctcss_squelch.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>
@@ -49,6 +50,22 @@ public:
ifnrPresets.define("Voice", IFNR_PRESET_VOICE); ifnrPresets.define("Voice", IFNR_PRESET_VOICE);
ifnrPresets.define("Narrow Band", IFNR_PRESET_NARROW_BAND); ifnrPresets.define("Narrow Band", IFNR_PRESET_NARROW_BAND);
squelchModes.define("off", "Off", SQUELCH_MODE_OFF);
squelchModes.define("power", "Power", SQUELCH_MODE_POWER);
//squelchModes.define("snr", "SNR", SQUELCH_MODE_SNR);
squelchModes.define("ctcss_mute", "CTCSS (Mute)", SQUELCH_MODE_CTCSS_MUTE);
squelchModes.define("ctcss_decode", "CTCSS (Decode Only)", SQUELCH_MODE_CTCSS_DECODE);
//squelchModes.define("dcs_mute", "DCS (Mute)", SQUELCH_MODE_DCS_MUTE);
//squelchModes.define("dcs_decode", "DCS (Decode Only)", SQUELCH_MODE_DCS_DECODE);
for (int i = 0; i < dsp::noise_reduction::_CTCSS_TONE_COUNT; i++) {
float tone = dsp::noise_reduction::CTCSS_TONES[i];
char buf[64];
sprintf(buf, "%.1fHz", tone);
ctcssTones.define((int)round(tone) * 10, buf, (dsp::noise_reduction::CTCSSTone)i);
}
ctcssTones.define(-1, "Any", dsp::noise_reduction::CTCSS_TONE_ANY);
// Initialize the config if it doesn't exist // Initialize the config if it doesn't exist
bool created = false; bool created = false;
config.acquire(); config.acquire();
@@ -72,19 +89,24 @@ public:
nb.init(NULL, 500.0 / 24000.0, 10.0); nb.init(NULL, 500.0 / 24000.0, 10.0);
fmnr.init(NULL, 32); fmnr.init(NULL, 32);
squelch.init(NULL, MIN_SQUELCH); powerSquelch.init(NULL, MIN_SQUELCH);
ifChain.addBlock(&nb, false); ifChain.addBlock(&nb, false);
ifChain.addBlock(&squelch, false); ifChain.addBlock(&powerSquelch, false);
ifChain.addBlock(&fmnr, false); ifChain.addBlock(&fmnr, false);
// Initialize audio DSP chain // Initialize audio DSP chain
afChain.init(&dummyAudioStream); afChain.init(&dummyAudioStream);
ctcss.init(NULL, 50000.0);
resamp.init(NULL, 250000.0, 48000.0); resamp.init(NULL, 250000.0, 48000.0);
hpTaps = dsp::taps::highPass(300.0, 100.0, 48000.0);
hpf.init(NULL, hpTaps);
deemp.init(NULL, 50e-6, 48000.0); deemp.init(NULL, 50e-6, 48000.0);
afChain.addBlock(&ctcss, false);
afChain.addBlock(&resamp, true); afChain.addBlock(&resamp, true);
afChain.addBlock(&hpf, false);
afChain.addBlock(&deemp, false); afChain.addBlock(&deemp, false);
// Initialize the sink // Initialize the sink
@@ -233,6 +255,33 @@ private:
} }
} }
// Squelch
if (_this->squelchAllowed) {
ImGui::LeftLabel("Squelch Mode");
ImGui::FillWidth();
if (ImGui::Combo(("##_radio_sqelch_mode_" + _this->name).c_str(), &_this->squelchModeId, _this->squelchModes.txt)) {
_this->setSquelchMode(_this->squelchModes[_this->squelchModeId]);
}
switch (_this->squelchModes[_this->squelchModeId]) {
case SQUELCH_MODE_POWER:
ImGui::LeftLabel("Squelch Level");
ImGui::FillWidth();
if (ImGui::SliderFloat(("##_radio_sqelch_lvl_" + _this->name).c_str(), &_this->squelchLevel, _this->MIN_SQUELCH, _this->MAX_SQUELCH, "%.3fdB")) {
_this->setSquelchLevel(_this->squelchLevel);
}
break;
case SQUELCH_MODE_CTCSS_MUTE:
if (_this->squelchModes[_this->squelchModeId] == SQUELCH_MODE_CTCSS_MUTE) {
ImGui::LeftLabel("CTCSS Tone");
ImGui::FillWidth();
if (ImGui::Combo(("##_radio_ctcss_tone_" + _this->name).c_str(), &_this->ctcssToneId, _this->ctcssTones.txt)) {
_this->setCTCSSTone(_this->ctcssTones[_this->ctcssToneId]);
}
}
}
}
// Noise blanker // Noise blanker
if (_this->nbAllowed) { if (_this->nbAllowed) {
if (ImGui::Checkbox(("Noise blanker (W.I.P.)##_radio_nb_ena_" + _this->name).c_str(), &_this->nbEnabled)) { if (ImGui::Checkbox(("Noise blanker (W.I.P.)##_radio_nb_ena_" + _this->name).c_str(), &_this->nbEnabled)) {
@@ -247,19 +296,6 @@ private:
if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); } if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); }
} }
// Squelch
if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) {
_this->setSquelchEnabled(_this->squelchEnabled);
}
if (!_this->squelchEnabled && _this->enabled) { style::beginDisabled(); }
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_sqelch_lvl_" + _this->name).c_str(), &_this->squelchLevel, _this->MIN_SQUELCH, _this->MAX_SQUELCH, "%.3fdB")) {
_this->setSquelchLevel(_this->squelchLevel);
}
if (!_this->squelchEnabled && _this->enabled) { style::endDisabled(); }
// FM IF Noise Reduction // FM IF Noise Reduction
if (_this->FMIFNRAllowed) { if (_this->FMIFNRAllowed) {
if (ImGui::Checkbox(("IF Noise Reduction##_radio_fmifnr_ena_" + _this->name).c_str(), &_this->FMIFNREnabled)) { if (ImGui::Checkbox(("IF Noise Reduction##_radio_fmifnr_ena_" + _this->name).c_str(), &_this->FMIFNREnabled)) {
@@ -276,9 +312,53 @@ private:
} }
} }
// High pass
if (_this->highPassAllowed) {
if (ImGui::Checkbox(("High Pass##_radio_hpf_" + _this->name).c_str(), &_this->highPass)) {
_this->setHighPass(_this->highPass);
}
}
// Demodulator specific menu // Demodulator specific menu
_this->selectedDemod->showMenu(); _this->selectedDemod->showMenu();
// Display the squelch diagnostics
switch (_this->squelchModes[_this->squelchModeId]) {
case SQUELCH_MODE_CTCSS_MUTE:
ImGui::TextUnformatted("Received Tone:");
ImGui::SameLine();
{
auto ctone = _this->ctcss.getCurrentTone();
auto dtone = _this->ctcssTones[_this->ctcssToneId];
if (ctone != dsp::noise_reduction::CTCSS_TONE_NONE) {
if (dtone == dsp::noise_reduction::CTCSS_TONE_ANY || ctone == dtone) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
}
else {
ImGui::TextColored(ImVec4(1, 0, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
}
}
else {
ImGui::TextUnformatted("None");
}
}
break;
case SQUELCH_MODE_CTCSS_DECODE:
ImGui::TextUnformatted("Received Tone:");
ImGui::SameLine();
{
auto ctone = _this->ctcss.getCurrentTone();
if (ctone != dsp::noise_reduction::CTCSS_TONE_NONE) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
}
else {
ImGui::TextUnformatted("None");
}
}
break;
}
if (!_this->enabled) { style::endDisabled(); } if (!_this->enabled) { style::endDisabled(); }
} }
@@ -287,13 +367,13 @@ private:
switch (id) { switch (id) {
case DemodID::RADIO_DEMOD_NFM: demod = new demod::NFM(); break; case DemodID::RADIO_DEMOD_NFM: demod = new demod::NFM(); break;
case DemodID::RADIO_DEMOD_WFM: demod = new demod::WFM(); break; case DemodID::RADIO_DEMOD_WFM: demod = new demod::WFM(); break;
case DemodID::RADIO_DEMOD_AM: demod = new demod::AM(); break; case DemodID::RADIO_DEMOD_AM: demod = new demod::AM(); break;
case DemodID::RADIO_DEMOD_DSB: demod = new demod::DSB(); break; case DemodID::RADIO_DEMOD_DSB: demod = new demod::DSB(); break;
case DemodID::RADIO_DEMOD_USB: demod = new demod::USB(); break; case DemodID::RADIO_DEMOD_USB: demod = new demod::USB(); break;
case DemodID::RADIO_DEMOD_CW: demod = new demod::CW(); break; case DemodID::RADIO_DEMOD_CW: demod = new demod::CW(); break;
case DemodID::RADIO_DEMOD_LSB: demod = new demod::LSB(); break; case DemodID::RADIO_DEMOD_LSB: demod = new demod::LSB(); break;
case DemodID::RADIO_DEMOD_RAW: demod = new demod::RAW(); break; case DemodID::RADIO_DEMOD_RAW: demod = new demod::RAW(); break;
default: demod = NULL; break; default: demod = NULL; break;
} }
if (!demod) { return NULL; } if (!demod) { return NULL; }
@@ -360,15 +440,20 @@ private:
maxBandwidth = selectedDemod->getMaxBandwidth(); maxBandwidth = selectedDemod->getMaxBandwidth();
bandwidthLocked = selectedDemod->getBandwidthLocked(); bandwidthLocked = selectedDemod->getBandwidthLocked();
snapInterval = selectedDemod->getDefaultSnapInterval(); snapInterval = selectedDemod->getDefaultSnapInterval();
squelchLevel = MIN_SQUELCH;
deempAllowed = selectedDemod->getDeempAllowed(); deempAllowed = selectedDemod->getDeempAllowed();
deempId = deempModes.valueId((DeemphasisMode)selectedDemod->getDefaultDeemphasisMode()); deempId = deempModes.valueId((DeemphasisMode)selectedDemod->getDefaultDeemphasisMode());
squelchEnabled = false; squelchModeId = squelchModes.valueId(SQUELCH_MODE_OFF);
squelchLevel = MIN_SQUELCH;
ctcssToneId = ctcssTones.valueId(dsp::noise_reduction::CTCSS_TONE_67Hz);
highPass = false;
postProcEnabled = selectedDemod->getPostProcEnabled(); postProcEnabled = selectedDemod->getPostProcEnabled();
FMIFNRAllowed = selectedDemod->getFMIFNRAllowed(); FMIFNRAllowed = selectedDemod->getFMIFNRAllowed();
FMIFNREnabled = false; FMIFNREnabled = false;
fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE); fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE);
nbAllowed = selectedDemod->getNBAllowed(); nbAllowed = selectedDemod->getNBAllowed();
squelchAllowed = selectedDemod->getSquelchAllowed();
highPassAllowed = selectedDemod->getHighPassAllowed();
nbEnabled = false; nbEnabled = false;
nbLevel = 0.0f; nbLevel = 0.0f;
double ifSamplerate = selectedDemod->getIFSampleRate(); double ifSamplerate = selectedDemod->getIFSampleRate();
@@ -380,11 +465,24 @@ private:
if (config.conf[name][selectedDemod->getName()].contains("snapInterval")) { if (config.conf[name][selectedDemod->getName()].contains("snapInterval")) {
snapInterval = config.conf[name][selectedDemod->getName()]["snapInterval"]; snapInterval = config.conf[name][selectedDemod->getName()]["snapInterval"];
} }
if (config.conf[name][selectedDemod->getName()].contains("squelchMode")) {
std::string squelchModeStr = config.conf[name][selectedDemod->getName()]["squelchMode"];
if (squelchModes.keyExists(squelchModeStr)) {
squelchModeId = squelchModes.keyId(squelchModeStr);
}
}
if (config.conf[name][selectedDemod->getName()].contains("squelchLevel")) { if (config.conf[name][selectedDemod->getName()].contains("squelchLevel")) {
squelchLevel = config.conf[name][selectedDemod->getName()]["squelchLevel"]; squelchLevel = config.conf[name][selectedDemod->getName()]["squelchLevel"];
} }
if (config.conf[name][selectedDemod->getName()].contains("squelchEnabled")) { if (config.conf[name][selectedDemod->getName()].contains("ctcssTone")) {
squelchEnabled = config.conf[name][selectedDemod->getName()]["squelchEnabled"]; int ctcssToneX10 = config.conf[name][selectedDemod->getName()]["ctcssTone"];
if (ctcssTones.keyExists(ctcssToneX10)) {
ctcssToneId = ctcssTones.keyId(ctcssToneX10);
}
}
if (config.conf[name][selectedDemod->getName()].contains("highPass")) {
highPass = config.conf[name][selectedDemod->getName()]["highPass"];
} }
if (config.conf[name][selectedDemod->getName()].contains("deempMode")) { if (config.conf[name][selectedDemod->getName()].contains("deempMode")) {
if (!config.conf[name][selectedDemod->getName()]["deempMode"].is_string()) { if (!config.conf[name][selectedDemod->getName()]["deempMode"].is_string()) {
@@ -434,16 +532,22 @@ private:
setFMIFNREnabled(FMIFNRAllowed ? FMIFNREnabled : false); setFMIFNREnabled(FMIFNRAllowed ? FMIFNREnabled : false);
// Configure squelch // Configure squelch
setSquelchMode(squelchAllowed ? squelchModes[squelchModeId] : SQUELCH_MODE_OFF);
setSquelchLevel(squelchLevel); setSquelchLevel(squelchLevel);
setSquelchEnabled(squelchEnabled); setCTCSSTone(ctcssTones[ctcssToneId]);
// Configure AF chain // Configure AF chain
if (postProcEnabled) { if (postProcEnabled) {
// Configure resampler // Configure resampler
afChain.stop(); afChain.stop();
resamp.setInSamplerate(selectedDemod->getAFSampleRate()); double afsr = selectedDemod->getAFSampleRate();
setAudioSampleRate(audioSampleRate); ctcss.setSamplerate(afsr);
resamp.setInSamplerate(afsr);
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
setAudioSampleRate(audioSampleRate);
// Configure the HPF
setHighPass(highPass && highPassAllowed);
// Configure deemphasis // Configure deemphasis
setDeemphasisMode(deempModes[deempId]); setDeemphasisMode(deempModes[deempId]);
@@ -489,12 +593,32 @@ private:
// Configure resampler // Configure resampler
resamp.setOutSamplerate(audioSampleRate); resamp.setOutSamplerate(audioSampleRate);
// Configure the HPF sample rate
hpTaps = dsp::taps::highPass(300.0, 100.0, audioSampleRate);
hpf.setTaps(hpTaps);
// Configure deemphasis sample rate // Configure deemphasis sample rate
deemp.setSamplerate(audioSampleRate); deemp.setSamplerate(audioSampleRate);
afChain.start(); afChain.start();
} }
void setHighPass(bool enabled) {
// Update the state
highPass = enabled;
// Check if post-processing is enabled and that a demodulator is selected
if (!postProcEnabled || !selectedDemod) { return; }
// Set the state of the HPF in the AF chain
afChain.setBlockEnabled(&hpf, enabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Save config
config.acquire();
config.conf[name][selectedDemod->getName()]["highPass"] = enabled;
config.release(true);
}
void setDeemphasisMode(DeemphasisMode mode) { void setDeemphasisMode(DeemphasisMode mode) {
deempId = deempModes.valueId(mode); deempId = deempModes.valueId(mode);
if (!postProcEnabled || !selectedDemod) { return; } if (!postProcEnabled || !selectedDemod) { return; }
@@ -522,6 +646,7 @@ private:
void setNBLevel(float level) { void setNBLevel(float level) {
nbLevel = std::clamp<float>(level, MIN_NB, MAX_NB); nbLevel = std::clamp<float>(level, MIN_NB, MAX_NB);
nb.setLevel(nbLevel); nb.setLevel(nbLevel);
if (!selectedDemod) { return; }
// Save config // Save config
config.acquire(); config.acquire();
@@ -529,20 +654,59 @@ private:
config.release(true); config.release(true);
} }
void setSquelchEnabled(bool enable) { void setSquelchMode(SquelchMode mode) {
squelchEnabled = enable; squelchModeId = squelchModes.valueId(mode);
if (!selectedDemod) { return; } if (!selectedDemod) { return; }
ifChain.setBlockEnabled(&squelch, squelchEnabled, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
// Disable all squelch blocks
ifChain.disableBlock(&powerSquelch, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
afChain.disableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Enable the block depending on the mode
switch (mode) {
case SQUELCH_MODE_OFF:
break;
case SQUELCH_MODE_POWER:
// Enable the power squelch block
ifChain.enableBlock(&powerSquelch, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
break;
case SQUELCH_MODE_SNR:
// TODO
break;
case SQUELCH_MODE_CTCSS_MUTE:
// Set the required tone and enable the CTCSS squelch block
ctcss.setRequiredTone(ctcssTones[ctcssToneId]);
afChain.enableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
break;
case SQUELCH_MODE_CTCSS_DECODE:
// Set the required tone to none and enable the CTCSS squelch block
ctcss.setRequiredTone(dsp::noise_reduction::CTCSS_TONE_NONE);
afChain.enableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
break;
case SQUELCH_MODE_DCS_MUTE:
// TODO
break;
case SQUELCH_MODE_DCS_DECODE:
// TODO
break;
}
// Save config // Save config
config.acquire(); config.acquire();
config.conf[name][selectedDemod->getName()]["squelchEnabled"] = squelchEnabled; config.conf[name][selectedDemod->getName()]["squelchMode"] = squelchModes.key(squelchModeId);
config.release(true); config.release(true);
} }
void setSquelchLevel(float level) { void setSquelchLevel(float level) {
squelchLevel = std::clamp<float>(level, MIN_SQUELCH, MAX_SQUELCH); squelchLevel = std::clamp<float>(level, MIN_SQUELCH, MAX_SQUELCH);
squelch.setLevel(squelchLevel); powerSquelch.setLevel(squelchLevel);
if (!selectedDemod) { return; }
// Save config // Save config
config.acquire(); config.acquire();
@@ -550,6 +714,24 @@ private:
config.release(true); config.release(true);
} }
void setCTCSSTone(dsp::noise_reduction::CTCSSTone tone) {
// Check for an invalid value
if (tone == dsp::noise_reduction::CTCSS_TONE_NONE) { return; }
// If not in CTCSS mute mode, do nothing
if (squelchModes[squelchModeId] != SQUELCH_MODE_CTCSS_MUTE) { return; }
// Set the tone
ctcssToneId = ctcssTones.valueId(tone);
ctcss.setRequiredTone(tone);
if (!selectedDemod) { return; }
// Save config
config.acquire();
config.conf[name][selectedDemod->getName()]["ctcssTone"] = ctcssTones.key(ctcssToneId);
config.release(true);
}
void setFMIFNREnabled(bool enabled) { void setFMIFNREnabled(bool enabled) {
FMIFNREnabled = enabled; FMIFNREnabled = enabled;
if (!selectedDemod) { return; } if (!selectedDemod) { return; }
@@ -619,13 +801,13 @@ private:
if (_this->bandwidthLocked) { return; } if (_this->bandwidthLocked) { return; }
_this->setBandwidth(*_in); _this->setBandwidth(*_in);
} }
else if (code == RADIO_IFACE_CMD_GET_SQUELCH_ENABLED && out) { else if (code == RADIO_IFACE_CMD_GET_SQUELCH_MODE && out) {
bool* _out = (bool*)out; SquelchMode* _out = (SquelchMode*)out;
*_out = _this->squelchEnabled; *_out = _this->squelchModes[_this->squelchModeId];
} }
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in && _this->enabled) { else if (code == RADIO_IFACE_CMD_SET_SQUELCH_MODE && in && _this->enabled) {
bool* _in = (bool*)in; SquelchMode* _in = (SquelchMode*)in;
_this->setSquelchEnabled(*_in); _this->setSquelchMode(*_in);
} }
else if (code == RADIO_IFACE_CMD_GET_SQUELCH_LEVEL && out) { else if (code == RADIO_IFACE_CMD_GET_SQUELCH_LEVEL && out) {
float* _out = (float*)out; float* _out = (float*)out;
@@ -635,6 +817,22 @@ private:
float* _in = (float*)in; float* _in = (float*)in;
_this->setSquelchLevel(*_in); _this->setSquelchLevel(*_in);
} }
else if (code == RADIO_IFACE_CMD_GET_CTCSS_TONE && out) {
dsp::noise_reduction::CTCSSTone* _out = (dsp::noise_reduction::CTCSSTone*)out;
*_out = _this->ctcssTones[_this->ctcssToneId];
}
else if (code == RADIO_IFACE_CMD_SET_CTCSS_TONE && in && _this->enabled) {
dsp::noise_reduction::CTCSSTone* _in = (dsp::noise_reduction::CTCSSTone*)in;
_this->setCTCSSTone(*_in);
}
else if (code == RADIO_IFACE_CMD_GET_HIGHPASS && out) {
bool* _out = (bool*)out;
*_out = _this->highPass;
}
else if (code == RADIO_IFACE_CMD_SET_HIGHPASS && in && _this->enabled) {
bool* _in = (bool*)in;
_this->setHighPass(*_in);
}
else { else {
return; return;
} }
@@ -655,12 +853,15 @@ private:
dsp::chain<dsp::complex_t> ifChain; dsp::chain<dsp::complex_t> ifChain;
dsp::noise_reduction::NoiseBlanker nb; dsp::noise_reduction::NoiseBlanker nb;
dsp::noise_reduction::FMIF fmnr; dsp::noise_reduction::FMIF fmnr;
dsp::noise_reduction::Squelch squelch; dsp::noise_reduction::PowerSquelch powerSquelch;
// Audio chain // Audio chain
dsp::stream<dsp::stereo_t> dummyAudioStream; dsp::stream<dsp::stereo_t> dummyAudioStream;
dsp::chain<dsp::stereo_t> afChain; dsp::chain<dsp::stereo_t> afChain;
dsp::noise_reduction::CTCSSSquelch ctcss;
dsp::multirate::RationalResampler<dsp::stereo_t> resamp; dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
dsp::tap<float> hpTaps;
dsp::filter::FIR<dsp::stereo_t, float> hpf;
dsp::filter::Deemphasis<dsp::stereo_t> deemp; dsp::filter::Deemphasis<dsp::stereo_t> deemp;
SinkManager::Stream stream; SinkManager::Stream stream;
@@ -669,6 +870,8 @@ private:
OptionList<std::string, DeemphasisMode> deempModes; OptionList<std::string, DeemphasisMode> deempModes;
OptionList<std::string, IFNRPreset> ifnrPresets; OptionList<std::string, IFNRPreset> ifnrPresets;
OptionList<std::string, SquelchMode> squelchModes;
OptionList<int, dsp::noise_reduction::CTCSSTone> ctcssTones;
double audioSampleRate = 48000.0; double audioSampleRate = 48000.0;
float minBandwidth; float minBandwidth;
@@ -679,8 +882,13 @@ private:
int selectedDemodID = 1; int selectedDemodID = 1;
bool postProcEnabled; bool postProcEnabled;
bool squelchEnabled = false; int squelchModeId = 0;
float squelchLevel; float squelchLevel;
int ctcssToneId = 0;
bool squelchAllowed = false;
bool highPass = false;
bool highPassAllowed = false;
int deempId = 0; int deempId = 0;
bool deempAllowed; bool deempAllowed;

View File

@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.13)
project(dragonlabs_source)
file(GLOB SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})
if (MSVC)
# Debugging only
find_package(PkgConfig REQUIRED)
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
target_link_libraries(dragonlabs_source PRIVATE PkgConfig::libusb)
target_include_directories(dragonlabs_source PRIVATE "C:/Users/ryzerth/Documents/DragonLabs/products/KrakenSlayer/host/src")
target_include_directories(dragonlabs_source PRIVATE "C:/Program Files/DragonLabs/CR/include")
target_link_directories(dragonlabs_source PRIVATE "C:/Program Files/DragonLabs/CR/lib")
target_link_libraries(dragonlabs_source PRIVATE dlcr)
elseif (ANDROID)
# Not supported yet...
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBDLCR REQUIRED libdlcr)
target_include_directories(dragonlabs_source PRIVATE ${LIBDLCR_INCLUDE_DIRS})
target_link_directories(dragonlabs_source PRIVATE ${LIBDLCR_LIBRARY_DIRS})
target_link_libraries(dragonlabs_source PRIVATE ${LIBDLCR_LIBRARIES})
endif ()

View File

@@ -0,0 +1,448 @@
#include <imgui.h>
#include <module.h>
#include <gui/gui.h>
#include <gui/smgui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <utils/optionlist.h>
#include <fstream>
#include <dlcr.h>
#include <dlcr_internal.h>
#include <lmx2572.h>
SDRPP_MOD_INFO{
/* Name: */ "dragonlabs_source",
/* Description: */ "Dragon Labs Source Module",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
#define CONCAT(a, b) ((std::string(a) + b).c_str())
class DragonLabsSourceModule : public ModuleManager::Instance {
public:
DragonLabsSourceModule(std::string name) {
this->name = name;
// Load the register debugging values
strcpy(clkRegStr, "00");
strcpy(clkValStr, "--");
strcpy(synRegStr, "00");
strcpy(synValStr, "--");
strcpy(adcRegStr, "00");
strcpy(adcValStr, "--");
strcpy(tunRegStr, "00");
strcpy(tunValStr, "--");
// Define the clock sources
clockSources.define("internal", "Internal", DLCR_CLOCK_INTERNAL);
clockSources.define("external", "External", DLCR_CLOCK_EXTERNAL);
// Define the channels
channels.define("chan1", "Channel 1", 0);
channels.define("chan2", "Channel 2", 1);
channels.define("chan3", "Channel 3", 2);
channels.define("chan4", "Channel 4", 3);
channels.define("chan5", "Channel 5", 4);
channels.define("chan6", "Channel 6", 5);
channels.define("chan7", "Channel 7", 6);
channels.define("chan8", "Channel 8", 7);
// Hardcode the samplerate
sampleRate = 12.5e6;
// Fill out the source handler
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 devices
refresh();
// Select first (TODO: Select from config)
select("");
sigpath::sourceManager.registerSource("Dragon Labs", &handler);
}
~DragonLabsSourceModule() {
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
void refresh() {
devices.clear();
// Get the list of devices
dlcr_info_t* list;
int count = dlcr_list_devices(&list);
if (count < 0) {
flog::error("Failed to list devices");
return;
}
// Generate the menu list
for (int i = 0; i < count; i++) {
// Format device name
std::string devName = "CR8-1725 ";
devName += " [";
devName += list[i].serial;
devName += ']';
// Save device
devices.define(list[i].serial, devName, list[i].serial);
}
// Free the device list
dlcr_free_device_list(list);
}
void select(const std::string& serial) {
// If there are no devices, give up
if (devices.empty()) {
selectedSerial.clear();
return;
}
// If the serial was not found, select the first available serial
if (!devices.keyExists(serial)) {
select(devices.key(0));
return;
}
// Save serial number
selectedSerial = serial;
}
static void menuSelected(void* ctx) {
DragonLabsSourceModule* _this = (DragonLabsSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
flog::info("DragonLabsSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
DragonLabsSourceModule* _this = (DragonLabsSourceModule*)ctx;
flog::info("DragonLabsSourceModule '{0}': Menu Deselect!", _this->name);
}
std::chrono::high_resolution_clock::time_point last;
static void start(void* ctx) {
DragonLabsSourceModule* _this = (DragonLabsSourceModule*)ctx;
if (_this->running) { return; }
// Open the device
int err = dlcr_open(&_this->openDev, _this->selectedSerial.c_str());
if (err) {
flog::error("Failed to open device '{}': {}", _this->selectedSerial, err);
return;
}
flog::debug("Device open");
// Configure the device
dlcr_set_freq(_this->openDev, DLCR_CHAN_ALL, _this->freq, true);
dlcr_set_lna_gain(_this->openDev, DLCR_CHAN_ALL, _this->lnaGain);
dlcr_set_mixer_gain(_this->openDev, DLCR_CHAN_ALL, _this->mixerGain);
dlcr_set_vga_gain(_this->openDev, DLCR_CHAN_ALL, _this->vgaGain);
// Start device
dlcr_start(_this->openDev, 0/*TODO*/, callback, _this);
_this->running = true;
flog::info("DragonLabsSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
DragonLabsSourceModule* _this = (DragonLabsSourceModule*)ctx;
if (!_this->running) { return; }
_this->running = false;
// Stop device
dlcr_stop(_this->openDev);
// Close device
dlcr_close(_this->openDev);
flog::info("DragonLabsSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
DragonLabsSourceModule* _this = (DragonLabsSourceModule*)ctx;
if (_this->running) {
dlcr_set_freq(_this->openDev, DLCR_CHAN_ALL, freq, _this->docal);
}
_this->calibrated = false;
_this->freq = freq;
flog::info("DragonLabsSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
double lmxFreq = 100e6;
static void menuHandler(void* ctx) {
DragonLabsSourceModule* _this = (DragonLabsSourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_dlcr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
_this->select(_this->devices.key(_this->devId));
core::setInputSampleRate(_this->sampleRate);
// TODO: Save
}
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_dlcr_refr_", _this->name))) {
_this->refresh();
_this->select(_this->selectedSerial);
core::setInputSampleRate(_this->sampleRate);
}
if (_this->running) { SmGui::EndDisabled(); }
SmGui::LeftLabel("Clock Source");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_dlcr_clock_", _this->name), &_this->clockSourceId, _this->clockSources.txt)) {
if (_this->running) {
dlcr_set_clock_source(_this->openDev, _this->clockSources[_this->clockSourceId]);
}
// TODO: Save
}
SmGui::LeftLabel("Channel");
SmGui::FillWidth();
SmGui::Combo(CONCAT("##_dlcr_chan_", _this->name), &_this->channelId, _this->channels.txt);
SmGui::LeftLabel("LNA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_dlcr_lna_gain_", _this->name), &_this->lnaGain, 0, 14)) {
if (_this->running) {
dlcr_set_lna_gain(_this->openDev, DLCR_CHAN_ALL, _this->lnaGain);
}
// TODO: Save
}
SmGui::LeftLabel("Mixer Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_dlcr_mixer_gain_", _this->name), &_this->mixerGain, 0, 15)) {
if (_this->running) {
dlcr_set_mixer_gain(_this->openDev, DLCR_CHAN_ALL, _this->mixerGain);
}
// TODO: Save
}
SmGui::LeftLabel("VGA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_dlcr_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) {
if (_this->running) {
dlcr_set_vga_gain(_this->openDev, DLCR_CHAN_ALL, _this->vgaGain);
}
// TODO: Save
}
SmGui::Checkbox(CONCAT("Debug##_dlcr_debug_", _this->name), &_this->debug);
if (_this->debug) {
ImGui::Separator();
SmGui::LeftLabel("Clockgen Reg");
SmGui::FillWidth();
SmGui::InputText(CONCAT("##_dlcr_clk_reg_", _this->name), _this->clkRegStr, 256);
SmGui::LeftLabel("Clockgen Value");
SmGui::FillWidth();
SmGui::InputText(CONCAT("##_dlcr_clk_val_", _this->name), _this->clkValStr, 256);
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Read##_dlcr_clk_rd_", _this->name))) {
if (_this->running) {
uint8_t val;
dlcr_si5351c_read_reg(_this->openDev, std::stoi(_this->clkRegStr, NULL, 16), &val);
sprintf(_this->clkValStr, "%02X", val);
}
}
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Write##_dlcr_clk_wr_", _this->name))) {
if (_this->running) {
dlcr_si5351c_write_reg(_this->openDev, std::stoi(_this->clkRegStr, NULL, 16), std::stoi(_this->clkValStr, NULL, 16));
}
}
ImGui::Separator();
SmGui::LeftLabel("Synth Reg");
SmGui::FillWidth();
SmGui::InputText(CONCAT("##_dlcr_syn_reg_", _this->name), _this->synRegStr, 256);
SmGui::LeftLabel("Synth Value");
SmGui::FillWidth();
SmGui::InputText(CONCAT("##_dlcr_syn_val_", _this->name), _this->synValStr, 256);
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Read##_dlcr_syn_rd_", _this->name))) {
if (_this->running) {
uint16_t val;
dlcr_lmx2572_read_reg(_this->openDev, std::stoi(_this->synRegStr, NULL, 16), &val);
sprintf(_this->synValStr, "%02X", val);
}
}
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Write##_dlcr_syn_wr_", _this->name))) {
if (_this->running) {
dlcr_lmx2572_write_reg(_this->openDev, std::stoi(_this->synRegStr, NULL, 16), std::stoi(_this->synValStr, NULL, 16));
}
}
ImGui::Separator();
SmGui::LeftLabel("ADC Reg");
SmGui::FillWidth();
SmGui::InputText(CONCAT("##_dlcr_adc_reg_", _this->name), _this->adcRegStr, 256);
SmGui::LeftLabel("ADC Value");
SmGui::FillWidth();
SmGui::InputText(CONCAT("##_dlcr_adc_val_", _this->name), _this->adcValStr, 256);
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Read##_dlcr_adc_rd_", _this->name))) {
if (_this->running) {
uint8_t val;
dlcr_mcp37211_read_reg(_this->openDev, std::stoi(_this->adcRegStr, NULL, 16), &val);
sprintf(_this->adcValStr, "%02X", val);
}
}
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Write##_dlcr_adc_wr_", _this->name))) {
if (_this->running) {
dlcr_mcp37211_write_reg(_this->openDev, std::stoi(_this->adcRegStr, NULL, 16), std::stoi(_this->adcValStr, NULL, 16));
}
}
ImGui::Separator();
SmGui::LeftLabel("Tuner Reg");
SmGui::FillWidth();
SmGui::InputText(CONCAT("##_dlcr_tun_reg_", _this->name), _this->tunRegStr, 256);
SmGui::LeftLabel("Tuner Value");
SmGui::FillWidth();
SmGui::InputText(CONCAT("##_dlcr_tun_val_", _this->name), _this->tunValStr, 256);
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Read##_dlcr_tun_rd_", _this->name))) {
if (_this->running) {
uint8_t val[8];
dlcr_r860_read_reg(_this->openDev, DLCR_TUNER_ALL, std::stoi(_this->tunRegStr, NULL, 16), val);
sprintf(_this->tunValStr, "%02X", val[0]);
for (int i = 0; i < 8; i++) {
printf("TUNER[%d][0x%02X] = 0x%02X\n", i, std::stoi(_this->tunRegStr, NULL, 16), val[i]);
}
}
}
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Write##_dlcr_tun_wr_", _this->name))) {
if (_this->running) {
dlcr_r860_write_reg(_this->openDev, DLCR_TUNER_ALL, std::stoi(_this->tunRegStr, NULL, 16), std::stoi(_this->tunValStr, NULL, 16));
}
}
ImGui::Separator();
SmGui::LeftLabel("Synth Freq");
SmGui::FillWidth();
ImGui::InputDouble("##_dlcr_synth_freq", &_this->synthFreq);
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Tune##_dlcr_synth_freq", _this->name))) {
if (_this->running) {
lmx2572_tune(_this->openDev, _this->synthFreq);
}
}
SmGui::FillWidth();
if (SmGui::Button(CONCAT("Shutdown##_dlcr_synth_freq", _this->name))) {
if (_this->running) {
lmx2572_shutdown(_this->openDev);
}
}
ImGui::Separator();
SmGui::Checkbox("Calibrate##_dlcr_cal", &_this->docal);
ImGui::Separator();
}
}
static void callback(dlcr_complex_t* samples[DLCR_CHANNEL_COUNT], size_t count, size_t drops, void* ctx) {
DragonLabsSourceModule* _this = (DragonLabsSourceModule*)ctx;
// Copy the data to the stream
memcpy(_this->stream.writeBuf, samples[_this->channelId], count * sizeof(dsp::complex_t));
// Send the samples
_this->stream.swap(count);
}
std::string name;
bool enabled = true;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq = 100e6;
OptionList<std::string, std::string> devices;
bool debug = false;
char clkRegStr[256];
char clkValStr[256];
char synRegStr[256];
char synValStr[256];
char adcRegStr[256];
char adcValStr[256];
char tunRegStr[256];
char tunValStr[256];
double synthFreq = 100e6;
bool docal = false;
int devId = 0;
int clockSourceId = 0;
int channelId = 0;
int lnaGain = 0;
int mixerGain = 0;
int vgaGain = 0;
std::string selectedSerial;
dlcr_t* openDev = NULL;
bool calibrated = false;
dsp::stream<dsp::complex_t> stream;
OptionList<std::string, dlcr_clock_t> clockSources;
OptionList<std::string, int> channels;
};
MOD_EXPORT void _INIT_() {
// Nothing here
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new DragonLabsSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (DragonLabsSourceModule*)instance;
}
MOD_EXPORT void _END_() {
// Nothing here
}