mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-11-10 04:37:37 +01:00
New stuff lmao
This commit is contained in:
parent
649be86cb6
commit
709627a738
@ -6,6 +6,7 @@ if (MSVC)
|
|||||||
link_directories(sdrpp "C:/Program Files/PothosSDR/lib/")
|
link_directories(sdrpp "C:/Program Files/PothosSDR/lib/")
|
||||||
include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/")
|
include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/")
|
||||||
include_directories(sdrpp "C:/Program Files/PothosSDR/include/")
|
include_directories(sdrpp "C:/Program Files/PothosSDR/include/")
|
||||||
|
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
else()
|
else()
|
||||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive -fsanitize=address -g")
|
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive -fsanitize=address -g")
|
||||||
# set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
# set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
||||||
@ -37,6 +38,7 @@ if (MSVC)
|
|||||||
endif (MSVC)
|
endif (MSVC)
|
||||||
|
|
||||||
add_executable(sdrpp ${SRC} ${IMGUI})
|
add_executable(sdrpp ${SRC} ${IMGUI})
|
||||||
|
# add_library(sdrpp ${SRC} ${IMGUI})
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
# Glew
|
# Glew
|
||||||
|
@ -11,8 +11,6 @@ struct RadioContext_t {
|
|||||||
std::string name;
|
std::string name;
|
||||||
int demod = 1;
|
int demod = 1;
|
||||||
SigPath sigPath;
|
SigPath sigPath;
|
||||||
// watcher<float> volume;
|
|
||||||
// watcher<int> audioDevice;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MOD_EXPORT void* _INIT_(mod::API_t* _API, ImGuiContext* imctx, std::string _name) {
|
MOD_EXPORT void* _INIT_(mod::API_t* _API, ImGuiContext* imctx, std::string _name) {
|
||||||
@ -21,24 +19,12 @@ MOD_EXPORT void* _INIT_(mod::API_t* _API, ImGuiContext* imctx, std::string _name
|
|||||||
ctx->name = _name;
|
ctx->name = _name;
|
||||||
ctx->sigPath.init(_name, 200000, 1000, API->registerVFO(_name, mod::API_t::REF_CENTER, 0, 200000, 200000, 1000));
|
ctx->sigPath.init(_name, 200000, 1000, API->registerVFO(_name, mod::API_t::REF_CENTER, 0, 200000, 200000, 1000));
|
||||||
ctx->sigPath.start();
|
ctx->sigPath.start();
|
||||||
// ctx->volume.val = 1.0f;
|
|
||||||
// ctx->volume.markAsChanged();
|
|
||||||
// API->bindVolumeVariable(&ctx->volume.val);
|
|
||||||
// ctx->audioDevice.val = ctx->sigPath.audio.getDeviceId();
|
|
||||||
// ctx->audioDevice.changed(); // clear change
|
|
||||||
ImGui::SetCurrentContext(imctx);
|
ImGui::SetCurrentContext(imctx);
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _NEW_FRAME_(RadioContext_t* ctx) {
|
MOD_EXPORT void _NEW_FRAME_(RadioContext_t* ctx) {
|
||||||
// if (ctx->volume.changed()) {
|
|
||||||
// ctx->sigPath.setVolume(ctx->volume.val);
|
|
||||||
// }
|
|
||||||
// if (ctx->audioDevice.changed()) {
|
|
||||||
// ctx->sigPath.audio.stop();
|
|
||||||
// ctx->sigPath.audio.setDevice(ctx->audioDevice.val);
|
|
||||||
// ctx->sigPath.audio.start();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
|
MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
|
||||||
@ -48,28 +34,28 @@ MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
|
|||||||
if (ImGui::RadioButton(CONCAT("NFM##_", ctx->name), ctx->demod == 0) && ctx->demod != 0) {
|
if (ImGui::RadioButton(CONCAT("NFM##_", ctx->name), ctx->demod == 0) && ctx->demod != 0) {
|
||||||
ctx->sigPath.setDemodulator(SigPath::DEMOD_NFM);
|
ctx->sigPath.setDemodulator(SigPath::DEMOD_NFM);
|
||||||
ctx->demod = 0;
|
ctx->demod = 0;
|
||||||
API->setVFOBandwidth(ctx->name, 12500);
|
|
||||||
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
|
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
|
||||||
}
|
}
|
||||||
if (ImGui::RadioButton(CONCAT("WFM##_", ctx->name), ctx->demod == 1) && ctx->demod != 1) {
|
if (ImGui::RadioButton(CONCAT("WFM##_", ctx->name), ctx->demod == 1) && ctx->demod != 1) {
|
||||||
ctx->sigPath.setDemodulator(SigPath::DEMOD_FM);
|
ctx->sigPath.setDemodulator(SigPath::DEMOD_FM);
|
||||||
ctx->demod = 1;
|
ctx->demod = 1;
|
||||||
API->setVFOBandwidth(ctx->name, 200000);
|
|
||||||
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
|
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
|
||||||
}
|
}
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
if (ImGui::RadioButton(CONCAT("AM##_", ctx->name), ctx->demod == 2) && ctx->demod != 2) {
|
if (ImGui::RadioButton(CONCAT("AM##_", ctx->name), ctx->demod == 2) && ctx->demod != 2) {
|
||||||
ctx->sigPath.setDemodulator(SigPath::DEMOD_AM);
|
ctx->sigPath.setDemodulator(SigPath::DEMOD_AM);
|
||||||
ctx->demod = 2;
|
ctx->demod = 2;
|
||||||
API->setVFOBandwidth(ctx->name, 12500);
|
|
||||||
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
|
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
|
||||||
}
|
}
|
||||||
if (ImGui::RadioButton(CONCAT("DSB##_", ctx->name), ctx->demod == 3) && ctx->demod != 3) { ctx->demod = 3; };
|
if (ImGui::RadioButton(CONCAT("DSB##_", ctx->name), ctx->demod == 3) && ctx->demod != 3) {
|
||||||
|
ctx->sigPath.setDemodulator(SigPath::DEMOD_DSB);
|
||||||
|
ctx->demod = 3;
|
||||||
|
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
|
||||||
|
}
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
if (ImGui::RadioButton(CONCAT("USB##_", ctx->name), ctx->demod == 4) && ctx->demod != 4) {
|
if (ImGui::RadioButton(CONCAT("USB##_", ctx->name), ctx->demod == 4) && ctx->demod != 4) {
|
||||||
ctx->sigPath.setDemodulator(SigPath::DEMOD_USB);
|
ctx->sigPath.setDemodulator(SigPath::DEMOD_USB);
|
||||||
ctx->demod = 4;
|
ctx->demod = 4;
|
||||||
API->setVFOBandwidth(ctx->name, 3000);
|
|
||||||
API->setVFOReference(ctx->name, mod::API_t::REF_LOWER);
|
API->setVFOReference(ctx->name, mod::API_t::REF_LOWER);
|
||||||
}
|
}
|
||||||
if (ImGui::RadioButton(CONCAT("CW##_", ctx->name), ctx->demod == 5) && ctx->demod != 5) { ctx->demod = 5; };
|
if (ImGui::RadioButton(CONCAT("CW##_", ctx->name), ctx->demod == 5) && ctx->demod != 5) { ctx->demod = 5; };
|
||||||
@ -77,7 +63,6 @@ MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
|
|||||||
if (ImGui::RadioButton(CONCAT("LSB##_", ctx->name), ctx->demod == 6) && ctx->demod != 6) {
|
if (ImGui::RadioButton(CONCAT("LSB##_", ctx->name), ctx->demod == 6) && ctx->demod != 6) {
|
||||||
ctx->sigPath.setDemodulator(SigPath::DEMOD_LSB);
|
ctx->sigPath.setDemodulator(SigPath::DEMOD_LSB);
|
||||||
ctx->demod = 6;
|
ctx->demod = 6;
|
||||||
API->setVFOBandwidth(ctx->name, 3000);
|
|
||||||
API->setVFOReference(ctx->name, mod::API_t::REF_UPPER);
|
API->setVFOReference(ctx->name, mod::API_t::REF_UPPER);
|
||||||
}
|
}
|
||||||
if (ImGui::RadioButton(CONCAT("RAW##_", ctx->name), ctx->demod == 7) && ctx->demod != 7) { ctx->demod = 7; };
|
if (ImGui::RadioButton(CONCAT("RAW##_", ctx->name), ctx->demod == 7) && ctx->demod != 7) { ctx->demod = 7; };
|
||||||
@ -85,21 +70,11 @@ MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
|
|||||||
|
|
||||||
ImGui::EndGroup();
|
ImGui::EndGroup();
|
||||||
|
|
||||||
// ImGui::PushItemWidth(ImGui::GetWindowSize().x);
|
ImGui::Checkbox(CONCAT("Deemphasis##_", ctx->name), &ctx->sigPath.deemp.bypass);
|
||||||
// ImGui::Combo(CONCAT("##_audio_dev_", ctx->name), &ctx->audioDevice.val, ctx->sigPath.audio.devTxtList.c_str());
|
|
||||||
// ImGui::PopItemWidth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _HANDLE_EVENT_(RadioContext_t* ctx, int eventId) {
|
MOD_EXPORT void _HANDLE_EVENT_(RadioContext_t* ctx, int eventId) {
|
||||||
// INSTEAD OF EVENTS, REGISTER HANDLER WHEN CREATING VFO
|
|
||||||
if (eventId == mod::EVENT_STREAM_PARAM_CHANGED) {
|
|
||||||
ctx->sigPath.updateBlockSize();
|
|
||||||
}
|
|
||||||
else if (eventId == mod::EVENT_SELECTED_VFO_CHANGED) {
|
|
||||||
// if (API->getSelectedVFOName() == ctx->name) {
|
|
||||||
// API->bindVolumeVariable(&ctx->volume.val);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _STOP_(RadioContext_t* ctx) {
|
MOD_EXPORT void _STOP_(RadioContext_t* ctx) {
|
||||||
|
@ -6,9 +6,16 @@ SigPath::SigPath() {
|
|||||||
|
|
||||||
int SigPath::sampleRateChangeHandler(void* ctx, float sampleRate) {
|
int SigPath::sampleRateChangeHandler(void* ctx, float sampleRate) {
|
||||||
SigPath* _this = (SigPath*)ctx;
|
SigPath* _this = (SigPath*)ctx;
|
||||||
|
_this->outputSampleRate = sampleRate;
|
||||||
_this->audioResamp.stop();
|
_this->audioResamp.stop();
|
||||||
_this->audioResamp.setOutputSampleRate(sampleRate, sampleRate / 2.0f, sampleRate / 2.0f);
|
_this->deemp.stop();
|
||||||
|
float bw = std::min<float>(_this->bandwidth, sampleRate / 2.0f);
|
||||||
|
spdlog::warn("New bandwidth: {0}", bw);
|
||||||
|
_this->audioResamp.setOutputSampleRate(sampleRate, bw, bw);
|
||||||
|
_this->deemp.setBlockSize(_this->audioResamp.getOutputBlockSize());
|
||||||
|
_this->deemp.setSamplerate(sampleRate);
|
||||||
_this->audioResamp.start();
|
_this->audioResamp.start();
|
||||||
|
_this->deemp.start();
|
||||||
return _this->audioResamp.getOutputBlockSize();
|
return _this->audioResamp.getOutputBlockSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,6 +25,7 @@ void SigPath::init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp:
|
|||||||
this->vfoName = vfoName;
|
this->vfoName = vfoName;
|
||||||
|
|
||||||
_demod = DEMOD_FM;
|
_demod = DEMOD_FM;
|
||||||
|
bandwidth = 200000;
|
||||||
|
|
||||||
// TODO: Set default VFO options
|
// TODO: Set default VFO options
|
||||||
|
|
||||||
@ -26,8 +34,11 @@ void SigPath::init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp:
|
|||||||
ssbDemod.init(input, 6000, 3000, 22);
|
ssbDemod.init(input, 6000, 3000, 22);
|
||||||
|
|
||||||
audioResamp.init(&demod.output, 200000, 48000, 800);
|
audioResamp.init(&demod.output, 200000, 48000, 800);
|
||||||
API->registerMonoStream(&audioResamp.output, vfoName, vfoName, sampleRateChangeHandler, this);
|
deemp.init(&audioResamp.output, 800, 50e-6, 48000);
|
||||||
|
outputSampleRate = API->registerMonoStream(&deemp.output, vfoName, vfoName, sampleRateChangeHandler, this);
|
||||||
API->setBlockSize(vfoName, audioResamp.getOutputBlockSize());
|
API->setBlockSize(vfoName, audioResamp.getOutputBlockSize());
|
||||||
|
|
||||||
|
setDemodulator(_demod);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SigPath::setSampleRate(float sampleRate) {
|
void SigPath::setSampleRate(float sampleRate) {
|
||||||
@ -43,6 +54,7 @@ void SigPath::setDemodulator(int demId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
audioResamp.stop();
|
audioResamp.stop();
|
||||||
|
deemp.stop();
|
||||||
|
|
||||||
// Stop current demodulator
|
// Stop current demodulator
|
||||||
if (_demod == DEMOD_FM) {
|
if (_demod == DEMOD_FM) {
|
||||||
@ -65,47 +77,76 @@ void SigPath::setDemodulator(int demId) {
|
|||||||
// Set input of the audio resampler
|
// Set input of the audio resampler
|
||||||
if (demId == DEMOD_FM) {
|
if (demId == DEMOD_FM) {
|
||||||
API->setVFOSampleRate(vfoName, 200000, 200000);
|
API->setVFOSampleRate(vfoName, 200000, 200000);
|
||||||
|
bandwidth = 15000;
|
||||||
demod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
demod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
||||||
demod.setSampleRate(200000);
|
demod.setSampleRate(200000);
|
||||||
demod.setDeviation(100000);
|
demod.setDeviation(100000);
|
||||||
audioResamp.setInput(&demod.output);
|
audioResamp.setInput(&demod.output);
|
||||||
audioResamp.setInputSampleRate(200000, API->getVFOOutputBlockSize(vfoName), 15000, 15000);
|
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
|
||||||
|
audioResamp.setInputSampleRate(200000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
|
||||||
|
deemp.bypass = false;
|
||||||
demod.start();
|
demod.start();
|
||||||
}
|
}
|
||||||
if (demId == DEMOD_NFM) {
|
if (demId == DEMOD_NFM) {
|
||||||
API->setVFOSampleRate(vfoName, 12500, 12500);
|
API->setVFOSampleRate(vfoName, 16000, 16000);
|
||||||
|
bandwidth = 8000;
|
||||||
demod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
demod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
||||||
demod.setSampleRate(12500);
|
demod.setSampleRate(16000);
|
||||||
demod.setDeviation(6250);
|
demod.setDeviation(8000);
|
||||||
audioResamp.setInput(&demod.output);
|
audioResamp.setInput(&demod.output);
|
||||||
audioResamp.setInputSampleRate(12500, API->getVFOOutputBlockSize(vfoName));
|
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
|
||||||
|
audioResamp.setInputSampleRate(16000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
|
||||||
|
deemp.bypass = true;
|
||||||
demod.start();
|
demod.start();
|
||||||
}
|
}
|
||||||
else if (demId == DEMOD_AM) {
|
else if (demId == DEMOD_AM) {
|
||||||
API->setVFOSampleRate(vfoName, 12500, 12500);
|
API->setVFOSampleRate(vfoName, 12500, 12500);
|
||||||
|
bandwidth = 6250;
|
||||||
amDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
amDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
||||||
audioResamp.setInput(&amDemod.output);
|
audioResamp.setInput(&amDemod.output);
|
||||||
audioResamp.setInputSampleRate(12500, API->getVFOOutputBlockSize(vfoName));
|
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
|
||||||
|
audioResamp.setInputSampleRate(12500, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
|
||||||
|
deemp.bypass = true;
|
||||||
amDemod.start();
|
amDemod.start();
|
||||||
}
|
}
|
||||||
else if (demId == DEMOD_USB) {
|
else if (demId == DEMOD_USB) {
|
||||||
API->setVFOSampleRate(vfoName, 6000, 3000);
|
API->setVFOSampleRate(vfoName, 6000, 3000);
|
||||||
|
bandwidth = 3000;
|
||||||
ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
||||||
ssbDemod.setMode(dsp::SSBDemod::MODE_USB);
|
ssbDemod.setMode(dsp::SSBDemod::MODE_USB);
|
||||||
audioResamp.setInput(&ssbDemod.output);
|
audioResamp.setInput(&ssbDemod.output);
|
||||||
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName));
|
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
|
||||||
|
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
|
||||||
|
deemp.bypass = true;
|
||||||
ssbDemod.start();
|
ssbDemod.start();
|
||||||
}
|
}
|
||||||
else if (demId == DEMOD_LSB) {
|
else if (demId == DEMOD_LSB) {
|
||||||
API->setVFOSampleRate(vfoName, 6000, 3000);
|
API->setVFOSampleRate(vfoName, 6000, 3000);
|
||||||
|
bandwidth = 3000;
|
||||||
ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
||||||
ssbDemod.setMode(dsp::SSBDemod::MODE_LSB);
|
ssbDemod.setMode(dsp::SSBDemod::MODE_LSB);
|
||||||
audioResamp.setInput(&ssbDemod.output);
|
audioResamp.setInput(&ssbDemod.output);
|
||||||
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName));
|
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
|
||||||
|
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
|
||||||
|
deemp.bypass = true;
|
||||||
|
ssbDemod.start();
|
||||||
|
}
|
||||||
|
else if (demId == DEMOD_DSB) {
|
||||||
|
API->setVFOSampleRate(vfoName, 6000, 6000);
|
||||||
|
bandwidth = 3000;
|
||||||
|
ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
|
||||||
|
ssbDemod.setMode(dsp::SSBDemod::MODE_DSB);
|
||||||
|
audioResamp.setInput(&ssbDemod.output);
|
||||||
|
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
|
||||||
|
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
|
||||||
|
deemp.bypass = true;
|
||||||
ssbDemod.start();
|
ssbDemod.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deemp.setBlockSize(audioResamp.getOutputBlockSize());
|
||||||
|
|
||||||
audioResamp.start();
|
audioResamp.start();
|
||||||
|
deemp.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SigPath::updateBlockSize() {
|
void SigPath::updateBlockSize() {
|
||||||
@ -115,5 +156,6 @@ void SigPath::updateBlockSize() {
|
|||||||
void SigPath::start() {
|
void SigPath::start() {
|
||||||
demod.start();
|
demod.start();
|
||||||
audioResamp.start();
|
audioResamp.start();
|
||||||
|
deemp.start();
|
||||||
API->startStream(vfoName);
|
API->startStream(vfoName);
|
||||||
}
|
}
|
@ -18,7 +18,7 @@ public:
|
|||||||
void start();
|
void start();
|
||||||
void setSampleRate(float sampleRate);
|
void setSampleRate(float sampleRate);
|
||||||
|
|
||||||
void setVFOFrequency(long frequency);
|
void setVFOFrequency(uint64_t frequency);
|
||||||
|
|
||||||
void updateBlockSize();
|
void updateBlockSize();
|
||||||
|
|
||||||
@ -30,9 +30,12 @@ public:
|
|||||||
DEMOD_AM,
|
DEMOD_AM,
|
||||||
DEMOD_USB,
|
DEMOD_USB,
|
||||||
DEMOD_LSB,
|
DEMOD_LSB,
|
||||||
|
DEMOD_DSB,
|
||||||
_DEMOD_COUNT
|
_DEMOD_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dsp::FMDeemphasis deemp;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int sampleRateChangeHandler(void* ctx, float sampleRate);
|
static int sampleRateChangeHandler(void* ctx, float sampleRate);
|
||||||
|
|
||||||
@ -49,6 +52,8 @@ private:
|
|||||||
std::string vfoName;
|
std::string vfoName;
|
||||||
|
|
||||||
float sampleRate;
|
float sampleRate;
|
||||||
|
float bandwidth;
|
||||||
|
float outputSampleRate;
|
||||||
int blockSize;
|
int blockSize;
|
||||||
int _demod;
|
int _demod;
|
||||||
};
|
};
|
@ -21,6 +21,8 @@ struct RecorderContext_t {
|
|||||||
std::string lastNameList;
|
std::string lastNameList;
|
||||||
std::string selectedStreamName;
|
std::string selectedStreamName;
|
||||||
int selectedStreamId;
|
int selectedStreamId;
|
||||||
|
uint64_t samplesWritten;
|
||||||
|
float sampleRate;
|
||||||
};
|
};
|
||||||
|
|
||||||
void _writeWorker(RecorderContext_t* ctx) {
|
void _writeWorker(RecorderContext_t* ctx) {
|
||||||
@ -34,6 +36,7 @@ void _writeWorker(RecorderContext_t* ctx) {
|
|||||||
sampleBuf[(i * 2) + 0] = floatBuf[i].l * 0x7FFF;
|
sampleBuf[(i * 2) + 0] = floatBuf[i].l * 0x7FFF;
|
||||||
sampleBuf[(i * 2) + 1] = floatBuf[i].r * 0x7FFF;
|
sampleBuf[(i * 2) + 1] = floatBuf[i].r * 0x7FFF;
|
||||||
}
|
}
|
||||||
|
ctx->samplesWritten += 1024;
|
||||||
ctx->writer->writeSamples(sampleBuf, 2048 * sizeof(int16_t));
|
ctx->writer->writeSamples(sampleBuf, 2048 * sizeof(int16_t));
|
||||||
}
|
}
|
||||||
delete[] floatBuf;
|
delete[] floatBuf;
|
||||||
@ -60,6 +63,9 @@ MOD_EXPORT void* _INIT_(mod::API_t* _API, ImGuiContext* imctx, std::string _name
|
|||||||
API = _API;
|
API = _API;
|
||||||
RecorderContext_t* ctx = new RecorderContext_t;
|
RecorderContext_t* ctx = new RecorderContext_t;
|
||||||
ctx->recording = false;
|
ctx->recording = false;
|
||||||
|
ctx->selectedStreamName = "";
|
||||||
|
ctx->selectedStreamId = 0;
|
||||||
|
ctx->lastNameList = "";
|
||||||
ImGui::SetCurrentContext(imctx);
|
ImGui::SetCurrentContext(imctx);
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
@ -78,14 +84,30 @@ MOD_EXPORT void _DRAW_MENU_(RecorderContext_t* ctx) {
|
|||||||
nameList += '\0';
|
nameList += '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nameList == "") {
|
||||||
|
ImGui::Text("No audio stream available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx->lastNameList != nameList) {
|
if (ctx->lastNameList != nameList) {
|
||||||
ctx->lastNameList = nameList;
|
ctx->lastNameList = nameList;
|
||||||
|
auto _nameIt = std::find(streamNames.begin(), streamNames.end(), ctx->selectedStreamName);
|
||||||
|
if (_nameIt == streamNames.end()) {
|
||||||
|
// TODO: verify if there even is a stream
|
||||||
|
ctx->selectedStreamId = 0;
|
||||||
|
ctx->selectedStreamName = streamNames[ctx->selectedStreamId];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx->selectedStreamId = std::distance(streamNames.begin(), _nameIt);
|
||||||
|
ctx->selectedStreamName = streamNames[ctx->selectedStreamId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PushItemWidth(menuColumnWidth);
|
ImGui::PushItemWidth(menuColumnWidth);
|
||||||
if (!ctx->recording) {
|
if (!ctx->recording) {
|
||||||
ImGui::Combo(CONCAT("##_strea_select_", ctx->name), &ctx->selectedStreamId, nameList.c_str());
|
if (ImGui::Combo(CONCAT("##_strea_select_", ctx->name), &ctx->selectedStreamId, nameList.c_str())) {
|
||||||
|
ctx->selectedStreamName = nameList[ctx->selectedStreamId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||||
@ -99,8 +121,10 @@ MOD_EXPORT void _DRAW_MENU_(RecorderContext_t* ctx) {
|
|||||||
|
|
||||||
if (!ctx->recording) {
|
if (!ctx->recording) {
|
||||||
if (ImGui::Button("Record", ImVec2(menuColumnWidth, 0))) {
|
if (ImGui::Button("Record", ImVec2(menuColumnWidth, 0))) {
|
||||||
|
ctx->samplesWritten = 0;
|
||||||
|
ctx->sampleRate = 48000;
|
||||||
ctx->writer = new WavWriter("recordings/" + genFileName("audio_"), 16, 2, 48000);
|
ctx->writer = new WavWriter("recordings/" + genFileName("audio_"), 16, 2, 48000);
|
||||||
ctx->stream = API->bindToStreamStereo("Radio", streamRemovedHandler, sampleRateChanged, ctx);
|
ctx->stream = API->bindToStreamStereo(ctx->selectedStreamName, streamRemovedHandler, sampleRateChanged, ctx);
|
||||||
ctx->workerThread = std::thread(_writeWorker, ctx);
|
ctx->workerThread = std::thread(_writeWorker, ctx);
|
||||||
ctx->recording = true;
|
ctx->recording = true;
|
||||||
ctx->startTime = time(0);
|
ctx->startTime = time(0);
|
||||||
@ -112,12 +136,13 @@ MOD_EXPORT void _DRAW_MENU_(RecorderContext_t* ctx) {
|
|||||||
ctx->stream->stopReader();
|
ctx->stream->stopReader();
|
||||||
ctx->workerThread.join();
|
ctx->workerThread.join();
|
||||||
ctx->stream->clearReadStop();
|
ctx->stream->clearReadStop();
|
||||||
API->unbindFromStreamStereo("Radio", ctx->stream);
|
API->unbindFromStreamStereo(ctx->selectedStreamName, ctx->stream);
|
||||||
ctx->writer->close();
|
ctx->writer->close();
|
||||||
delete ctx->writer;
|
delete ctx->writer;
|
||||||
ctx->recording = false;
|
ctx->recording = false;
|
||||||
}
|
}
|
||||||
time_t diff = time(0) - ctx->startTime;
|
uint64_t seconds = ctx->samplesWritten / (uint64_t)ctx->sampleRate;
|
||||||
|
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);
|
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec);
|
||||||
}
|
}
|
||||||
|
@ -260,6 +260,9 @@ namespace dsp {
|
|||||||
else if (mode == MODE_LSB) {
|
else if (mode == MODE_LSB) {
|
||||||
lo.setFrequency(-_bandWidth / 2.0f);
|
lo.setFrequency(-_bandWidth / 2.0f);
|
||||||
}
|
}
|
||||||
|
else if (mode == MODE_LSB) {
|
||||||
|
lo.setFrequency(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stream<float> output;
|
stream<float> output;
|
||||||
@ -267,6 +270,7 @@ namespace dsp {
|
|||||||
enum {
|
enum {
|
||||||
MODE_USB,
|
MODE_USB,
|
||||||
MODE_LSB,
|
MODE_LSB,
|
||||||
|
MODE_DSB,
|
||||||
_MODE_COUNT
|
_MODE_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -309,4 +313,102 @@ namespace dsp {
|
|||||||
int _mode;
|
int _mode;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// class CWDemod {
|
||||||
|
// public:
|
||||||
|
// CWDemod() {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void init(stream<complex_t>* input, float sampleRate, float bandWidth, int blockSize) {
|
||||||
|
// _blockSize = blockSize;
|
||||||
|
// _bandWidth = bandWidth;
|
||||||
|
// _mode = MODE_USB;
|
||||||
|
// output.init(blockSize * 2);
|
||||||
|
// lo.init(bandWidth / 2.0f, sampleRate, blockSize);
|
||||||
|
// mixer.init(input, &lo.output, blockSize);
|
||||||
|
// lo.start();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void start() {
|
||||||
|
// mixer.start();
|
||||||
|
// _workerThread = std::thread(_worker, this);
|
||||||
|
// running = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void stop() {
|
||||||
|
// mixer.stop();
|
||||||
|
// mixer.output.stopReader();
|
||||||
|
// output.stopWriter();
|
||||||
|
// _workerThread.join();
|
||||||
|
// mixer.output.clearReadStop();
|
||||||
|
// output.clearWriteStop();
|
||||||
|
// running = false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void setBlockSize(int blockSize) {
|
||||||
|
// if (running) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// _blockSize = blockSize;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void setMode(int mode) {
|
||||||
|
// if (mode < 0 && mode >= _MODE_COUNT) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// _mode = mode;
|
||||||
|
// if (mode == MODE_USB) {
|
||||||
|
// lo.setFrequency(_bandWidth / 2.0f);
|
||||||
|
// }
|
||||||
|
// else if (mode == MODE_LSB) {
|
||||||
|
// lo.setFrequency(-_bandWidth / 2.0f);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// stream<float> output;
|
||||||
|
|
||||||
|
// private:
|
||||||
|
// static void _worker(CWDemod* _this) {
|
||||||
|
// complex_t* inBuf = new complex_t[_this->_blockSize];
|
||||||
|
// float* outBuf = new float[_this->_blockSize];
|
||||||
|
|
||||||
|
// float min, max, factor;
|
||||||
|
|
||||||
|
// while (true) {
|
||||||
|
// if (_this->mixer.output.read(inBuf, _this->_blockSize) < 0) { break; };
|
||||||
|
// min = INFINITY;
|
||||||
|
// max = -INFINITY;
|
||||||
|
// for (int i = 0; i < _this->_blockSize; i++) {
|
||||||
|
// outBuf[i] = inBuf[i].q;
|
||||||
|
// if (inBuf[i].q < min) {
|
||||||
|
// min = inBuf[i].q;
|
||||||
|
// }
|
||||||
|
// if (inBuf[i].q > max) {
|
||||||
|
// max = inBuf[i].q;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// factor = (max - min) / 2;
|
||||||
|
// for (int i = 0; i < _this->_blockSize; i++) {
|
||||||
|
// outBuf[i] /= factor;
|
||||||
|
// }
|
||||||
|
// if (_this->output.write(outBuf, _this->_blockSize) < 0) { break; };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// delete[] inBuf;
|
||||||
|
// delete[] outBuf;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// std::thread _workerThread;
|
||||||
|
// SineSource lo;
|
||||||
|
// Multiplier mixer;
|
||||||
|
// int _blockSize;
|
||||||
|
// float _bandWidth;
|
||||||
|
// int _mode;
|
||||||
|
// bool running = false;
|
||||||
|
// };
|
||||||
};
|
};
|
114
src/dsp/filter.h
114
src/dsp/filter.h
@ -4,11 +4,12 @@
|
|||||||
#include <dsp/types.h>
|
#include <dsp/types.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <dsp/math.h>
|
#include <dsp/math.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#define GET_FROM_RIGHT_BUF(buffer, delayLine, delayLineSz, n) (((n) < 0) ? delayLine[(delayLineSz) + (n)] : buffer[(n)])
|
#define GET_FROM_RIGHT_BUF(buffer, delayLine, delayLineSz, n) (((n) < 0) ? delayLine[(delayLineSz) + (n)] : buffer[(n)])
|
||||||
|
|
||||||
namespace dsp {
|
namespace dsp {
|
||||||
inline void BlackmanWindow(std::vector<float>& taps, float sampleRate, float cutoff, float transWidth) {
|
inline void BlackmanWindow(std::vector<float>& taps, float sampleRate, float cutoff, float transWidth, int addedTaps = 0) {
|
||||||
taps.clear();
|
taps.clear();
|
||||||
|
|
||||||
float fc = cutoff / sampleRate;
|
float fc = cutoff / sampleRate;
|
||||||
@ -16,7 +17,7 @@ namespace dsp {
|
|||||||
fc = 1.0f;
|
fc = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _M = 4.0f / (transWidth / sampleRate);
|
int _M = (4.0f / (transWidth / sampleRate)) + (float)addedTaps;
|
||||||
if (_M < 4) {
|
if (_M < 4) {
|
||||||
_M = 4;
|
_M = 4;
|
||||||
}
|
}
|
||||||
@ -377,4 +378,113 @@ namespace dsp {
|
|||||||
float* _taps;
|
float* _taps;
|
||||||
bool running;
|
bool running;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FMDeemphasis {
|
||||||
|
public:
|
||||||
|
FMDeemphasis() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FMDeemphasis(stream<float>* input, int bufferSize, float tau, float sampleRate) : output(bufferSize * 2) {
|
||||||
|
_in = input;
|
||||||
|
_bufferSize = bufferSize;
|
||||||
|
bypass = false;
|
||||||
|
_tau = tau;
|
||||||
|
_sampleRate = sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(stream<float>* input, int bufferSize, float tau, float sampleRate) {
|
||||||
|
output.init(bufferSize * 2);
|
||||||
|
_in = input;
|
||||||
|
_bufferSize = bufferSize;
|
||||||
|
bypass = false;
|
||||||
|
_tau = tau;
|
||||||
|
_sampleRate = sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_workerThread = std::thread(_worker, this);
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_in->stopReader();
|
||||||
|
output.stopWriter();
|
||||||
|
_workerThread.join();
|
||||||
|
_in->clearReadStop();
|
||||||
|
output.clearWriteStop();
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBlockSize(int blockSize) {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_bufferSize = blockSize;
|
||||||
|
output.setMaxLatency(blockSize * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSamplerate(float sampleRate) {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_sampleRate = sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTau(float tau) {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tau = tau;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream<float> output;
|
||||||
|
bool bypass;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _worker(FMDeemphasis* _this) {
|
||||||
|
float* inBuf = new float[_this->_bufferSize];
|
||||||
|
float* outBuf = new float[_this->_bufferSize];
|
||||||
|
int count = _this->_bufferSize;
|
||||||
|
float lastOut = 0.0f;
|
||||||
|
float dt = 1.0f / _this->_sampleRate;
|
||||||
|
float alpha = dt / (_this->_tau + dt);
|
||||||
|
|
||||||
|
spdlog::warn("Deemp filter started: {0}, {1}", _this->_tau * 1000000.0, _this->_sampleRate);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (_this->_in->read(inBuf, count) < 0) { break; };
|
||||||
|
if (_this->bypass) {
|
||||||
|
if (_this->output.write(inBuf, count) < 0) { break; };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isnan(lastOut)) {
|
||||||
|
lastOut = 0.0f;
|
||||||
|
}
|
||||||
|
outBuf[0] = (alpha * inBuf[0]) + ((1-alpha) * lastOut);
|
||||||
|
for (int i = 1; i < count; i++) {
|
||||||
|
outBuf[i] = (alpha * inBuf[i]) + ((1 - alpha) * outBuf[i - 1]);
|
||||||
|
}
|
||||||
|
lastOut = outBuf[count - 1];
|
||||||
|
|
||||||
|
if (_this->output.write(outBuf, count) < 0) { break; };
|
||||||
|
}
|
||||||
|
delete[] inBuf;
|
||||||
|
delete[] outBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream<float>* _in;
|
||||||
|
int _bufferSize;
|
||||||
|
std::thread _workerThread;
|
||||||
|
bool running = false;
|
||||||
|
float _sampleRate;
|
||||||
|
float _tau;
|
||||||
|
};
|
||||||
};
|
};
|
@ -564,4 +564,212 @@ namespace dsp {
|
|||||||
int _blockSize;
|
int _blockSize;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FloatPolyphaseFIRResampler {
|
||||||
|
public:
|
||||||
|
FloatPolyphaseFIRResampler() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(stream<float>* in, float inputSampleRate, float outputSampleRate, int blockSize, float passBand = -1.0f, float transWidth = -1.0f) {
|
||||||
|
_input = in;
|
||||||
|
_outputSampleRate = outputSampleRate;
|
||||||
|
_inputSampleRate = inputSampleRate;
|
||||||
|
int _gcd = std::gcd((int)inputSampleRate, (int)outputSampleRate);
|
||||||
|
_interp = outputSampleRate / _gcd;
|
||||||
|
_decim = inputSampleRate / _gcd;
|
||||||
|
_blockSize = blockSize;
|
||||||
|
outputBlockSize = (blockSize * _interp) / _decim;
|
||||||
|
output.init(outputBlockSize * 2);
|
||||||
|
|
||||||
|
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
|
||||||
|
if (passBand > 0.0f && transWidth > 0.0f) {
|
||||||
|
dsp::BlackmanWindow(_taps, _outputSampleRate, passBand, transWidth, _interp - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dsp::BlackmanWindow(_taps, _outputSampleRate, cutoff, cutoff, _interp - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_workerThread = std::thread(_worker, this);
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_input->stopReader();
|
||||||
|
output.stopWriter();
|
||||||
|
_workerThread.join();
|
||||||
|
_input->clearReadStop();
|
||||||
|
output.clearWriteStop();
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInputSampleRate(float inputSampleRate, int blockSize = -1, float passBand = -1.0f, float transWidth = -1.0f) {
|
||||||
|
stop();
|
||||||
|
_inputSampleRate = inputSampleRate;
|
||||||
|
int _gcd = std::gcd((int)inputSampleRate, (int)_outputSampleRate);
|
||||||
|
_interp = _outputSampleRate / _gcd;
|
||||||
|
_decim = inputSampleRate / _gcd;
|
||||||
|
|
||||||
|
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
|
||||||
|
if (passBand > 0.0f && transWidth > 0.0f) {
|
||||||
|
dsp::BlackmanWindow(_taps, _outputSampleRate, passBand, transWidth, _interp - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dsp::BlackmanWindow(_taps,_outputSampleRate, cutoff, cutoff, _interp - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockSize > 0) {
|
||||||
|
_blockSize = blockSize;
|
||||||
|
}
|
||||||
|
outputBlockSize = (blockSize * _interp) / _decim;
|
||||||
|
output.setMaxLatency(outputBlockSize * 2);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOutputSampleRate(float outputSampleRate, float passBand = -1.0f, float transWidth = -1.0f) {
|
||||||
|
stop();
|
||||||
|
_outputSampleRate = outputSampleRate;
|
||||||
|
int _gcd = std::gcd((int)_inputSampleRate, (int)outputSampleRate);
|
||||||
|
_interp = outputSampleRate / _gcd;
|
||||||
|
_decim = _inputSampleRate / _gcd;
|
||||||
|
outputBlockSize = (_blockSize * _interp) / _decim;
|
||||||
|
output.setMaxLatency(outputBlockSize * 2);
|
||||||
|
|
||||||
|
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
|
||||||
|
if (passBand > 0.0f && transWidth > 0.0f) {
|
||||||
|
dsp::BlackmanWindow(_taps, _outputSampleRate, passBand, transWidth, _interp - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dsp::BlackmanWindow(_taps, _outputSampleRate, cutoff, cutoff, _interp - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFilterParams(float passBand, float transWidth) {
|
||||||
|
stop();
|
||||||
|
dsp::BlackmanWindow(_taps, _outputSampleRate, passBand, transWidth, _interp - 1);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBlockSize(int blockSize) {
|
||||||
|
stop();
|
||||||
|
_blockSize = blockSize;
|
||||||
|
outputBlockSize = (_blockSize * _interp) / _decim;
|
||||||
|
output.setMaxLatency(outputBlockSize * 2);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInput(stream<float>* input) {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getOutputBlockSize() {
|
||||||
|
return outputBlockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream<float> output;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _worker(FloatPolyphaseFIRResampler* _this) {
|
||||||
|
float* inBuf = new float[_this->_blockSize];
|
||||||
|
float* outBuf = new float[_this->outputBlockSize];
|
||||||
|
|
||||||
|
int inCount = _this->_blockSize;
|
||||||
|
int outCount = _this->outputBlockSize;
|
||||||
|
|
||||||
|
int interp = _this->_interp;
|
||||||
|
int decim = _this->_decim;
|
||||||
|
float correction = interp;//(float)sqrt((float)interp);
|
||||||
|
|
||||||
|
int tapCount = _this->_taps.size();
|
||||||
|
float* taps = new float[tapCount];
|
||||||
|
for (int i = 0; i < tapCount; i++) {
|
||||||
|
taps[i] = _this->_taps[i] * correction;
|
||||||
|
}
|
||||||
|
|
||||||
|
float* delayBuf = new float[tapCount];
|
||||||
|
|
||||||
|
float* delayStart = &inBuf[std::max<int>(inCount - tapCount, 0)];
|
||||||
|
int delaySize = tapCount * sizeof(float);
|
||||||
|
float* delayBufEnd = &delayBuf[std::max<int>(tapCount - inCount, 0)];
|
||||||
|
int moveSize = std::min<int>(inCount, tapCount - inCount) * sizeof(float);
|
||||||
|
int inSize = inCount * sizeof(float);
|
||||||
|
|
||||||
|
int afterInterp = inCount * interp;
|
||||||
|
int outIndex = 0;
|
||||||
|
|
||||||
|
tapCount -= interp - 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (_this->_input->read(inBuf, inCount) < 0) { break; };
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < outCount; i++) {
|
||||||
|
outBuf[i] = 0;
|
||||||
|
int filterId = (i * decim) % interp;
|
||||||
|
int inputId = (i * decim) / interp;
|
||||||
|
for (int j = 0; j < tapCount; j++) {
|
||||||
|
outBuf[i] += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, inputId - j) * taps[j + filterId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (tapCount > inCount) {
|
||||||
|
memmove(delayBuf, delayBufEnd, moveSize);
|
||||||
|
memcpy(delayBufEnd, delayStart, inSize);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy(delayBuf, delayStart, delaySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_this->output.write(outBuf, _this->outputBlockSize) < 0) { break; };
|
||||||
|
}
|
||||||
|
delete[] inBuf;
|
||||||
|
delete[] outBuf;
|
||||||
|
delete[] delayBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread _workerThread;
|
||||||
|
|
||||||
|
stream<float>* _input;
|
||||||
|
std::vector<float> _taps;
|
||||||
|
int _interp;
|
||||||
|
int _decim;
|
||||||
|
int outputBlockSize;
|
||||||
|
float _outputSampleRate;
|
||||||
|
float _inputSampleRate;
|
||||||
|
int _blockSize;
|
||||||
|
bool running = false;
|
||||||
|
};
|
||||||
};
|
};
|
@ -141,7 +141,7 @@ void FrequencySelect::draw() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long freq = 0;
|
uint64_t freq = 0;
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
freq += digits[i] * pow(10, 11 - i);
|
freq += digits[i] * pow(10, 11 - i);
|
||||||
}
|
}
|
||||||
@ -151,9 +151,9 @@ void FrequencySelect::draw() {
|
|||||||
ImGui::NewLine();
|
ImGui::NewLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrequencySelect::setFrequency(long freq) {
|
void FrequencySelect::setFrequency(uint64_t freq) {
|
||||||
int i = 11;
|
int i = 11;
|
||||||
for (long f = freq; i >= 0; i--) {
|
for (uint64_t f = freq; i >= 0; i--) {
|
||||||
digits[i] = f % 10;
|
digits[i] = f % 10;
|
||||||
f -= digits[i];
|
f -= digits[i];
|
||||||
f /= 10;
|
f /= 10;
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
class FrequencySelect {
|
class FrequencySelect {
|
||||||
public:
|
public:
|
||||||
FrequencySelect();
|
FrequencySelect();
|
||||||
void init();
|
void init();
|
||||||
void draw();
|
void draw();
|
||||||
void setFrequency(long freq);
|
void setFrequency(uint64_t freq);
|
||||||
|
|
||||||
long frequency;
|
uint64_t frequency;
|
||||||
bool frequencyChanged = false;
|
bool frequencyChanged = false;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
66
src/main.cpp
66
src/main.cpp
@ -21,10 +21,21 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool maximized = false;
|
||||||
|
|
||||||
static void glfw_error_callback(int error, const char* description) {
|
static void glfw_error_callback(int error, const char* description) {
|
||||||
spdlog::error("Glfw Error {0}: {1}", error, description);
|
spdlog::error("Glfw Error {0}: {1}", error, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void maximized_callback(GLFWwindow* window, int n) {
|
||||||
|
if (n == GLFW_TRUE) {
|
||||||
|
maximized = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
maximized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
//FreeConsole();
|
//FreeConsole();
|
||||||
@ -32,6 +43,11 @@ int main() {
|
|||||||
|
|
||||||
spdlog::info("SDR++ v" VERSION_STR);
|
spdlog::info("SDR++ v" VERSION_STR);
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
spdlog::info("Loading config");
|
||||||
|
config::load("config.json");
|
||||||
|
config::startAutoSave();
|
||||||
|
|
||||||
// Setup window
|
// Setup window
|
||||||
glfwSetErrorCallback(glfw_error_callback);
|
glfwSetErrorCallback(glfw_error_callback);
|
||||||
if (!glfwInit()) {
|
if (!glfwInit()) {
|
||||||
@ -43,14 +59,23 @@ int main() {
|
|||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||||
|
|
||||||
|
int winWidth = config::config["windowSize"]["w"];
|
||||||
|
int winHeight = config::config["windowSize"]["h"];
|
||||||
|
maximized = config::config["maximized"];
|
||||||
|
|
||||||
// Create window with graphics context
|
// Create window with graphics context
|
||||||
GLFWwindow* window = glfwCreateWindow(1280, 720, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
GLFWwindow* window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||||
if (window == NULL)
|
if (window == NULL)
|
||||||
return 1;
|
return 1;
|
||||||
glfwMakeContextCurrent(window);
|
glfwMakeContextCurrent(window);
|
||||||
glfwSwapInterval(1); // Enable vsync
|
glfwSwapInterval(1); // Enable vsync
|
||||||
|
|
||||||
|
if (maximized) {
|
||||||
|
glfwMaximizeWindow(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
glfwSetWindowMaximizeCallback(window, maximized_callback);
|
||||||
|
|
||||||
// Load app icon
|
// Load app icon
|
||||||
GLFWimage icons[10];
|
GLFWimage icons[10];
|
||||||
icons[0].pixels = stbi_load("res/icons/sdrpp.png", &icons[0].width, &icons[0].height, 0, 4);
|
icons[0].pixels = stbi_load("res/icons/sdrpp.png", &icons[0].width, &icons[0].height, 0, 4);
|
||||||
@ -94,11 +119,6 @@ int main() {
|
|||||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||||
ImGui_ImplOpenGL3_Init("#version 150");
|
ImGui_ImplOpenGL3_Init("#version 150");
|
||||||
|
|
||||||
// Load config
|
|
||||||
spdlog::info("Loading config");
|
|
||||||
config::load("config.json");
|
|
||||||
config::startAutoSave();
|
|
||||||
|
|
||||||
style::setDarkStyle();
|
style::setDarkStyle();
|
||||||
|
|
||||||
spdlog::info("Loading icons");
|
spdlog::info("Loading icons");
|
||||||
@ -114,6 +134,8 @@ int main() {
|
|||||||
|
|
||||||
spdlog::info("Ready.");
|
spdlog::info("Ready.");
|
||||||
|
|
||||||
|
bool _maximized = maximized;
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
while (!glfwWindowShouldClose(window)) {
|
while (!glfwWindowShouldClose(window)) {
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
@ -123,15 +145,31 @@ int main() {
|
|||||||
ImGui_ImplGlfw_NewFrame();
|
ImGui_ImplGlfw_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
if (_maximized != maximized) {
|
||||||
|
_maximized = maximized;
|
||||||
|
config::config["maximized"]= _maximized;
|
||||||
|
config::configModified = true;
|
||||||
|
if (!maximized) {
|
||||||
|
glfwSetWindowSize(window, config::config["windowSize"]["w"], config::config["windowSize"]["h"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int wwidth, wheight;
|
int _winWidth, _winHeight;
|
||||||
glfwGetWindowSize(window, &wwidth, &wheight);
|
glfwGetWindowSize(window, &_winWidth, &_winHeight);
|
||||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(wwidth, wheight));
|
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
|
||||||
|
winWidth = _winWidth;
|
||||||
drawWindow();
|
winHeight = _winHeight;
|
||||||
|
config::config["windowSize"]["w"] = winWidth;
|
||||||
|
config::config["windowSize"]["h"] = winHeight;
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (winWidth > 0 && winHeight > 0) {
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
|
||||||
|
drawWindow();
|
||||||
|
}
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
|
@ -41,7 +41,7 @@ dsp::NullSink sink;
|
|||||||
int devId = 0;
|
int devId = 0;
|
||||||
int srId = 0;
|
int srId = 0;
|
||||||
watcher<int> bandplanId(0, true);
|
watcher<int> bandplanId(0, true);
|
||||||
watcher<long> freq(90500000L);
|
watcher<uint64_t> freq(90500000Ui64);
|
||||||
int demod = 1;
|
int demod = 1;
|
||||||
watcher<float> vfoFreq(92000000.0f);
|
watcher<float> vfoFreq(92000000.0f);
|
||||||
float dummyVolume = 1.0f;
|
float dummyVolume = 1.0f;
|
||||||
@ -57,6 +57,11 @@ watcher<bool> bandPlanEnabled(true, false);
|
|||||||
bool showCredits = false;
|
bool showCredits = false;
|
||||||
std::string audioStreamName = "";
|
std::string audioStreamName = "";
|
||||||
std::string sourceName = "";
|
std::string sourceName = "";
|
||||||
|
int menuWidth = 300;
|
||||||
|
bool grabbingMenu = false;
|
||||||
|
int newWidth = 300;
|
||||||
|
bool showWaterfall = true;
|
||||||
|
int fftHeight = 300;
|
||||||
|
|
||||||
void saveCurrentSource() {
|
void saveCurrentSource() {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -188,25 +193,23 @@ void windowInit() {
|
|||||||
}
|
}
|
||||||
if (!settingsFound) {
|
if (!settingsFound) {
|
||||||
sampleRate = soapy.getSampleRate();
|
sampleRate = soapy.getSampleRate();
|
||||||
|
sigPath.setSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
// Search for the first source in the list to have a config
|
// Search for the first source in the list to have a config
|
||||||
// If no pre-defined source, selected default device
|
// If no pre-defined source, selected default device
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load last band plan configuration
|
|
||||||
|
|
||||||
// TODO: Save/load config window size/fullscreen
|
|
||||||
// Also add a loading screen
|
// Also add a loading screen
|
||||||
// And a module add/remove/change order menu
|
// Adjustable "snap to grid" for each VFO
|
||||||
// get rid of watchers and use if() instead
|
// Finish the recorder module
|
||||||
// Add squelsh
|
// Add squelsh
|
||||||
// Bandwidth ajustment
|
// Bandwidth ajustment
|
||||||
// DSB / CW and RAW modes;
|
// DSB / CW and RAW modes;
|
||||||
// Write a recorder
|
|
||||||
// Adjustable "snap to grid" for each VFO
|
|
||||||
// Bring VFO to a visible place when changing sample rate if it's smaller
|
// Bring VFO to a visible place when changing sample rate if it's smaller
|
||||||
// Possibility to resize waterfall and menu
|
|
||||||
// Have a proper root directory
|
// Have a proper root directory
|
||||||
|
|
||||||
|
// And a module add/remove/change order menu
|
||||||
|
// get rid of watchers and use if() instead
|
||||||
// Switch to double for all frequecies and bandwidth
|
// Switch to double for all frequecies and bandwidth
|
||||||
|
|
||||||
// Update UI settings
|
// Update UI settings
|
||||||
@ -265,6 +268,17 @@ void windowInit() {
|
|||||||
if (audioStreamName != "") {
|
if (audioStreamName != "") {
|
||||||
volume = &audio::streams[audioStreamName]->volume;
|
volume = &audio::streams[audioStreamName]->volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menuWidth = config::config["menuWidth"];
|
||||||
|
newWidth = menuWidth;
|
||||||
|
|
||||||
|
showWaterfall = config::config["showWaterfall"];
|
||||||
|
if (!showWaterfall) {
|
||||||
|
wtf.hideWaterfall();
|
||||||
|
}
|
||||||
|
|
||||||
|
fftHeight = config::config["fftHeight"];
|
||||||
|
wtf.setFFTHeight(fftHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setVFO(float freq) {
|
void setVFO(float freq) {
|
||||||
@ -409,6 +423,13 @@ void drawWindow() {
|
|||||||
wtf.bandplan = bandPlanEnabled.val ? &bandplan::bandplans[bandplan::bandplanNames[bandplanId.val]] : NULL;
|
wtf.bandplan = bandPlanEnabled.val ? &bandplan::bandplans[bandplan::bandplanNames[bandplanId.val]] : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _fftHeight = wtf.getFFTHeight();
|
||||||
|
if (fftHeight != _fftHeight) {
|
||||||
|
fftHeight = _fftHeight;
|
||||||
|
config::config["fftHeight"] = fftHeight;
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
|
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
|
||||||
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
|
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
|
||||||
|
|
||||||
@ -466,10 +487,36 @@ void drawWindow() {
|
|||||||
showCredits = false;
|
showCredits = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Columns(3, "WindowColumns", false);
|
// Handle menu resize
|
||||||
|
float curY = ImGui::GetCursorPosY();
|
||||||
ImVec2 winSize = ImGui::GetWindowSize();
|
ImVec2 winSize = ImGui::GetWindowSize();
|
||||||
ImGui::SetColumnWidth(0, 300);
|
ImVec2 mousePos = ImGui::GetMousePos();
|
||||||
ImGui::SetColumnWidth(1, winSize.x - 300 - 60);
|
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||||
|
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||||
|
if (grabbingMenu) {
|
||||||
|
newWidth = mousePos.x;
|
||||||
|
newWidth = std::clamp<float>(newWidth, 250, winSize.x - 250);
|
||||||
|
ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive));
|
||||||
|
}
|
||||||
|
if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) {
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
|
||||||
|
if (click) {
|
||||||
|
grabbingMenu = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
|
||||||
|
}
|
||||||
|
if(!down && grabbingMenu) {
|
||||||
|
grabbingMenu = false;
|
||||||
|
menuWidth = newWidth;
|
||||||
|
config::config["menuWidth"] = menuWidth;
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Columns(3, "WindowColumns", false);
|
||||||
|
ImGui::SetColumnWidth(0, menuWidth);
|
||||||
|
ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60);
|
||||||
ImGui::SetColumnWidth(2, 60);
|
ImGui::SetColumnWidth(2, 60);
|
||||||
|
|
||||||
// Left Column
|
// Left Column
|
||||||
@ -668,7 +715,10 @@ void drawWindow() {
|
|||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("Display")) {
|
if (ImGui::CollapsingHeader("Display", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
|
if (ImGui::Checkbox("Show waterfall", &showWaterfall)) {
|
||||||
|
showWaterfall ? wtf.showWaterfall() : wtf.hideWaterfall();
|
||||||
|
}
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -693,6 +743,7 @@ void drawWindow() {
|
|||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
ImGui::BeginChild("WaterfallControls");
|
ImGui::BeginChild("WaterfallControls");
|
||||||
|
|
||||||
@ -735,6 +786,7 @@ void drawWindow() {
|
|||||||
wtf.setWaterfallMin(fftMin);
|
wtf.setWaterfallMin(fftMin);
|
||||||
wtf.setWaterfallMax(fftMax);
|
wtf.setWaterfallMax(fftMax);
|
||||||
|
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
if (showCredits) {
|
if (showCredits) {
|
||||||
|
@ -58,7 +58,9 @@ namespace vfoman {
|
|||||||
if (vfos.find(name) == vfos.end()) {
|
if (vfos.find(name) == vfos.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vfos[name].dspVFO->setOutputSampleRate(sampleRate, bandwidth);
|
VFO_t vfo = vfos[name];
|
||||||
|
vfo.dspVFO->setOutputSampleRate(sampleRate, bandwidth);
|
||||||
|
vfo.wtfVFO->setBandwidth(bandwidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setReference(std::string name, int ref){
|
void setReference(std::string name, int ref){
|
||||||
|
@ -83,7 +83,9 @@ namespace ImGui {
|
|||||||
fftMax = 0.0f;
|
fftMax = 0.0f;
|
||||||
waterfallMin = -70.0f;
|
waterfallMin = -70.0f;
|
||||||
waterfallMax = 0.0f;
|
waterfallMax = 0.0f;
|
||||||
fftHeight = 250;
|
FFTAreaHeight = 300;
|
||||||
|
newFFTAreaHeight = FFTAreaHeight;
|
||||||
|
fftHeight = FFTAreaHeight - 50;
|
||||||
dataWidth = 600;
|
dataWidth = 600;
|
||||||
lastWidgetPos.x = 0;
|
lastWidgetPos.x = 0;
|
||||||
lastWidgetPos.y = 0;
|
lastWidgetPos.y = 0;
|
||||||
@ -113,8 +115,8 @@ namespace ImGui {
|
|||||||
// Vertical scale
|
// Vertical scale
|
||||||
for (float line = startLine; line > fftMin; line -= vRange) {
|
for (float line = startLine; line > fftMin; line -= vRange) {
|
||||||
float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor);
|
float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor);
|
||||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, roundf(yPos)),
|
window->DrawList->AddLine(ImVec2(roundf(widgetPos.x + 50), roundf(yPos)),
|
||||||
ImVec2(widgetPos.x + dataWidth + 50, roundf(yPos)),
|
ImVec2(roundf(widgetPos.x + dataWidth + 50), roundf(yPos)),
|
||||||
IM_COL32(50, 50, 50, 255), 1.0f);
|
IM_COL32(50, 50, 50, 255), 1.0f);
|
||||||
sprintf(buf, "%d", (int)line);
|
sprintf(buf, "%d", (int)line);
|
||||||
ImVec2 txtSz = ImGui::CalcTextSize(buf);
|
ImVec2 txtSz = ImGui::CalcTextSize(buf);
|
||||||
@ -263,6 +265,9 @@ namespace ImGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WaterFall::updateWaterfallFb() {
|
void WaterFall::updateWaterfallFb() {
|
||||||
|
if (!waterfallVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
|
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
|
||||||
int drawDataSize;
|
int drawDataSize;
|
||||||
int drawDataStart;
|
int drawDataStart;
|
||||||
@ -357,14 +362,31 @@ namespace ImGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WaterFall::onResize() {
|
void WaterFall::onResize() {
|
||||||
|
// return if widget is too small
|
||||||
|
if (widgetSize.x < 100 || widgetSize.y < 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waterfallVisible) {
|
||||||
|
FFTAreaHeight = std::min<int>(FFTAreaHeight, widgetSize.y - 50);
|
||||||
|
fftHeight = FFTAreaHeight - 50;
|
||||||
|
waterfallHeight = widgetSize.y - fftHeight - 52;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fftHeight = widgetSize.y - 50;
|
||||||
|
}
|
||||||
dataWidth = widgetSize.x - 60.0f;
|
dataWidth = widgetSize.x - 60.0f;
|
||||||
waterfallHeight = widgetSize.y - fftHeight - 52;
|
|
||||||
delete[] latestFFT;
|
delete[] latestFFT;
|
||||||
delete[] waterfallFb;
|
|
||||||
|
if (waterfallVisible) {
|
||||||
|
delete[] waterfallFb;
|
||||||
|
}
|
||||||
|
|
||||||
latestFFT = new float[dataWidth];
|
latestFFT = new float[dataWidth];
|
||||||
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
|
if (waterfallVisible) {
|
||||||
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
|
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
|
||||||
|
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
|
||||||
|
}
|
||||||
for (int i = 0; i < dataWidth; i++) {
|
for (int i = 0; i < dataWidth; i++) {
|
||||||
latestFFT[i] = -1000.0f; // Hide everything
|
latestFFT[i] = -1000.0f; // Hide everything
|
||||||
}
|
}
|
||||||
@ -389,15 +411,8 @@ namespace ImGui {
|
|||||||
buf_mtx.lock();
|
buf_mtx.lock();
|
||||||
window = GetCurrentWindow();
|
window = GetCurrentWindow();
|
||||||
|
|
||||||
// Fix for weird ImGui bug
|
|
||||||
ImVec2 tmpWidgetEndPos = ImGui::GetWindowContentRegionMax();
|
|
||||||
if (tmpWidgetEndPos.x < 100 || tmpWidgetEndPos.y < fftHeight + 100) {
|
|
||||||
buf_mtx.unlock();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||||
widgetEndPos = tmpWidgetEndPos;
|
widgetEndPos = ImGui::GetWindowContentRegionMax();
|
||||||
widgetPos.x += window->Pos.x;
|
widgetPos.x += window->Pos.x;
|
||||||
widgetPos.y += window->Pos.y;
|
widgetPos.y += window->Pos.y;
|
||||||
widgetEndPos.x += window->Pos.x - 4; // Padding
|
widgetEndPos.x += window->Pos.x - 4; // Padding
|
||||||
@ -422,12 +437,44 @@ namespace ImGui {
|
|||||||
processInputs();
|
processInputs();
|
||||||
|
|
||||||
drawFFT();
|
drawFFT();
|
||||||
drawWaterfall();
|
if (waterfallVisible) {
|
||||||
|
drawWaterfall();
|
||||||
|
}
|
||||||
drawVFOs();
|
drawVFOs();
|
||||||
if (bandplan != NULL) {
|
if (bandplan != NULL) {
|
||||||
drawBandPlan();
|
drawBandPlan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!waterfallVisible) {
|
||||||
|
buf_mtx.unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle fft resize
|
||||||
|
ImVec2 winSize = ImGui::GetWindowSize();
|
||||||
|
ImVec2 mousePos = ImGui::GetMousePos();
|
||||||
|
mousePos.x -= widgetPos.x;
|
||||||
|
mousePos.y -= widgetPos.y;
|
||||||
|
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||||
|
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||||
|
if (draggingFW) {
|
||||||
|
newFFTAreaHeight = mousePos.y;
|
||||||
|
newFFTAreaHeight = std::clamp<float>(newFFTAreaHeight, 150, widgetSize.y - 50);
|
||||||
|
ImGui::GetForegroundDrawList()->AddLine(ImVec2(widgetPos.x, newFFTAreaHeight + widgetPos.y), ImVec2(widgetEndPos.x, newFFTAreaHeight + widgetPos.y),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_SeparatorActive));
|
||||||
|
}
|
||||||
|
if (mousePos.y >= newFFTAreaHeight - 2 && mousePos.y <= newFFTAreaHeight + 2 && mousePos.x > 0 && mousePos.x < widgetSize.x) {
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
|
||||||
|
if (click) {
|
||||||
|
draggingFW = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!down && draggingFW) {
|
||||||
|
draggingFW = false;
|
||||||
|
FFTAreaHeight = newFFTAreaHeight;
|
||||||
|
onResize();
|
||||||
|
}
|
||||||
|
|
||||||
buf_mtx.unlock();
|
buf_mtx.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,15 +490,18 @@ namespace ImGui {
|
|||||||
rawFFTs.resize(waterfallHeight);
|
rawFFTs.resize(waterfallHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
|
if (waterfallVisible) {
|
||||||
float pixel;
|
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
|
||||||
float dataRange = waterfallMax - waterfallMin;
|
float pixel;
|
||||||
for (int j = 0; j < dataWidth; j++) {
|
float dataRange = waterfallMax - waterfallMin;
|
||||||
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
for (int j = 0; j < dataWidth; j++) {
|
||||||
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
|
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||||
waterfallFb[j] = waterfallPallet[id];
|
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
|
||||||
|
waterfallFb[j] = waterfallPallet[id];
|
||||||
|
}
|
||||||
|
waterfallUpdate = true;
|
||||||
}
|
}
|
||||||
waterfallUpdate = true;
|
|
||||||
buf_mtx.unlock();
|
buf_mtx.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,5 +760,24 @@ namespace ImGui {
|
|||||||
window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255));
|
window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void WaterFall::showWaterfall() {
|
||||||
|
waterfallVisible = true;
|
||||||
|
onResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaterFall::hideWaterfall() {
|
||||||
|
waterfallVisible = false;
|
||||||
|
onResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaterFall::setFFTHeight(int height) {
|
||||||
|
FFTAreaHeight = height;
|
||||||
|
onResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
int WaterFall::getFFTHeight() {
|
||||||
|
return FFTAreaHeight;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,6 +87,12 @@ namespace ImGui {
|
|||||||
|
|
||||||
void selectFirstVFO();
|
void selectFirstVFO();
|
||||||
|
|
||||||
|
void showWaterfall();
|
||||||
|
void hideWaterfall();
|
||||||
|
|
||||||
|
void setFFTHeight(int height);
|
||||||
|
int getFFTHeight();
|
||||||
|
|
||||||
bool centerFreqMoved = false;
|
bool centerFreqMoved = false;
|
||||||
bool vfoFreqChanged = false;
|
bool vfoFreqChanged = false;
|
||||||
bool bandplanEnabled = false;
|
bool bandplanEnabled = false;
|
||||||
@ -175,5 +181,10 @@ namespace ImGui {
|
|||||||
|
|
||||||
uint32_t* waterfallFb;
|
uint32_t* waterfallFb;
|
||||||
|
|
||||||
|
bool draggingFW = false;
|
||||||
|
int FFTAreaHeight;
|
||||||
|
int newFFTAreaHeight;
|
||||||
|
|
||||||
|
bool waterfallVisible = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
Loading…
Reference in New Issue
Block a user