mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-11-06 10:47:34 +01:00
a LOT of new stuff
This commit is contained in:
parent
31a95031e4
commit
eadaf3ce6b
@ -29,6 +29,12 @@ include_directories(sdrpp "src/")
|
|||||||
include_directories(sdrpp "src/imgui")
|
include_directories(sdrpp "src/imgui")
|
||||||
file(GLOB SRC "src/*.cpp")
|
file(GLOB SRC "src/*.cpp")
|
||||||
file(GLOB IMGUI "src/imgui/*.cpp")
|
file(GLOB IMGUI "src/imgui/*.cpp")
|
||||||
|
|
||||||
|
# If on windows, set the executable icon
|
||||||
|
if (MSVC)
|
||||||
|
set(SRC ${SRC} "win32/resources.rc")
|
||||||
|
endif (MSVC)
|
||||||
|
|
||||||
add_executable(sdrpp ${SRC} ${IMGUI})
|
add_executable(sdrpp ${SRC} ${IMGUI})
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
|
5
config.json
Normal file
5
config.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"frequency": 90500000,
|
||||||
|
"source": "",
|
||||||
|
"sourceSettings": {}
|
||||||
|
}
|
@ -11,8 +11,8 @@ struct RadioContext_t {
|
|||||||
std::string name;
|
std::string name;
|
||||||
int demod = 1;
|
int demod = 1;
|
||||||
SigPath sigPath;
|
SigPath sigPath;
|
||||||
watcher<float> volume;
|
// watcher<float> volume;
|
||||||
watcher<int> audioDevice;
|
// 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 +21,24 @@ 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.val = 1.0f;
|
||||||
ctx->volume.markAsChanged();
|
// ctx->volume.markAsChanged();
|
||||||
API->bindVolumeVariable(&ctx->volume.val);
|
// API->bindVolumeVariable(&ctx->volume.val);
|
||||||
ctx->audioDevice.val = ctx->sigPath.audio.getDeviceId();
|
// ctx->audioDevice.val = ctx->sigPath.audio.getDeviceId();
|
||||||
ctx->audioDevice.changed(); // clear change
|
// 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()) {
|
// if (ctx->volume.changed()) {
|
||||||
ctx->sigPath.setVolume(ctx->volume.val);
|
// ctx->sigPath.setVolume(ctx->volume.val);
|
||||||
}
|
// }
|
||||||
if (ctx->audioDevice.changed()) {
|
// if (ctx->audioDevice.changed()) {
|
||||||
ctx->sigPath.audio.stop();
|
// ctx->sigPath.audio.stop();
|
||||||
ctx->sigPath.audio.setDevice(ctx->audioDevice.val);
|
// ctx->sigPath.audio.setDevice(ctx->audioDevice.val);
|
||||||
ctx->sigPath.audio.start();
|
// ctx->sigPath.audio.start();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
|
MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
|
||||||
@ -85,9 +85,9 @@ MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
|
|||||||
|
|
||||||
ImGui::EndGroup();
|
ImGui::EndGroup();
|
||||||
|
|
||||||
ImGui::PushItemWidth(ImGui::GetWindowSize().x);
|
// ImGui::PushItemWidth(ImGui::GetWindowSize().x);
|
||||||
ImGui::Combo(CONCAT("##_audio_dev_", ctx->name), &ctx->audioDevice.val, ctx->sigPath.audio.devTxtList.c_str());
|
// ImGui::Combo(CONCAT("##_audio_dev_", ctx->name), &ctx->audioDevice.val, ctx->sigPath.audio.devTxtList.c_str());
|
||||||
ImGui::PopItemWidth();
|
// ImGui::PopItemWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_EXPORT void _HANDLE_EVENT_(RadioContext_t* ctx, int eventId) {
|
MOD_EXPORT void _HANDLE_EVENT_(RadioContext_t* ctx, int eventId) {
|
||||||
@ -96,9 +96,9 @@ MOD_EXPORT void _HANDLE_EVENT_(RadioContext_t* ctx, int eventId) {
|
|||||||
ctx->sigPath.updateBlockSize();
|
ctx->sigPath.updateBlockSize();
|
||||||
}
|
}
|
||||||
else if (eventId == mod::EVENT_SELECTED_VFO_CHANGED) {
|
else if (eventId == mod::EVENT_SELECTED_VFO_CHANGED) {
|
||||||
if (API->getSelectedVFOName() == ctx->name) {
|
// if (API->getSelectedVFOName() == ctx->name) {
|
||||||
API->bindVolumeVariable(&ctx->volume.val);
|
// API->bindVolumeVariable(&ctx->volume.val);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,14 @@ SigPath::SigPath() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SigPath::sampleRateChangeHandler(void* ctx, float sampleRate) {
|
||||||
|
SigPath* _this = (SigPath*)ctx;
|
||||||
|
_this->audioResamp.stop();
|
||||||
|
_this->audioResamp.setOutputSampleRate(sampleRate, sampleRate / 2.0f, sampleRate / 2.0f);
|
||||||
|
_this->audioResamp.start();
|
||||||
|
return _this->audioResamp.getOutputBlockSize();
|
||||||
|
}
|
||||||
|
|
||||||
void SigPath::init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp::stream<dsp::complex_t>* input) {
|
void SigPath::init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp::stream<dsp::complex_t>* input) {
|
||||||
this->sampleRate = sampleRate;
|
this->sampleRate = sampleRate;
|
||||||
this->blockSize = blockSize;
|
this->blockSize = blockSize;
|
||||||
@ -18,7 +26,8 @@ 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);
|
||||||
audio.init(&audioResamp.output, 64);
|
API->registerMonoStream(&audioResamp.output, vfoName, vfoName, sampleRateChangeHandler, this);
|
||||||
|
API->setBlockSize(vfoName, audioResamp.getOutputBlockSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SigPath::setSampleRate(float sampleRate) {
|
void SigPath::setSampleRate(float sampleRate) {
|
||||||
@ -28,10 +37,6 @@ void SigPath::setSampleRate(float sampleRate) {
|
|||||||
setDemodulator(_demod);
|
setDemodulator(_demod);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SigPath::setVolume(float volume) {
|
|
||||||
audio.setVolume(volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SigPath::setDemodulator(int demId) {
|
void SigPath::setDemodulator(int demId) {
|
||||||
if (demId < 0 || demId >= _DEMOD_COUNT) {
|
if (demId < 0 || demId >= _DEMOD_COUNT) {
|
||||||
return;
|
return;
|
||||||
@ -64,7 +69,7 @@ void SigPath::setDemodulator(int demId) {
|
|||||||
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));
|
audioResamp.setInputSampleRate(200000, API->getVFOOutputBlockSize(vfoName), 15000, 15000);
|
||||||
demod.start();
|
demod.start();
|
||||||
}
|
}
|
||||||
if (demId == DEMOD_NFM) {
|
if (demId == DEMOD_NFM) {
|
||||||
@ -110,10 +115,5 @@ void SigPath::updateBlockSize() {
|
|||||||
void SigPath::start() {
|
void SigPath::start() {
|
||||||
demod.start();
|
demod.start();
|
||||||
audioResamp.start();
|
audioResamp.start();
|
||||||
audio.start();
|
API->startStream(vfoName);
|
||||||
}
|
|
||||||
|
|
||||||
void SigPath::DEBUG_TEST() {
|
|
||||||
audio.stop();
|
|
||||||
audio.start();
|
|
||||||
}
|
}
|
@ -19,16 +19,11 @@ public:
|
|||||||
void setSampleRate(float sampleRate);
|
void setSampleRate(float sampleRate);
|
||||||
|
|
||||||
void setVFOFrequency(long frequency);
|
void setVFOFrequency(long frequency);
|
||||||
void setVolume(float volume);
|
|
||||||
|
|
||||||
void updateBlockSize();
|
void updateBlockSize();
|
||||||
|
|
||||||
void setDemodulator(int demod);
|
void setDemodulator(int demod);
|
||||||
|
|
||||||
void DEBUG_TEST();
|
|
||||||
|
|
||||||
io::AudioSink audio;
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
DEMOD_FM,
|
DEMOD_FM,
|
||||||
DEMOD_NFM,
|
DEMOD_NFM,
|
||||||
@ -39,6 +34,8 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static int sampleRateChangeHandler(void* ctx, float sampleRate);
|
||||||
|
|
||||||
dsp::stream<dsp::complex_t> input;
|
dsp::stream<dsp::complex_t> input;
|
||||||
|
|
||||||
// Demodulators
|
// Demodulators
|
||||||
|
@ -71,7 +71,7 @@ I will soon publish a contributing.md listing the code style to use.
|
|||||||
# Credits
|
# Credits
|
||||||
## Libaries used
|
## Libaries used
|
||||||
* [SoapySDR (PothosWare)](https://github.com/pothosware/SoapySDR)
|
* [SoapySDR (PothosWare)](https://github.com/pothosware/SoapySDR)
|
||||||
* [ImGui (ocornut)](https://github.com/ocornut/imgui)
|
* [Dear ImGui (ocornut)](https://github.com/ocornut/imgui)
|
||||||
* [spdlog (gabime)](https://github.com/gabime/spdlog)
|
* [spdlog (gabime)](https://github.com/gabime/spdlog)
|
||||||
* [json (nlohmann)](https://github.com/nlohmann/json)
|
* [json (nlohmann)](https://github.com/nlohmann/json)
|
||||||
* [portaudio (PortAudio community)](http://www.portaudio.com/)
|
* [portaudio (PortAudio community)](http://www.portaudio.com/)
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 39 KiB |
BIN
res/icons/play.png
Normal file
BIN
res/icons/play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
res/icons/sdrpp.ico
Normal file
BIN
res/icons/sdrpp.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
BIN
res/icons/sdrpp.png
Normal file
BIN
res/icons/sdrpp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
res/icons/stop.png
Normal file
BIN
res/icons/stop.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
257
src/audio.cpp
257
src/audio.cpp
@ -1 +1,258 @@
|
|||||||
#include <audio.h>
|
#include <audio.h>
|
||||||
|
|
||||||
|
namespace audio {
|
||||||
|
std::map<std::string, AudioStream_t*> streams;
|
||||||
|
|
||||||
|
float registerMonoStream(dsp::stream<float>* stream, std::string name, std::string vfoName, int (*sampleRateChangeHandler)(void* ctx, float sampleRate), void* ctx) {
|
||||||
|
AudioStream_t* astr = new AudioStream_t;
|
||||||
|
astr->type = STREAM_TYPE_MONO;
|
||||||
|
astr->ctx = ctx;
|
||||||
|
astr->audio = new io::AudioSink;
|
||||||
|
astr->audio->init(1);
|
||||||
|
astr->deviceId = astr->audio->getDeviceId();
|
||||||
|
float sampleRate = astr->audio->devices[astr->deviceId].sampleRates[0];
|
||||||
|
int blockSize = sampleRate / 200; // default block size
|
||||||
|
astr->monoAudioStream = new dsp::stream<float>(blockSize * 2);
|
||||||
|
astr->audio->setBlockSize(blockSize);
|
||||||
|
astr->audio->setStreamType(io::AudioSink::MONO);
|
||||||
|
astr->audio->setMonoInput(astr->monoAudioStream);
|
||||||
|
astr->audio->setSampleRate(sampleRate);
|
||||||
|
astr->blockSize = blockSize;
|
||||||
|
astr->sampleRate = sampleRate;
|
||||||
|
astr->monoStream = stream;
|
||||||
|
astr->sampleRateChangeHandler = sampleRateChangeHandler;
|
||||||
|
astr->monoDynSplit = new dsp::DynamicSplitter<float>(stream, blockSize);
|
||||||
|
astr->monoDynSplit->bind(astr->monoAudioStream);
|
||||||
|
astr->running = false;
|
||||||
|
astr->volume = 1.0f;
|
||||||
|
astr->sampleRateId = 0;
|
||||||
|
astr->vfoName = vfoName;
|
||||||
|
streams[name] = astr;
|
||||||
|
return sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
float registerStereoStream(dsp::stream<dsp::StereoFloat_t>* stream, std::string name, std::string vfoName, int (*sampleRateChangeHandler)(void* ctx, float sampleRate), void* ctx) {
|
||||||
|
AudioStream_t* astr = new AudioStream_t;
|
||||||
|
astr->type = STREAM_TYPE_STEREO;
|
||||||
|
astr->ctx = ctx;
|
||||||
|
astr->audio = new io::AudioSink;
|
||||||
|
astr->audio->init(1);
|
||||||
|
float sampleRate = astr->audio->devices[astr->audio->getDeviceId()].sampleRates[0];
|
||||||
|
int blockSize = sampleRate / 200; // default block size
|
||||||
|
astr->stereoAudioStream = new dsp::stream<dsp::StereoFloat_t>(blockSize * 2);
|
||||||
|
astr->audio->setBlockSize(blockSize);
|
||||||
|
astr->audio->setStreamType(io::AudioSink::STEREO);
|
||||||
|
astr->audio->setStereoInput(astr->stereoAudioStream);
|
||||||
|
astr->audio->setSampleRate(sampleRate);
|
||||||
|
astr->blockSize = blockSize;
|
||||||
|
astr->sampleRate = sampleRate;
|
||||||
|
astr->stereoStream = stream;
|
||||||
|
astr->sampleRateChangeHandler = sampleRateChangeHandler;
|
||||||
|
astr->stereoDynSplit = new dsp::DynamicSplitter<dsp::StereoFloat_t>(stream, blockSize);
|
||||||
|
astr->stereoDynSplit->bind(astr->stereoAudioStream);
|
||||||
|
astr->running = false;
|
||||||
|
streams[name] = astr;
|
||||||
|
astr->vfoName = vfoName;
|
||||||
|
return sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startStream(std::string name) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
if (astr->running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (astr->type == STREAM_TYPE_MONO) {
|
||||||
|
astr->monoDynSplit->start();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
astr->stereoDynSplit->start();
|
||||||
|
}
|
||||||
|
astr->audio->start();
|
||||||
|
astr->running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopStream(std::string name) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
if (!astr->running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (astr->type == STREAM_TYPE_MONO) {
|
||||||
|
astr->monoDynSplit->stop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
astr->stereoDynSplit->stop();
|
||||||
|
}
|
||||||
|
astr->audio->stop();
|
||||||
|
astr->running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeStream(std::string name) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
stopStream(name);
|
||||||
|
for (int i = 0; i < astr->boundStreams.size(); i++) {
|
||||||
|
astr->boundStreams[i].streamRemovedHandler(astr->ctx);
|
||||||
|
}
|
||||||
|
delete astr->monoDynSplit;
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp::stream<float>* bindToStreamMono(std::string name, void (*streamRemovedHandler)(void* ctx), void (*sampleRateChangeHandler)(void* ctx, float sampleRate, int blockSize), void* ctx) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
BoundStream_t bstr;
|
||||||
|
bstr.type = STREAM_TYPE_MONO;
|
||||||
|
bstr.ctx = ctx;
|
||||||
|
bstr.streamRemovedHandler = streamRemovedHandler;
|
||||||
|
bstr.sampleRateChangeHandler = sampleRateChangeHandler;
|
||||||
|
if (astr->type == STREAM_TYPE_MONO) {
|
||||||
|
bstr.monoStream = new dsp::stream<float>(astr->blockSize * 2);
|
||||||
|
astr->monoDynSplit->bind(bstr.monoStream);
|
||||||
|
return bstr.monoStream;
|
||||||
|
}
|
||||||
|
bstr.stereoStream = new dsp::stream<dsp::StereoFloat_t>(astr->blockSize * 2);
|
||||||
|
bstr.s2m = new dsp::StereoToMono(bstr.stereoStream, astr->blockSize * 2);
|
||||||
|
bstr.monoStream = &bstr.s2m->output;
|
||||||
|
astr->stereoDynSplit->bind(bstr.stereoStream);
|
||||||
|
bstr.s2m->start();
|
||||||
|
return bstr.monoStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp::stream<dsp::StereoFloat_t>* bindToStreamStereo(std::string name, void (*streamRemovedHandler)(void* ctx), void (*sampleRateChangeHandler)(void* ctx, float sampleRate, int blockSize), void* ctx) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
BoundStream_t bstr;
|
||||||
|
bstr.type = STREAM_TYPE_STEREO;
|
||||||
|
bstr.ctx = ctx;
|
||||||
|
bstr.streamRemovedHandler = streamRemovedHandler;
|
||||||
|
bstr.sampleRateChangeHandler = sampleRateChangeHandler;
|
||||||
|
if (astr->type == STREAM_TYPE_STEREO) {
|
||||||
|
bstr.stereoStream = new dsp::stream<dsp::StereoFloat_t>(astr->blockSize * 2);
|
||||||
|
astr->stereoDynSplit->bind(bstr.stereoStream);
|
||||||
|
return bstr.stereoStream;
|
||||||
|
}
|
||||||
|
bstr.monoStream = new dsp::stream<float>(astr->blockSize * 2);
|
||||||
|
bstr.m2s = new dsp::MonoToStereo(bstr.monoStream, astr->blockSize * 2);
|
||||||
|
bstr.stereoStream = &bstr.m2s->output;
|
||||||
|
astr->monoDynSplit->bind(bstr.monoStream);
|
||||||
|
bstr.m2s->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBlockSize(std::string name, int blockSize) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
if (astr->running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (astr->type == STREAM_TYPE_MONO) {
|
||||||
|
astr->monoDynSplit->setBlockSize(blockSize);
|
||||||
|
for (int i = 0; i < astr->boundStreams.size(); i++) {
|
||||||
|
BoundStream_t bstr = astr->boundStreams[i];
|
||||||
|
bstr.monoStream->setMaxLatency(blockSize * 2);
|
||||||
|
if (bstr.type == STREAM_TYPE_STEREO) {
|
||||||
|
bstr.m2s->stop();
|
||||||
|
bstr.m2s->setBlockSize(blockSize);
|
||||||
|
bstr.m2s->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
astr->blockSize = blockSize;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
astr->monoDynSplit->setBlockSize(blockSize);
|
||||||
|
for (int i = 0; i < astr->boundStreams.size(); i++) {
|
||||||
|
BoundStream_t bstr = astr->boundStreams[i];
|
||||||
|
bstr.stereoStream->setMaxLatency(blockSize * 2);
|
||||||
|
if (bstr.type == STREAM_TYPE_MONO) {
|
||||||
|
bstr.s2m->stop();
|
||||||
|
bstr.s2m->setBlockSize(blockSize);
|
||||||
|
bstr.s2m->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
astr->blockSize = blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unbindFromStreamMono(std::string name, dsp::stream<float>* stream) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
for (int i = 0; i < astr->boundStreams.size(); i++) {
|
||||||
|
BoundStream_t bstr = astr->boundStreams[i];
|
||||||
|
if (bstr.monoStream != stream) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (astr->type == STREAM_TYPE_STEREO) {
|
||||||
|
bstr.s2m->stop();
|
||||||
|
delete bstr.s2m;
|
||||||
|
}
|
||||||
|
delete stream;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unbindFromStreamStereo(std::string name, dsp::stream<dsp::StereoFloat_t>* stream) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
for (int i = 0; i < astr->boundStreams.size(); i++) {
|
||||||
|
BoundStream_t bstr = astr->boundStreams[i];
|
||||||
|
if (bstr.stereoStream != stream) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (astr->type == STREAM_TYPE_MONO) {
|
||||||
|
bstr.s2m->stop();
|
||||||
|
delete bstr.m2s;
|
||||||
|
}
|
||||||
|
delete stream;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getNameFromVFO(std::string vfoName) {
|
||||||
|
for (auto const& [name, stream] : streams) {
|
||||||
|
if (stream->vfoName == vfoName) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSampleRate(std::string name, float sampleRate) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
if (astr->running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int blockSize = astr->sampleRateChangeHandler(astr->ctx, sampleRate);
|
||||||
|
astr->audio->setSampleRate(sampleRate);
|
||||||
|
astr->audio->setBlockSize(blockSize);
|
||||||
|
if (astr->type == STREAM_TYPE_MONO) {
|
||||||
|
astr->monoDynSplit->setBlockSize(blockSize);
|
||||||
|
for (int i = 0; i < astr->boundStreams.size(); i++) {
|
||||||
|
BoundStream_t bstr = astr->boundStreams[i];
|
||||||
|
if (bstr.type == STREAM_TYPE_STEREO) {
|
||||||
|
bstr.m2s->stop();
|
||||||
|
bstr.m2s->setBlockSize(blockSize);
|
||||||
|
bstr.sampleRateChangeHandler(bstr.ctx, sampleRate, blockSize);
|
||||||
|
bstr.m2s->start();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bstr.sampleRateChangeHandler(bstr.ctx, sampleRate, blockSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
astr->stereoDynSplit->setBlockSize(blockSize);
|
||||||
|
for (int i = 0; i < astr->boundStreams.size(); i++) {
|
||||||
|
BoundStream_t bstr = astr->boundStreams[i];
|
||||||
|
if (bstr.type == STREAM_TYPE_MONO) {
|
||||||
|
bstr.s2m->stop();
|
||||||
|
bstr.s2m->setBlockSize(blockSize);
|
||||||
|
bstr.sampleRateChangeHandler(bstr.ctx, sampleRate, blockSize);
|
||||||
|
bstr.s2m->start();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bstr.sampleRateChangeHandler(bstr.ctx, sampleRate, blockSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAudioDevice(std::string name, int deviceId, float sampleRate) {
|
||||||
|
AudioStream_t* astr = streams[name];
|
||||||
|
if (astr->running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
astr->deviceId = deviceId;
|
||||||
|
astr->audio->setDevice(deviceId);
|
||||||
|
setSampleRate(name, sampleRate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
59
src/audio.h
59
src/audio.h
@ -1,6 +1,63 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <dsp/stream.h>
|
#include <dsp/stream.h>
|
||||||
|
#include <dsp/routing.h>
|
||||||
|
#include <io/audio.h>
|
||||||
|
#include <map>
|
||||||
|
#include <watcher.h>
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
void registerStream(dsp::stream<float>* stream, std::string name, std::string vfoName);
|
enum {
|
||||||
|
STREAM_TYPE_MONO,
|
||||||
|
STREAM_TYPE_STEREO,
|
||||||
|
_STREAM_TYPE_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoundStream_t {
|
||||||
|
dsp::stream<float>* monoStream;
|
||||||
|
dsp::stream<dsp::StereoFloat_t>* stereoStream;
|
||||||
|
dsp::StereoToMono* s2m;
|
||||||
|
dsp::MonoToStereo* m2s;
|
||||||
|
void (*streamRemovedHandler)(void* ctx);
|
||||||
|
void (*sampleRateChangeHandler)(void* ctx, float sampleRate, int blockSize);
|
||||||
|
void* ctx;
|
||||||
|
int type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioStream_t {
|
||||||
|
io::AudioSink* audio;
|
||||||
|
dsp::stream<float>* monoAudioStream;
|
||||||
|
dsp::stream<dsp::StereoFloat_t>* stereoAudioStream;
|
||||||
|
std::vector<BoundStream_t> boundStreams;
|
||||||
|
dsp::stream<float>* monoStream;
|
||||||
|
dsp::DynamicSplitter<float>* monoDynSplit;
|
||||||
|
dsp::stream<dsp::StereoFloat_t>* stereoStream;
|
||||||
|
dsp::DynamicSplitter<dsp::StereoFloat_t>* stereoDynSplit;
|
||||||
|
int (*sampleRateChangeHandler)(void* ctx, float sampleRate);
|
||||||
|
float sampleRate;
|
||||||
|
int blockSize;
|
||||||
|
int type;
|
||||||
|
bool running = false;
|
||||||
|
float volume;
|
||||||
|
int sampleRateId;
|
||||||
|
int deviceId;
|
||||||
|
void* ctx;
|
||||||
|
std::string vfoName;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern std::map<std::string, AudioStream_t*> streams;
|
||||||
|
|
||||||
|
float registerMonoStream(dsp::stream<float>* stream, std::string name, std::string vfoName, int (*sampleRateChangeHandler)(void* ctx, float sampleRate), void* ctx);
|
||||||
|
float registerStereoStream(dsp::stream<dsp::StereoFloat_t>* stream, std::string name, std::string vfoName, int (*sampleRateChangeHandler)(void* ctx, float sampleRate), void* ctx);
|
||||||
|
void startStream(std::string name);
|
||||||
|
void stopStream(std::string name);
|
||||||
|
void removeStream(std::string name);
|
||||||
|
dsp::stream<float>* bindToStreamMono(std::string name, void (*streamRemovedHandler)(void* ctx), void (*sampleRateChangeHandler)(void* ctx, float sampleRate, int blockSize), void* ctx);
|
||||||
|
dsp::stream<dsp::StereoFloat_t>* bindToStreamStereo(std::string name, void (*streamRemovedHandler)(void* ctx), void (*sampleRateChangeHandler)(void* ctx, float sampleRate, int blockSize), void* ctx);
|
||||||
|
void setBlockSize(std::string name, int blockSize);
|
||||||
|
void unbindFromStreamMono(std::string name, dsp::stream<float>* stream);
|
||||||
|
void unbindFromStreamStereo(std::string name, dsp::stream<dsp::StereoFloat_t>* stream);
|
||||||
|
std::string getNameFromVFO(std::string vfoName);
|
||||||
|
void setSampleRate(std::string name, float sampleRate);
|
||||||
|
void setAudioDevice(std::string name, int deviceId, float sampleRate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
53
src/config.cpp
Normal file
53
src/config.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include <config.h>
|
||||||
|
|
||||||
|
namespace config {
|
||||||
|
bool configModified = false;
|
||||||
|
json config;
|
||||||
|
bool autoSaveRunning = false;
|
||||||
|
std::string _path;
|
||||||
|
std::thread _workerThread;
|
||||||
|
|
||||||
|
void _autoSaveWorker() {
|
||||||
|
while (autoSaveRunning) {
|
||||||
|
if (configModified) {
|
||||||
|
configModified = false;
|
||||||
|
std::ofstream file(_path.c_str());
|
||||||
|
file << std::setw(4) << config;
|
||||||
|
file.close();
|
||||||
|
spdlog::info("Config saved");
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(std::string path) {
|
||||||
|
if (!std::filesystem::exists(path)) {
|
||||||
|
spdlog::error("Config file does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!std::filesystem::is_regular_file(path)) {
|
||||||
|
spdlog::error("Config file isn't a file...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_path = path;
|
||||||
|
std::ifstream file(path.c_str());
|
||||||
|
config << file;
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void startAutoSave() {
|
||||||
|
if (autoSaveRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
autoSaveRunning = true;
|
||||||
|
_workerThread = std::thread(_autoSaveWorker);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopAutoSave() {
|
||||||
|
if (!autoSaveRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
autoSaveRunning = false;
|
||||||
|
_workerThread.join();
|
||||||
|
}
|
||||||
|
};
|
20
src/config.h
Normal file
20
src/config.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <json.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
namespace config {
|
||||||
|
void load(std::string path);
|
||||||
|
void startAutoSave();
|
||||||
|
void stopAutoSave();
|
||||||
|
|
||||||
|
extern bool configModified;
|
||||||
|
extern json config;
|
||||||
|
};
|
@ -313,8 +313,16 @@ namespace dsp {
|
|||||||
int inCount = _this->_blockSize;
|
int inCount = _this->_blockSize;
|
||||||
int outCount = _this->outputBlockSize;
|
int outCount = _this->outputBlockSize;
|
||||||
|
|
||||||
float* taps = _this->_taps.data();
|
int interp = _this->_interp;
|
||||||
|
int decim = _this->_decim;
|
||||||
|
float correction = interp;//(float)sqrt((float)interp);
|
||||||
|
|
||||||
int tapCount = _this->_taps.size();
|
int tapCount = _this->_taps.size();
|
||||||
|
float* taps = new float[tapCount];
|
||||||
|
for (int i = 0; i < tapCount; i++) {
|
||||||
|
taps[i] = _this->_taps[i] * correction;
|
||||||
|
}
|
||||||
|
|
||||||
complex_t* delayBuf = new complex_t[tapCount];
|
complex_t* delayBuf = new complex_t[tapCount];
|
||||||
|
|
||||||
complex_t* delayStart = &inBuf[std::max<int>(inCount - tapCount, 0)];
|
complex_t* delayStart = &inBuf[std::max<int>(inCount - tapCount, 0)];
|
||||||
@ -323,11 +331,6 @@ namespace dsp {
|
|||||||
int moveSize = std::min<int>(inCount, tapCount - inCount) * sizeof(complex_t);
|
int moveSize = std::min<int>(inCount, tapCount - inCount) * sizeof(complex_t);
|
||||||
int inSize = inCount * sizeof(complex_t);
|
int inSize = inCount * sizeof(complex_t);
|
||||||
|
|
||||||
int interp = _this->_interp;
|
|
||||||
int decim = _this->_decim;
|
|
||||||
|
|
||||||
float correction = (float)sqrt((float)interp);
|
|
||||||
|
|
||||||
int afterInterp = inCount * interp;
|
int afterInterp = inCount * interp;
|
||||||
int outIndex = 0;
|
int outIndex = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -335,12 +338,9 @@ namespace dsp {
|
|||||||
for (int i = 0; outIndex < outCount; i += decim) {
|
for (int i = 0; outIndex < outCount; i += decim) {
|
||||||
outBuf[outIndex].i = 0;
|
outBuf[outIndex].i = 0;
|
||||||
outBuf[outIndex].q = 0;
|
outBuf[outIndex].q = 0;
|
||||||
for (int j = 0; j < tapCount; j++) {
|
for (int j = i % interp; j < tapCount; j += interp) {
|
||||||
if ((i - j) % interp != 0) {
|
outBuf[outIndex].i += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, (i - j) / interp).i * taps[j];
|
||||||
continue;
|
outBuf[outIndex].q += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, (i - j) / interp).q * taps[j];
|
||||||
}
|
|
||||||
outBuf[outIndex].i += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, (i - j) / interp).i * taps[j] * correction;
|
|
||||||
outBuf[outIndex].q += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, (i - j) / interp).q * taps[j] * correction;
|
|
||||||
}
|
}
|
||||||
outIndex++;
|
outIndex++;
|
||||||
}
|
}
|
||||||
@ -358,6 +358,7 @@ namespace dsp {
|
|||||||
delete[] inBuf;
|
delete[] inBuf;
|
||||||
delete[] outBuf;
|
delete[] outBuf;
|
||||||
delete[] delayBuf;
|
delete[] delayBuf;
|
||||||
|
delete[] taps;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread _workerThread;
|
std::thread _workerThread;
|
||||||
@ -501,8 +502,16 @@ namespace dsp {
|
|||||||
int inCount = _this->_blockSize;
|
int inCount = _this->_blockSize;
|
||||||
int outCount = _this->outputBlockSize;
|
int outCount = _this->outputBlockSize;
|
||||||
|
|
||||||
float* taps = _this->_taps.data();
|
int interp = _this->_interp;
|
||||||
|
int decim = _this->_decim;
|
||||||
|
float correction = interp;//(float)sqrt((float)interp);
|
||||||
|
|
||||||
int tapCount = _this->_taps.size();
|
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* delayBuf = new float[tapCount];
|
||||||
|
|
||||||
float* delayStart = &inBuf[std::max<int>(inCount - tapCount, 0)];
|
float* delayStart = &inBuf[std::max<int>(inCount - tapCount, 0)];
|
||||||
@ -511,25 +520,22 @@ namespace dsp {
|
|||||||
int moveSize = std::min<int>(inCount, tapCount - inCount) * sizeof(float);
|
int moveSize = std::min<int>(inCount, tapCount - inCount) * sizeof(float);
|
||||||
int inSize = inCount * sizeof(float);
|
int inSize = inCount * sizeof(float);
|
||||||
|
|
||||||
int interp = _this->_interp;
|
|
||||||
int decim = _this->_decim;
|
|
||||||
|
|
||||||
float correction = (float)sqrt((float)interp);
|
|
||||||
|
|
||||||
int afterInterp = inCount * interp;
|
int afterInterp = inCount * interp;
|
||||||
int outIndex = 0;
|
int outIndex = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (_this->_input->read(inBuf, inCount) < 0) { break; };
|
if (_this->_input->read(inBuf, inCount) < 0) { break; };
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; outIndex < outCount; i += decim) {
|
for (int i = 0; outIndex < outCount; i += decim) {
|
||||||
outBuf[outIndex] = 0;
|
outBuf[outIndex] = 0;
|
||||||
for (int j = 0; j < tapCount; j++) {
|
for (int j = (i % interp); j < tapCount; j += interp) {
|
||||||
if ((i - j) % interp != 0) {
|
outBuf[outIndex] += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, (i - j) / interp) * taps[j];
|
||||||
continue;
|
|
||||||
}
|
|
||||||
outBuf[outIndex] += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, (i - j) / interp) * taps[j] * correction;
|
|
||||||
}
|
}
|
||||||
outIndex++;
|
outIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
outIndex = 0;
|
outIndex = 0;
|
||||||
if (tapCount > inCount) {
|
if (tapCount > inCount) {
|
||||||
memmove(delayBuf, delayBufEnd, moveSize);
|
memmove(delayBuf, delayBufEnd, moveSize);
|
||||||
|
@ -76,18 +76,19 @@ namespace dsp {
|
|||||||
bool running = false;
|
bool running = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
class DynamicSplitter {
|
class DynamicSplitter {
|
||||||
public:
|
public:
|
||||||
DynamicSplitter() {
|
DynamicSplitter() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicSplitter(stream<complex_t>* input, int bufferSize) {
|
DynamicSplitter(stream<T>* input, int bufferSize) {
|
||||||
_in = input;
|
_in = input;
|
||||||
_bufferSize = bufferSize;
|
_bufferSize = bufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(stream<complex_t>* input, int bufferSize) {
|
void init(stream<T>* input, int bufferSize) {
|
||||||
_in = input;
|
_in = input;
|
||||||
_bufferSize = bufferSize;
|
_bufferSize = bufferSize;
|
||||||
}
|
}
|
||||||
@ -128,14 +129,14 @@ namespace dsp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void bind(stream<complex_t>* stream) {
|
void bind(stream<T>* stream) {
|
||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
outputs.push_back(stream);
|
outputs.push_back(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void unbind(stream<complex_t>* stream) {
|
void unbind(stream<T>* stream) {
|
||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -150,7 +151,7 @@ namespace dsp {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static void _worker(DynamicSplitter* _this) {
|
static void _worker(DynamicSplitter* _this) {
|
||||||
complex_t* buf = new complex_t[_this->_bufferSize];
|
T* buf = new T[_this->_bufferSize];
|
||||||
int outputCount = _this->outputs.size();
|
int outputCount = _this->outputs.size();
|
||||||
while (true) {
|
while (true) {
|
||||||
if (_this->_in->read(buf, _this->_bufferSize) < 0) { break; };
|
if (_this->_in->read(buf, _this->_bufferSize) < 0) { break; };
|
||||||
@ -161,10 +162,146 @@ namespace dsp {
|
|||||||
delete[] buf;
|
delete[] buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream<complex_t>* _in;
|
stream<T>* _in;
|
||||||
|
int _bufferSize;
|
||||||
|
std::thread _workerThread;
|
||||||
|
bool running = false;
|
||||||
|
std::vector<stream<T>*> outputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class MonoToStereo {
|
||||||
|
public:
|
||||||
|
MonoToStereo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MonoToStereo(stream<float>* input, int bufferSize) {
|
||||||
|
_in = input;
|
||||||
|
_bufferSize = bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(stream<float>* input, int bufferSize) {
|
||||||
|
_in = input;
|
||||||
|
_bufferSize = bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream<StereoFloat_t> output;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _worker(MonoToStereo* _this) {
|
||||||
|
float* inBuf = new float[_this->_bufferSize];
|
||||||
|
StereoFloat_t* outBuf = new StereoFloat_t[_this->_bufferSize];
|
||||||
|
while (true) {
|
||||||
|
if (_this->_in->read(inBuf, _this->_bufferSize) < 0) { break; };
|
||||||
|
for (int i = 0; i < _this->_bufferSize; i++) {
|
||||||
|
outBuf[i].l = inBuf[i];
|
||||||
|
outBuf[i].r = inBuf[i];
|
||||||
|
}
|
||||||
|
if (_this->output.write(outBuf, _this->_bufferSize) < 0) { break; };
|
||||||
|
}
|
||||||
|
delete[] inBuf;
|
||||||
|
delete[] outBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream<float>* _in;
|
||||||
|
int _bufferSize;
|
||||||
|
std::thread _workerThread;
|
||||||
|
bool running = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StereoToMono {
|
||||||
|
public:
|
||||||
|
StereoToMono() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
StereoToMono(stream<StereoFloat_t>* input, int bufferSize) {
|
||||||
|
_in = input;
|
||||||
|
_bufferSize = bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(stream<StereoFloat_t>* input, int bufferSize) {
|
||||||
|
_in = input;
|
||||||
|
_bufferSize = bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream<float> output;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _worker(StereoToMono* _this) {
|
||||||
|
StereoFloat_t* inBuf = new StereoFloat_t[_this->_bufferSize];
|
||||||
|
float* outBuf = new float[_this->_bufferSize];
|
||||||
|
while (true) {
|
||||||
|
if (_this->_in->read(inBuf, _this->_bufferSize) < 0) { break; };
|
||||||
|
for (int i = 0; i < _this->_bufferSize; i++) {
|
||||||
|
outBuf[i] = (inBuf[i].l + inBuf[i].r) / 2.0f;
|
||||||
|
}
|
||||||
|
if (_this->output.write(outBuf, _this->_bufferSize) < 0) { break; };
|
||||||
|
}
|
||||||
|
delete[] inBuf;
|
||||||
|
delete[] outBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream<StereoFloat_t>* _in;
|
||||||
int _bufferSize;
|
int _bufferSize;
|
||||||
std::thread _workerThread;
|
std::thread _workerThread;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
std::vector<stream<complex_t>*> outputs;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -5,4 +5,9 @@ namespace dsp {
|
|||||||
float q;
|
float q;
|
||||||
float i;
|
float i;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StereoFloat_t {
|
||||||
|
float l;
|
||||||
|
float r;
|
||||||
|
};
|
||||||
};
|
};
|
@ -25,7 +25,9 @@ namespace icons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
LOGO = (ImTextureID)loadTexture("res/icons/logo.png");
|
LOGO = (ImTextureID)loadTexture("res/icons/sdrpp.png");
|
||||||
|
PLAY = (ImTextureID)loadTexture("res/icons/play.png");
|
||||||
|
STOP = (ImTextureID)loadTexture("res/icons/stop.png");
|
||||||
PLAY_RAW = (ImTextureID)loadTexture("res/icons/play_raw.png");
|
PLAY_RAW = (ImTextureID)loadTexture("res/icons/play_raw.png");
|
||||||
STOP_RAW = (ImTextureID)loadTexture("res/icons/stop_raw.png");
|
STOP_RAW = (ImTextureID)loadTexture("res/icons/stop_raw.png");
|
||||||
}
|
}
|
||||||
|
@ -2640,6 +2640,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
|
|||||||
|
|
||||||
// Slider behavior
|
// Slider behavior
|
||||||
ImRect grab_bb;
|
ImRect grab_bb;
|
||||||
|
|
||||||
const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_None, &grab_bb);
|
const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_None, &grab_bb);
|
||||||
if (value_changed)
|
if (value_changed)
|
||||||
MarkItemEdited(id);
|
MarkItemEdited(id);
|
||||||
|
2631
src/imgui/stb_image_resize.h
Normal file
2631
src/imgui/stb_image_resize.h
Normal file
File diff suppressed because it is too large
Load Diff
233
src/io/audio.h
233
src/io/audio.h
@ -9,34 +9,128 @@
|
|||||||
namespace io {
|
namespace io {
|
||||||
class AudioSink {
|
class AudioSink {
|
||||||
public:
|
public:
|
||||||
|
enum {
|
||||||
|
MONO,
|
||||||
|
STEREO,
|
||||||
|
_TYPE_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioDevice_t {
|
||||||
|
std::string name;
|
||||||
|
int index;
|
||||||
|
int channels;
|
||||||
|
std::vector<float> sampleRates;
|
||||||
|
std::string txtSampleRates;
|
||||||
|
};
|
||||||
|
|
||||||
AudioSink() {
|
AudioSink() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioSink(dsp::stream<float>* in, int bufferSize) {
|
AudioSink(int bufferSize) {
|
||||||
_bufferSize = bufferSize;
|
_bufferSize = bufferSize;
|
||||||
_input = in;
|
monoBuffer = new float[_bufferSize];
|
||||||
buffer = new float[_bufferSize * 2];
|
stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
|
||||||
_volume = 1.0f;
|
|
||||||
Pa_Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void init(dsp::stream<float>* in, int bufferSize) {
|
|
||||||
_bufferSize = bufferSize;
|
|
||||||
_input = in;
|
|
||||||
buffer = new float[_bufferSize * 2];
|
|
||||||
_volume = 1.0f;
|
_volume = 1.0f;
|
||||||
Pa_Initialize();
|
Pa_Initialize();
|
||||||
|
|
||||||
devTxtList = "";
|
devTxtList = "";
|
||||||
int devCount = Pa_GetDeviceCount();
|
int devCount = Pa_GetDeviceCount();
|
||||||
|
devIndex = Pa_GetDefaultOutputDevice();
|
||||||
const PaDeviceInfo *deviceInfo;
|
const PaDeviceInfo *deviceInfo;
|
||||||
|
PaStreamParameters outputParams;
|
||||||
|
outputParams.sampleFormat = paFloat32;
|
||||||
|
outputParams.hostApiSpecificStreamInfo = NULL;
|
||||||
for(int i = 0; i < devCount; i++) {
|
for(int i = 0; i < devCount; i++) {
|
||||||
deviceInfo = Pa_GetDeviceInfo(i);
|
deviceInfo = Pa_GetDeviceInfo(i);
|
||||||
|
if (deviceInfo->maxOutputChannels < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AudioDevice_t dev;
|
||||||
|
dev.name = deviceInfo->name;
|
||||||
|
dev.index = i;
|
||||||
|
dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
|
||||||
|
dev.sampleRates.clear();
|
||||||
|
dev.txtSampleRates = "";
|
||||||
|
for (int j = 0; j < 6; j++) {
|
||||||
|
outputParams.channelCount = dev.channels;
|
||||||
|
outputParams.device = dev.index;
|
||||||
|
outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
|
||||||
|
PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
|
||||||
|
if (err != paFormatIsSupported) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
|
||||||
|
dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
|
||||||
|
dev.txtSampleRates += '\0';
|
||||||
|
}
|
||||||
|
if (dev.sampleRates.size() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i == devIndex) {
|
||||||
|
devListIndex = devices.size();
|
||||||
|
}
|
||||||
|
devices.push_back(dev);
|
||||||
devTxtList += deviceInfo->name;
|
devTxtList += deviceInfo->name;
|
||||||
devTxtList += '\0';
|
devTxtList += '\0';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(int bufferSize) {
|
||||||
|
_bufferSize = bufferSize;
|
||||||
|
monoBuffer = new float[_bufferSize];
|
||||||
|
stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
|
||||||
|
_volume = 1.0f;
|
||||||
|
Pa_Initialize();
|
||||||
|
|
||||||
|
devTxtList = "";
|
||||||
|
int devCount = Pa_GetDeviceCount();
|
||||||
devIndex = Pa_GetDefaultOutputDevice();
|
devIndex = Pa_GetDefaultOutputDevice();
|
||||||
|
const PaDeviceInfo *deviceInfo;
|
||||||
|
PaStreamParameters outputParams;
|
||||||
|
outputParams.sampleFormat = paFloat32;
|
||||||
|
outputParams.hostApiSpecificStreamInfo = NULL;
|
||||||
|
for(int i = 0; i < devCount; i++) {
|
||||||
|
deviceInfo = Pa_GetDeviceInfo(i);
|
||||||
|
if (deviceInfo->maxOutputChannels < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AudioDevice_t dev;
|
||||||
|
dev.name = deviceInfo->name;
|
||||||
|
dev.index = i;
|
||||||
|
dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
|
||||||
|
dev.sampleRates.clear();
|
||||||
|
dev.txtSampleRates = "";
|
||||||
|
for (int j = 0; j < 6; j++) {
|
||||||
|
outputParams.channelCount = dev.channels;
|
||||||
|
outputParams.device = dev.index;
|
||||||
|
outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
|
||||||
|
PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
|
||||||
|
if (err != paFormatIsSupported) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
|
||||||
|
dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
|
||||||
|
dev.txtSampleRates += '\0';
|
||||||
|
}
|
||||||
|
if (dev.sampleRates.size() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i == devIndex) {
|
||||||
|
devListIndex = devices.size();
|
||||||
|
}
|
||||||
|
devices.push_back(dev);
|
||||||
|
devTxtList += deviceInfo->name;
|
||||||
|
devTxtList += '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMonoInput(dsp::stream<float>* input) {
|
||||||
|
_monoInput = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStereoInput(dsp::stream<dsp::StereoFloat_t>* input) {
|
||||||
|
_stereoInput = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setVolume(float volume) {
|
void setVolume(float volume) {
|
||||||
@ -47,13 +141,25 @@ namespace io {
|
|||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const PaDeviceInfo *deviceInfo;
|
||||||
|
AudioDevice_t dev = devices[devListIndex];
|
||||||
PaStreamParameters outputParams;
|
PaStreamParameters outputParams;
|
||||||
|
deviceInfo = Pa_GetDeviceInfo(dev.index);
|
||||||
outputParams.channelCount = 2;
|
outputParams.channelCount = 2;
|
||||||
outputParams.sampleFormat = paFloat32;
|
outputParams.sampleFormat = paFloat32;
|
||||||
outputParams.hostApiSpecificStreamInfo = NULL;
|
outputParams.hostApiSpecificStreamInfo = NULL;
|
||||||
outputParams.device = devIndex;
|
outputParams.device = dev.index;
|
||||||
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
|
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
|
||||||
PaError err = Pa_OpenStream(&stream, NULL, &outputParams, 48000.0f, _bufferSize, paClipOff, _callback, this);
|
PaError err;
|
||||||
|
if (streamType == MONO) {
|
||||||
|
err = Pa_OpenStream(&stream, NULL, &outputParams, _sampleRate, _bufferSize, NULL,
|
||||||
|
(dev.channels == 2) ? _mono_to_stereo_callback : _mono_to_mono_callback, this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err = Pa_OpenStream(&stream, NULL, &outputParams, _sampleRate, _bufferSize, NULL,
|
||||||
|
(dev.channels == 2) ? _stereo_to_stereo_callback : _stereo_to_mono_callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
|
spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
|
||||||
return;
|
return;
|
||||||
@ -81,47 +187,128 @@ namespace io {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_bufferSize = blockSize;
|
_bufferSize = blockSize;
|
||||||
|
delete[] monoBuffer;
|
||||||
|
delete[] stereoBuffer;
|
||||||
|
monoBuffer = new float[_bufferSize];
|
||||||
|
stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSampleRate(float sampleRate) {
|
||||||
|
_sampleRate = sampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDevice(int id) {
|
void setDevice(int id) {
|
||||||
if (devIndex == id) {
|
if (devListIndex == id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
devIndex = id;
|
devListIndex = id;
|
||||||
|
devIndex = devices[id].index;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDeviceId() {
|
int getDeviceId() {
|
||||||
return devIndex;
|
return devListIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStreamType(int type) {
|
||||||
|
streamType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string devTxtList;
|
std::string devTxtList;
|
||||||
|
std::vector<AudioDevice_t> devices;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int _callback(const void *input,
|
static int _mono_to_mono_callback(const void *input,
|
||||||
void *output,
|
void *output,
|
||||||
unsigned long frameCount,
|
unsigned long frameCount,
|
||||||
const PaStreamCallbackTimeInfo* timeInfo,
|
const PaStreamCallbackTimeInfo* timeInfo,
|
||||||
PaStreamCallbackFlags statusFlags, void *userData ) {
|
PaStreamCallbackFlags statusFlags, void *userData ) {
|
||||||
AudioSink* _this = (AudioSink*)userData;
|
AudioSink* _this = (AudioSink*)userData;
|
||||||
float* outbuf = (float*)output;
|
float* outbuf = (float*)output;
|
||||||
_this->_input->read(_this->buffer, frameCount);
|
_this->_monoInput->read(_this->monoBuffer, frameCount);
|
||||||
|
|
||||||
float vol = powf(_this->_volume, 2);
|
float vol = powf(_this->_volume, 2);
|
||||||
for (int i = 0; i < frameCount; i++) {
|
for (int i = 0; i < frameCount; i++) {
|
||||||
outbuf[(i * 2) + 0] = _this->buffer[i] * vol;
|
outbuf[i] = _this->monoBuffer[i] * vol;
|
||||||
outbuf[(i * 2) + 1] = _this->buffer[i] * vol;
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _stereo_to_stereo_callback(const void *input,
|
||||||
|
void *output,
|
||||||
|
unsigned long frameCount,
|
||||||
|
const PaStreamCallbackTimeInfo* timeInfo,
|
||||||
|
PaStreamCallbackFlags statusFlags, void *userData ) {
|
||||||
|
AudioSink* _this = (AudioSink*)userData;
|
||||||
|
dsp::StereoFloat_t* outbuf = (dsp::StereoFloat_t*)output;
|
||||||
|
_this->_stereoInput->read(_this->stereoBuffer, frameCount);
|
||||||
|
|
||||||
|
// Note: Calculate the power in the UI instead of here
|
||||||
|
|
||||||
|
float vol = powf(_this->_volume, 2);
|
||||||
|
for (int i = 0; i < frameCount; i++) {
|
||||||
|
outbuf[i].l = _this->stereoBuffer[i].l * vol;
|
||||||
|
outbuf[i].r = _this->stereoBuffer[i].r * vol;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _mono_to_stereo_callback(const void *input,
|
||||||
|
void *output,
|
||||||
|
unsigned long frameCount,
|
||||||
|
const PaStreamCallbackTimeInfo* timeInfo,
|
||||||
|
PaStreamCallbackFlags statusFlags, void *userData ) {
|
||||||
|
AudioSink* _this = (AudioSink*)userData;
|
||||||
|
dsp::StereoFloat_t* outbuf = (dsp::StereoFloat_t*)output;
|
||||||
|
_this->_monoInput->read(_this->monoBuffer, frameCount);
|
||||||
|
|
||||||
|
float vol = powf(_this->_volume, 2);
|
||||||
|
for (int i = 0; i < frameCount; i++) {
|
||||||
|
outbuf[i].l = _this->monoBuffer[i] * vol;
|
||||||
|
outbuf[i].r = _this->monoBuffer[i] * vol;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _stereo_to_mono_callback(const void *input,
|
||||||
|
void *output,
|
||||||
|
unsigned long frameCount,
|
||||||
|
const PaStreamCallbackTimeInfo* timeInfo,
|
||||||
|
PaStreamCallbackFlags statusFlags, void *userData ) {
|
||||||
|
AudioSink* _this = (AudioSink*)userData;
|
||||||
|
float* outbuf = (float*)output;
|
||||||
|
_this->_stereoInput->read(_this->stereoBuffer, frameCount);
|
||||||
|
|
||||||
|
// Note: Calculate the power in the UI instead of here
|
||||||
|
|
||||||
|
float vol = powf(_this->_volume, 2);
|
||||||
|
for (int i = 0; i < frameCount; i++) {
|
||||||
|
outbuf[i] = ((_this->stereoBuffer[i].l + _this->stereoBuffer[i].r) / 2.0f) * vol;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float POSSIBLE_SAMP_RATE[6] = {
|
||||||
|
48000.0f,
|
||||||
|
44100.0f,
|
||||||
|
24000.0f,
|
||||||
|
22050.0f,
|
||||||
|
12000.0f,
|
||||||
|
11025.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
int streamType;
|
||||||
int devIndex;
|
int devIndex;
|
||||||
|
int devListIndex;
|
||||||
|
float _sampleRate;
|
||||||
int _bufferSize;
|
int _bufferSize;
|
||||||
dsp::stream<float>* _input;
|
dsp::stream<float>* _monoInput;
|
||||||
float* buffer;
|
dsp::stream<dsp::StereoFloat_t>* _stereoInput;
|
||||||
float _volume;
|
float* monoBuffer;
|
||||||
|
dsp::StereoFloat_t* stereoBuffer;
|
||||||
|
float _volume = 1.0f;
|
||||||
PaStream *stream;
|
PaStream *stream;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
};
|
};
|
||||||
|
@ -52,6 +52,7 @@ namespace io {
|
|||||||
|
|
||||||
devList = SoapySDR::Device::enumerate();
|
devList = SoapySDR::Device::enumerate();
|
||||||
txtDevList = "";
|
txtDevList = "";
|
||||||
|
devNameList.clear();
|
||||||
if (devList.size() == 0) {
|
if (devList.size() == 0) {
|
||||||
txtDevList += '\0';
|
txtDevList += '\0';
|
||||||
return;
|
return;
|
||||||
@ -59,6 +60,7 @@ namespace io {
|
|||||||
for (int i = 0; i < devList.size(); i++) {
|
for (int i = 0; i < devList.size(); i++) {
|
||||||
txtDevList += devList[i]["label"];
|
txtDevList += devList[i]["label"];
|
||||||
txtDevList += '\0';
|
txtDevList += '\0';
|
||||||
|
devNameList.push_back(devList[i]["label"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +76,7 @@ namespace io {
|
|||||||
txtSampleRateList += std::to_string((int)sampleRates[i]);
|
txtSampleRateList += std::to_string((int)sampleRates[i]);
|
||||||
txtSampleRateList += '\0';
|
txtSampleRateList += '\0';
|
||||||
}
|
}
|
||||||
|
_sampleRate = sampleRates[0];
|
||||||
|
|
||||||
gainList = dev->listGains(SOAPY_SDR_RX, 0);
|
gainList = dev->listGains(SOAPY_SDR_RX, 0);
|
||||||
gainRanges.clear();
|
gainRanges.clear();
|
||||||
@ -84,7 +87,11 @@ namespace io {
|
|||||||
currentGains = new float[gainList.size()];
|
currentGains = new float[gainList.size()];
|
||||||
for (int i = 0; i < gainList.size(); i++) {
|
for (int i = 0; i < gainList.size(); i++) {
|
||||||
gainRanges.push_back(dev->getGainRange(SOAPY_SDR_RX, 0, gainList[i]));
|
gainRanges.push_back(dev->getGainRange(SOAPY_SDR_RX, 0, gainList[i]));
|
||||||
currentGains[i] = dev->getGain(SOAPY_SDR_RX, 0, gainList[i]);
|
SoapySDR::Range rng = dev->getGainRange(SOAPY_SDR_RX, 0, gainList[i]);
|
||||||
|
|
||||||
|
spdlog::info("{0}: {1} -> {2} (Step: {3})", gainList[i], rng.minimum(), rng.maximum(), rng.step());
|
||||||
|
|
||||||
|
currentGains[i] = rng.minimum();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,8 +116,14 @@ namespace io {
|
|||||||
return running;
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float getSampleRate() {
|
||||||
|
return _sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
SoapySDR::KwargsList devList;
|
SoapySDR::KwargsList devList;
|
||||||
|
std::vector<std::string> devNameList;
|
||||||
std::string txtDevList;
|
std::string txtDevList;
|
||||||
|
|
||||||
std::vector<double> sampleRates;
|
std::vector<double> sampleRates;
|
||||||
std::string txtSampleRateList;
|
std::string txtSampleRateList;
|
||||||
|
|
||||||
|
47
src/main.cpp
47
src/main.cpp
@ -5,12 +5,17 @@
|
|||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <main_window.h>
|
#include <main_window.h>
|
||||||
#include <styles.h>
|
#include <style.h>
|
||||||
#include <icons.h>
|
#include <icons.h>
|
||||||
#include <version.h>
|
#include <version.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <bandplan.h>
|
#include <bandplan.h>
|
||||||
#include <module.h>
|
#include <module.h>
|
||||||
|
#include <stb_image.h>
|
||||||
|
#include <config.h>
|
||||||
|
|
||||||
|
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||||
|
#include <stb_image_resize.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@ -38,6 +43,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
// 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(1280, 720, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||||
if (window == NULL)
|
if (window == NULL)
|
||||||
@ -45,6 +51,33 @@ int main() {
|
|||||||
glfwMakeContextCurrent(window);
|
glfwMakeContextCurrent(window);
|
||||||
glfwSwapInterval(1); // Enable vsync
|
glfwSwapInterval(1); // Enable vsync
|
||||||
|
|
||||||
|
// Load app icon
|
||||||
|
GLFWimage icons[10];
|
||||||
|
icons[0].pixels = stbi_load("res/icons/sdrpp.png", &icons[0].width, &icons[0].height, 0, 4);
|
||||||
|
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4); icons[1].width = icons[1].height = 16;
|
||||||
|
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4); icons[2].width = icons[2].height = 24;
|
||||||
|
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4); icons[3].width = icons[3].height = 32;
|
||||||
|
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4); icons[4].width = icons[4].height = 48;
|
||||||
|
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4); icons[5].width = icons[5].height = 64;
|
||||||
|
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4); icons[6].width = icons[6].height = 96;
|
||||||
|
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4); icons[7].width = icons[7].height = 128;
|
||||||
|
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4); icons[8].width = icons[8].height = 196;
|
||||||
|
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4); icons[9].width = icons[9].height = 256;
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
|
||||||
|
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
|
||||||
|
glfwSetWindowIcon(window, 10, icons);
|
||||||
|
stbi_image_free(icons[0].pixels);
|
||||||
|
for (int i = 1; i < 10; i++) {
|
||||||
|
free(icons[i].pixels);
|
||||||
|
}
|
||||||
|
|
||||||
if (glewInit() != GLEW_OK) {
|
if (glewInit() != GLEW_OK) {
|
||||||
spdlog::error("Failed to initialize OpenGL loader!");
|
spdlog::error("Failed to initialize OpenGL loader!");
|
||||||
return 1;
|
return 1;
|
||||||
@ -60,9 +93,12 @@ int main() {
|
|||||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||||
ImGui_ImplOpenGL3_Init("#version 150");
|
ImGui_ImplOpenGL3_Init("#version 150");
|
||||||
|
|
||||||
setImguiStyle(io);
|
// Load config
|
||||||
|
spdlog::info("Loading config");
|
||||||
|
config::load("config.json");
|
||||||
|
config::startAutoSave();
|
||||||
|
|
||||||
windowInit();
|
style::setDefaultStyle();
|
||||||
|
|
||||||
spdlog::info("Loading icons");
|
spdlog::info("Loading icons");
|
||||||
icons::load();
|
icons::load();
|
||||||
@ -73,6 +109,8 @@ int main() {
|
|||||||
spdlog::info("Loading band plans color table");
|
spdlog::info("Loading band plans color table");
|
||||||
bandplan::loadColorTable("band_colors.json");
|
bandplan::loadColorTable("band_colors.json");
|
||||||
|
|
||||||
|
windowInit();
|
||||||
|
|
||||||
spdlog::info("Ready.");
|
spdlog::info("Ready.");
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
@ -88,12 +126,9 @@ int main() {
|
|||||||
glfwGetWindowSize(window, &wwidth, &wheight);
|
glfwGetWindowSize(window, &wwidth, &wheight);
|
||||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||||
ImGui::SetNextWindowSize(ImVec2(wwidth, wheight));
|
ImGui::SetNextWindowSize(ImVec2(wwidth, wheight));
|
||||||
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
|
|
||||||
|
|
||||||
drawWindow();
|
drawWindow();
|
||||||
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
int display_w, display_h;
|
int display_w, display_h;
|
||||||
|
@ -9,6 +9,7 @@ fftwf_plan p;
|
|||||||
float* tempData;
|
float* tempData;
|
||||||
float* uiGains;
|
float* uiGains;
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
|
ImFont* bigFont;
|
||||||
|
|
||||||
int fftSize = 8192 * 8;
|
int fftSize = 8192 * 8;
|
||||||
|
|
||||||
@ -37,14 +38,68 @@ void fftHandler(dsp::complex_t* samples) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dsp::NullSink sink;
|
dsp::NullSink sink;
|
||||||
|
int devId = 0;
|
||||||
|
int srId = 0;
|
||||||
|
watcher<int> bandplanId(0, true);
|
||||||
|
watcher<long> freq(90500000L);
|
||||||
|
int demod = 1;
|
||||||
|
watcher<float> vfoFreq(92000000.0f);
|
||||||
|
float dummyVolume = 1.0f;
|
||||||
|
float* volume = &dummyVolume;
|
||||||
|
float fftMin = -70.0f;
|
||||||
|
float fftMax = 0.0f;
|
||||||
|
watcher<float> offset(0.0f, true);
|
||||||
|
watcher<float> bw(8000000.0f, true);
|
||||||
|
int sampleRate = 8000000;
|
||||||
|
bool playing = false;
|
||||||
|
watcher<bool> dcbias(false, false);
|
||||||
|
watcher<bool> bandPlanEnabled(true, false);
|
||||||
|
bool showCredits = false;
|
||||||
|
std::string audioStreamName = "";
|
||||||
|
std::string sourceName = "";
|
||||||
|
|
||||||
|
void saveCurrentSource() {
|
||||||
|
int i = 0;
|
||||||
|
for (std::string gainName : soapy.gainList) {
|
||||||
|
config::config["sourceSettings"][sourceName]["gains"][gainName] = uiGains[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
config::config["sourceSettings"][sourceName]["sampleRate"] = soapy.sampleRates[srId];
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadSourceConfig(std::string name) {
|
||||||
|
json sourceSettings = config::config["sourceSettings"][name];
|
||||||
|
|
||||||
|
// Set sample rate
|
||||||
|
|
||||||
|
spdlog::warn("Type {0}", sourceSettings.contains("sampleRate"));
|
||||||
|
|
||||||
|
sampleRate = sourceSettings["sampleRate"];
|
||||||
|
|
||||||
|
sigPath.setSampleRate(sampleRate);
|
||||||
|
soapy.setSampleRate(sampleRate);
|
||||||
|
auto _srIt = std::find(soapy.sampleRates.begin(), soapy.sampleRates.end(), sampleRate);
|
||||||
|
srId = std::distance(soapy.sampleRates.begin(), _srIt);
|
||||||
|
spdlog::warn("sr {0}", srId);
|
||||||
|
|
||||||
|
// Set gains
|
||||||
|
delete uiGains;
|
||||||
|
uiGains = new float[soapy.gainList.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (std::string gainName : soapy.gainList) {
|
||||||
|
uiGains[i] = sourceSettings["gains"][gainName];
|
||||||
|
soapy.setGain(i, uiGains[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update GUI
|
||||||
|
wtf.setBandwidth(sampleRate);
|
||||||
|
wtf.setViewBandwidth(sampleRate);
|
||||||
|
bw.val = sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
void windowInit() {
|
void windowInit() {
|
||||||
int sampleRate = 8000000;
|
|
||||||
wtf.setBandwidth(sampleRate);
|
|
||||||
wtf.setCenterFrequency(90500000);
|
|
||||||
|
|
||||||
fSel.init();
|
fSel.init();
|
||||||
fSel.setFrequency(90500000);
|
|
||||||
|
|
||||||
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||||
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||||
@ -60,24 +115,92 @@ void windowInit() {
|
|||||||
spdlog::info("Loading modules");
|
spdlog::info("Loading modules");
|
||||||
mod::initAPI(&wtf);
|
mod::initAPI(&wtf);
|
||||||
mod::loadFromList("module_list.json");
|
mod::loadFromList("module_list.json");
|
||||||
}
|
|
||||||
|
|
||||||
watcher<int> devId(0, true);
|
bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 128.0f);
|
||||||
watcher<int> srId(0, true);
|
|
||||||
watcher<int> bandplanId(0, true);
|
// Load last source configuration
|
||||||
watcher<long> freq(90500000L);
|
uint64_t frequency = config::config["frequency"];
|
||||||
int demod = 1;
|
sourceName = config::config["source"];
|
||||||
watcher<float> vfoFreq(92000000.0f);
|
auto _sourceIt = std::find(soapy.devNameList.begin(), soapy.devNameList.end(), sourceName);
|
||||||
float dummyVolume = 1.0f;
|
if (_sourceIt != soapy.devNameList.end() && config::config["sourceSettings"].contains(sourceName)) {
|
||||||
float* volume = &dummyVolume;
|
json sourceSettings = config::config["sourceSettings"][sourceName];
|
||||||
float fftMin = -70.0f;
|
devId = std::distance(soapy.devNameList.begin(), _sourceIt);
|
||||||
float fftMax = 0.0f;
|
soapy.setDevice(soapy.devList[devId]);
|
||||||
watcher<float> offset(0.0f, true);
|
loadSourceConfig(sourceName);
|
||||||
watcher<float> bw(8000000.0f, true);
|
}
|
||||||
int sampleRate = 1000000;
|
else {
|
||||||
bool playing = false;
|
int i = 0;
|
||||||
watcher<bool> dcbias(false, false);
|
bool settingsFound = false;
|
||||||
watcher<bool> bandPlanEnabled(true, false);
|
for (std::string devName : soapy.devNameList) {
|
||||||
|
if (config::config["sourceSettings"].contains(devName)) {
|
||||||
|
sourceName = devName;
|
||||||
|
settingsFound = true;
|
||||||
|
devId = i;
|
||||||
|
soapy.setDevice(soapy.devList[i]);
|
||||||
|
loadSourceConfig(sourceName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (!settingsFound) {
|
||||||
|
sampleRate = soapy.getSampleRate();
|
||||||
|
}
|
||||||
|
// Search for the first source in the list to have a config
|
||||||
|
// If no pre-defined source, selected default device
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load last band plan configuration
|
||||||
|
|
||||||
|
// TODO: Save/load config for audio streams + window size/fullscreen
|
||||||
|
|
||||||
|
|
||||||
|
// Update UI settings
|
||||||
|
fftMin = config::config["min"];
|
||||||
|
fftMax = config::config["max"];
|
||||||
|
wtf.setFFTMin(fftMin);
|
||||||
|
wtf.setWaterfallMin(fftMin);
|
||||||
|
wtf.setFFTMax(fftMax);
|
||||||
|
wtf.setWaterfallMax(fftMax);
|
||||||
|
|
||||||
|
bandPlanEnabled.val = config::config["bandPlanEnabled"];
|
||||||
|
bandPlanEnabled.markAsChanged();
|
||||||
|
|
||||||
|
std::string bandPlanName = config::config["bandPlan"];
|
||||||
|
auto _bandplanIt = bandplan::bandplans.find(bandPlanName);
|
||||||
|
if (_bandplanIt != bandplan::bandplans.end()) {
|
||||||
|
bandplanId.val = std::distance(bandplan::bandplans.begin(), bandplan::bandplans.find(bandPlanName));
|
||||||
|
spdlog::warn("{0} => {1}", bandplanId.val, bandPlanName);
|
||||||
|
|
||||||
|
if (bandPlanEnabled.val) {
|
||||||
|
wtf.bandplan = &bandplan::bandplans[bandPlanName];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wtf.bandplan = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bandplanId.val = 0;
|
||||||
|
}
|
||||||
|
bandplanId.markAsChanged();
|
||||||
|
|
||||||
|
|
||||||
|
fSel.setFrequency(frequency);
|
||||||
|
fSel.frequencyChanged = false;
|
||||||
|
soapy.setFrequency(frequency);
|
||||||
|
wtf.setCenterFrequency(frequency);
|
||||||
|
wtf.setBandwidth(sampleRate);
|
||||||
|
wtf.setViewBandwidth(sampleRate);
|
||||||
|
bw.val = sampleRate;
|
||||||
|
wtf.vfoFreqChanged = false;
|
||||||
|
wtf.centerFreqMoved = false;
|
||||||
|
wtf.selectFirstVFO();
|
||||||
|
|
||||||
|
|
||||||
|
audioStreamName = audio::getNameFromVFO(wtf.selectedVFO);
|
||||||
|
if (audioStreamName != "") {
|
||||||
|
volume = &audio::streams[audioStreamName]->volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setVFO(float freq) {
|
void setVFO(float freq) {
|
||||||
ImGui::WaterfallVFO* vfo = wtf.vfos[wtf.selectedVFO];
|
ImGui::WaterfallVFO* vfo = wtf.vfos[wtf.selectedVFO];
|
||||||
@ -165,14 +288,15 @@ void setVFO(float freq) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void drawWindow() {
|
void drawWindow() {
|
||||||
if (wtf.selectedVFO == "" && wtf.vfos.size() > 0) {
|
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
|
||||||
wtf.selectFirstVFO();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::WaterfallVFO* vfo = wtf.vfos[wtf.selectedVFO];
|
ImGui::WaterfallVFO* vfo = wtf.vfos[wtf.selectedVFO];
|
||||||
|
|
||||||
if (vfo->centerOffsetChanged) {
|
if (vfo->centerOffsetChanged) {
|
||||||
fSel.setFrequency(wtf.getCenterFrequency() + vfo->generalOffset);
|
fSel.setFrequency(wtf.getCenterFrequency() + vfo->generalOffset);
|
||||||
|
fSel.frequencyChanged = false;
|
||||||
|
config::config["frequency"] = fSel.frequency;
|
||||||
|
config::configModified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
vfoman::updateFromWaterfall();
|
vfoman::updateFromWaterfall();
|
||||||
@ -180,7 +304,14 @@ void drawWindow() {
|
|||||||
if (wtf.selectedVFOChanged) {
|
if (wtf.selectedVFOChanged) {
|
||||||
wtf.selectedVFOChanged = false;
|
wtf.selectedVFOChanged = false;
|
||||||
fSel.setFrequency(vfo->generalOffset + wtf.getCenterFrequency());
|
fSel.setFrequency(vfo->generalOffset + wtf.getCenterFrequency());
|
||||||
|
fSel.frequencyChanged = false;
|
||||||
mod::broadcastEvent(mod::EVENT_SELECTED_VFO_CHANGED);
|
mod::broadcastEvent(mod::EVENT_SELECTED_VFO_CHANGED);
|
||||||
|
audioStreamName = audio::getNameFromVFO(wtf.selectedVFO);
|
||||||
|
if (audioStreamName != "") {
|
||||||
|
volume = &audio::streams[audioStreamName]->volume;
|
||||||
|
}
|
||||||
|
config::config["frequency"] = fSel.frequency;
|
||||||
|
config::configModified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fSel.frequencyChanged) {
|
if (fSel.frequencyChanged) {
|
||||||
@ -189,36 +320,16 @@ void drawWindow() {
|
|||||||
vfo->centerOffsetChanged = false;
|
vfo->centerOffsetChanged = false;
|
||||||
vfo->lowerOffsetChanged = false;
|
vfo->lowerOffsetChanged = false;
|
||||||
vfo->upperOffsetChanged = false;
|
vfo->upperOffsetChanged = false;
|
||||||
|
config::config["frequency"] = fSel.frequency;
|
||||||
|
config::configModified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wtf.centerFreqMoved) {
|
if (wtf.centerFreqMoved) {
|
||||||
wtf.centerFreqMoved = false;
|
wtf.centerFreqMoved = false;
|
||||||
soapy.setFrequency(wtf.getCenterFrequency());
|
soapy.setFrequency(wtf.getCenterFrequency());
|
||||||
fSel.setFrequency(wtf.getCenterFrequency() + vfo->generalOffset);
|
fSel.setFrequency(wtf.getCenterFrequency() + vfo->generalOffset);
|
||||||
}
|
config::config["frequency"] = fSel.frequency;
|
||||||
|
config::configModified = true;
|
||||||
if (devId.changed() && soapy.devList.size() > 0) {
|
|
||||||
spdlog::info("Changed input device: {0}", devId.val);
|
|
||||||
soapy.setDevice(soapy.devList[devId.val]);
|
|
||||||
srId.markAsChanged();
|
|
||||||
if (soapy.gainList.size() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
delete[] uiGains;
|
|
||||||
uiGains = new float[soapy.gainList.size()];
|
|
||||||
for (int i = 0; i < soapy.gainList.size(); i++) {
|
|
||||||
uiGains[i] = soapy.currentGains[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (srId.changed() && soapy.devList.size() > 0) {
|
|
||||||
spdlog::info("Changed sample rate: {0}", srId.val);
|
|
||||||
sampleRate = soapy.sampleRates[srId.val];
|
|
||||||
soapy.setSampleRate(sampleRate);
|
|
||||||
wtf.setBandwidth(sampleRate);
|
|
||||||
wtf.setViewBandwidth(sampleRate);
|
|
||||||
sigPath.setSampleRate(sampleRate);
|
|
||||||
bw.val = sampleRate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dcbias.changed()) {
|
if (dcbias.changed()) {
|
||||||
@ -248,13 +359,13 @@ void drawWindow() {
|
|||||||
|
|
||||||
// To Bar
|
// To Bar
|
||||||
if (playing) {
|
if (playing) {
|
||||||
if (ImGui::ImageButton(icons::STOP_RAW, ImVec2(30, 30))) {
|
if (ImGui::ImageButton(icons::STOP, ImVec2(40, 40), ImVec2(0, 0), ImVec2(1, 1), 0)) {
|
||||||
soapy.stop();
|
soapy.stop();
|
||||||
playing = false;
|
playing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (ImGui::ImageButton(icons::PLAY_RAW, ImVec2(30, 30)) && soapy.devList.size() > 0) {
|
if (ImGui::ImageButton(icons::PLAY, ImVec2(40, 40), ImVec2(0, 0), ImVec2(1, 1), 0) && soapy.devList.size() > 0) {
|
||||||
soapy.start();
|
soapy.start();
|
||||||
soapy.setFrequency(wtf.getCenterFrequency());
|
soapy.setFrequency(wtf.getCenterFrequency());
|
||||||
playing = true;
|
playing = true;
|
||||||
@ -265,12 +376,31 @@ void drawWindow() {
|
|||||||
|
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
|
||||||
ImGui::SetNextItemWidth(200);
|
ImGui::SetNextItemWidth(200);
|
||||||
ImGui::SliderFloat("##_2_", volume, 0.0f, 1.0f, "");
|
if (ImGui::SliderFloat("##_2_", volume, 0.0f, 1.0f, "")) {
|
||||||
|
if (audioStreamName != "") {
|
||||||
|
audio::streams[audioStreamName]->audio->setVolume(*volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
fSel.draw();
|
fSel.draw();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
// Logo button
|
||||||
|
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48);
|
||||||
|
ImGui::SetCursorPosY(10);
|
||||||
|
if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) {
|
||||||
|
showCredits = true;
|
||||||
|
}
|
||||||
|
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||||
|
showCredits = false;
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(GLFW_KEY_ESCAPE)) {
|
||||||
|
showCredits = false;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Columns(3, "WindowColumns", false);
|
ImGui::Columns(3, "WindowColumns", false);
|
||||||
ImVec2 winSize = ImGui::GetWindowSize();
|
ImVec2 winSize = ImGui::GetWindowSize();
|
||||||
ImGui::SetColumnWidth(0, 300);
|
ImGui::SetColumnWidth(0, 300);
|
||||||
@ -279,88 +409,223 @@ void drawWindow() {
|
|||||||
|
|
||||||
// Left Column
|
// Left Column
|
||||||
ImGui::BeginChild("Left Column");
|
ImGui::BeginChild("Left Column");
|
||||||
|
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("Source")) {
|
if (ImGui::CollapsingHeader("Source", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
ImGui::PushItemWidth(ImGui::GetWindowSize().x);
|
if (playing) { style::beginDisabled(); };
|
||||||
ImGui::Combo("##_0_", &devId.val, soapy.txtDevList.c_str());
|
|
||||||
|
ImGui::PushItemWidth(menuColumnWidth);
|
||||||
|
if (ImGui::Combo("##_0_", &devId, soapy.txtDevList.c_str())) {
|
||||||
|
spdlog::info("Changed input device: {0}", devId);
|
||||||
|
sourceName = soapy.devNameList[devId];
|
||||||
|
soapy.setDevice(soapy.devList[devId]);
|
||||||
|
if (soapy.gainList.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete[] uiGains;
|
||||||
|
uiGains = new float[soapy.gainList.size()];
|
||||||
|
for (int i = 0; i < soapy.gainList.size(); i++) {
|
||||||
|
uiGains[i] = soapy.currentGains[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config::config["sourceSettings"].contains(sourceName)) {
|
||||||
|
loadSourceConfig(sourceName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
srId = 0;
|
||||||
|
sampleRate = soapy.getSampleRate();
|
||||||
|
bw.val = sampleRate;
|
||||||
|
wtf.setBandwidth(sampleRate);
|
||||||
|
wtf.setViewBandwidth(sampleRate);
|
||||||
|
sigPath.setSampleRate(sampleRate);
|
||||||
|
for (int i = 0; i < soapy.gainList.size(); i++) {
|
||||||
|
uiGains[i] = soapy.gainRanges[i].minimum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setVFO(fSel.frequency);
|
||||||
|
config::config["source"] = sourceName;
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
if (!playing) {
|
if (ImGui::Combo("##_1_", &srId, soapy.txtSampleRateList.c_str())) {
|
||||||
ImGui::Combo("##_1_", &srId.val, soapy.txtSampleRateList.c_str());
|
spdlog::info("Changed sample rate: {0}", srId);
|
||||||
}
|
sampleRate = soapy.sampleRates[srId];
|
||||||
else {
|
soapy.setSampleRate(sampleRate);
|
||||||
ImGui::Text("%.0f Samples/s", soapy.sampleRates[srId.val]);
|
wtf.setBandwidth(sampleRate);
|
||||||
|
wtf.setViewBandwidth(sampleRate);
|
||||||
|
sigPath.setSampleRate(sampleRate);
|
||||||
|
bw.val = sampleRate;
|
||||||
|
|
||||||
|
if (!config::config["sourceSettings"].contains(sourceName)) {
|
||||||
|
saveCurrentSource();
|
||||||
|
}
|
||||||
|
config::config["sourceSettings"][sourceName]["sampleRate"] = sampleRate;
|
||||||
|
config::configModified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Refresh")) {
|
if (ImGui::Button("Refresh", ImVec2(menuColumnWidth - ImGui::GetCursorPosX(), 0.0f))) {
|
||||||
soapy.refresh();
|
soapy.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (playing) { style::endDisabled(); };
|
||||||
|
|
||||||
|
float maxTextLength = 0;
|
||||||
|
float txtLen = 0;
|
||||||
|
char buf[100];
|
||||||
|
|
||||||
|
// Calculate the spacing
|
||||||
|
for (int i = 0; i < soapy.gainList.size(); i++) {
|
||||||
|
sprintf(buf, "%s gain", soapy.gainList[i].c_str());
|
||||||
|
txtLen = ImGui::CalcTextSize(buf).x;
|
||||||
|
if (txtLen > maxTextLength) {
|
||||||
|
maxTextLength = txtLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < soapy.gainList.size(); i++) {
|
for (int i = 0; i < soapy.gainList.size(); i++) {
|
||||||
ImGui::Text("%s gain", soapy.gainList[i].c_str());
|
ImGui::Text("%s gain", soapy.gainList[i].c_str());
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
sprintf(buf, "##_gain_slide_%d_", i);
|
sprintf(buf, "##_gain_slide_%d_", i);
|
||||||
ImGui::SliderFloat(buf, &uiGains[i], soapy.gainRanges[i].minimum(), soapy.gainRanges[i].maximum());
|
|
||||||
|
ImGui::SetCursorPosX(maxTextLength + 5);
|
||||||
|
ImGui::PushItemWidth(menuColumnWidth - (maxTextLength + 5));
|
||||||
|
if (ImGui::SliderFloat(buf, &uiGains[i], soapy.gainRanges[i].minimum(), soapy.gainRanges[i].maximum())) {
|
||||||
|
soapy.setGain(i, uiGains[i]);
|
||||||
|
config::config["sourceSettings"][sourceName]["gains"][soapy.gainList[i]] = uiGains[i];
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
if (uiGains[i] != soapy.currentGains[i]) {
|
if (uiGains[i] != soapy.currentGains[i]) {
|
||||||
soapy.setGain(i, uiGains[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < modCount; i++) {
|
for (int i = 0; i < modCount; i++) {
|
||||||
if (ImGui::CollapsingHeader(mod::moduleNames[i].c_str())) {
|
if (ImGui::CollapsingHeader(mod::moduleNames[i].c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
mod = mod::modules[mod::moduleNames[i]];
|
mod = mod::modules[mod::moduleNames[i]];
|
||||||
mod._DRAW_MENU_(mod.ctx);
|
mod._DRAW_MENU_(mod.ctx);
|
||||||
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::CollapsingHeader("Audio");
|
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("Band Plan")) {
|
if (ImGui::CollapsingHeader("Audio", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
ImGui::PushItemWidth(ImGui::GetWindowSize().x);
|
int count = 0;
|
||||||
ImGui::Combo("##_4_", &bandplanId.val, bandplan::bandplanNameTxt.c_str());
|
int maxCount = audio::streams.size();
|
||||||
|
for (auto const& [name, stream] : audio::streams) {
|
||||||
|
int deviceId;
|
||||||
|
float vol = 1.0f;
|
||||||
|
deviceId = stream->audio->getDeviceId();
|
||||||
|
|
||||||
|
ImGui::SetCursorPosX((menuColumnWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f));
|
||||||
|
ImGui::Text(name.c_str());
|
||||||
|
|
||||||
|
ImGui::PushItemWidth(menuColumnWidth);
|
||||||
|
if (ImGui::Combo(("##_audio_dev_0_"+ name).c_str(), &stream->deviceId, stream->audio->devTxtList.c_str())) {
|
||||||
|
audio::stopStream(name);
|
||||||
|
audio::setAudioDevice(name, stream->deviceId, stream->audio->devices[deviceId].sampleRates[0]);
|
||||||
|
audio::startStream(name);
|
||||||
|
stream->sampleRateId = 0;
|
||||||
|
}
|
||||||
|
if (ImGui::Combo(("##_audio_sr_0_" + name).c_str(), &stream->sampleRateId, stream->audio->devices[deviceId].txtSampleRates.c_str())) {
|
||||||
|
audio::stopStream(name);
|
||||||
|
audio::setSampleRate(name, stream->audio->devices[deviceId].sampleRates[stream->sampleRateId]);
|
||||||
|
audio::startStream(name);
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat(("##_audio_vol_0_" + name).c_str(), &stream->volume, 0.0f, 1.0f, "")) {
|
||||||
|
stream->audio->setVolume(stream->volume);
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
count++;
|
||||||
|
if (count < maxCount) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::CollapsingHeader("Band Plan", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
|
ImGui::PushItemWidth(menuColumnWidth);
|
||||||
|
if (ImGui::Combo("##_4_", &bandplanId.val, bandplan::bandplanNameTxt.c_str())) {
|
||||||
|
config::config["bandPlan"] = bandplan::bandplanNames[bandplanId.val];
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
ImGui::Checkbox("Enabled", &bandPlanEnabled.val);
|
if (ImGui::Checkbox("Enabled", &bandPlanEnabled.val)) {
|
||||||
|
config::config["bandPlanEnabled"] = bandPlanEnabled.val;
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId.val]];
|
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId.val]];
|
||||||
ImGui::Text("Country: %s (%s)", plan.countryName, plan.countryCode);
|
ImGui::Text("Country: %s (%s)", plan.countryName, plan.countryCode);
|
||||||
ImGui::Text("Author: %s", plan.authorName);
|
ImGui::Text("Author: %s", plan.authorName);
|
||||||
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::CollapsingHeader("Display");
|
if (ImGui::CollapsingHeader("Display")) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::CollapsingHeader("Recording");
|
if (ImGui::CollapsingHeader("Recording")) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
|
||||||
if(ImGui::CollapsingHeader("Debug")) {
|
if(ImGui::CollapsingHeader("Debug")) {
|
||||||
ImGui::Text("Frame time: %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate);
|
ImGui::Text("Frame time: %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate);
|
||||||
ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
|
ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
|
||||||
ImGui::Text("Center Frequency: %.1f FPS", wtf.getCenterFrequency());
|
ImGui::Text("Center Frequency: %.0f Hz", wtf.getCenterFrequency());
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
// Right Column
|
// Right Column
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
|
|
||||||
ImGui::BeginChild("Waterfall");
|
ImGui::BeginChild("Waterfall");
|
||||||
|
|
||||||
wtf.draw();
|
wtf.draw();
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
ImGui::PopStyleVar();
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
|
ImGui::BeginChild("WaterfallControls");
|
||||||
|
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Zoom").x / 2.0f));
|
||||||
ImGui::Text("Zoom");
|
ImGui::Text("Zoom");
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10);
|
||||||
ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw.val, sampleRate, 1000.0f, "");
|
ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw.val, sampleRate, 1000.0f, "");
|
||||||
|
|
||||||
ImGui::NewLine();
|
ImGui::NewLine();
|
||||||
|
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Max").x / 2.0f));
|
||||||
ImGui::Text("Max");
|
ImGui::Text("Max");
|
||||||
ImGui::VSliderFloat("##_8_", ImVec2(20.0f, 150.0f), &fftMax, 0.0f, -100.0f, "");
|
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10);
|
||||||
|
if (ImGui::VSliderFloat("##_8_", ImVec2(20.0f, 150.0f), &fftMax, 0.0f, -100.0f, "")) {
|
||||||
|
config::config["max"] = fftMax;
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::NewLine();
|
ImGui::NewLine();
|
||||||
|
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Min").x / 2.0f));
|
||||||
ImGui::Text("Min");
|
ImGui::Text("Min");
|
||||||
ImGui::VSliderFloat("##_9_", ImVec2(20.0f, 150.0f), &fftMin, 0.0f, -100.0f, "");
|
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10);
|
||||||
|
if (ImGui::VSliderFloat("##_9_", ImVec2(20.0f, 150.0f), &fftMin, 0.0f, -100.0f, "")) {
|
||||||
|
config::config["min"] = fftMin;
|
||||||
|
config::configModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
if (bw.changed()) {
|
if (bw.changed()) {
|
||||||
wtf.setViewBandwidth(bw.val);
|
wtf.setViewBandwidth(bw.val);
|
||||||
@ -371,6 +636,52 @@ void drawWindow() {
|
|||||||
wtf.setFFTMax(fftMax);
|
wtf.setFFTMax(fftMax);
|
||||||
wtf.setWaterfallMin(fftMin);
|
wtf.setWaterfallMin(fftMin);
|
||||||
wtf.setWaterfallMax(fftMax);
|
wtf.setWaterfallMax(fftMax);
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (showCredits) {
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
|
||||||
|
ImGui::OpenPopup("Credits");
|
||||||
|
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
|
||||||
|
|
||||||
|
ImGui::PushFont(bigFont);
|
||||||
|
ImGui::Text("SDR++ ");
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Image(icons::LOGO, ImVec2(128, 128));
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Spacing();
|
||||||
|
|
||||||
|
ImGui::Text("This software is brought to you by\n\n");
|
||||||
|
|
||||||
|
ImGui::Columns(3, "CreditColumns", true);
|
||||||
|
|
||||||
|
// Contributors
|
||||||
|
ImGui::Text("Contributors");
|
||||||
|
ImGui::BulletText("Ryzerth (Creator)");
|
||||||
|
ImGui::BulletText("aosync");
|
||||||
|
ImGui::BulletText("Benjamin Kyd");
|
||||||
|
ImGui::BulletText("Tobias Mädel");
|
||||||
|
ImGui::BulletText("Raov");
|
||||||
|
|
||||||
|
// Libraries
|
||||||
|
ImGui::NextColumn();
|
||||||
|
ImGui::Text("Libraries");
|
||||||
|
ImGui::BulletText("SoapySDR (PothosWare)");
|
||||||
|
ImGui::BulletText("Dear ImGui (ocornut)");
|
||||||
|
ImGui::BulletText("spdlog (gabime)");
|
||||||
|
ImGui::BulletText("json (nlohmann)");
|
||||||
|
ImGui::BulletText("portaudio (PA Comm.)");
|
||||||
|
|
||||||
|
// Patrons
|
||||||
|
ImGui::NextColumn();
|
||||||
|
ImGui::Text("Patrons");
|
||||||
|
ImGui::BulletText("SignalsEverywhere");
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void bindVolumeVariable(float* vol) {
|
void bindVolumeVariable(float* vol) {
|
||||||
|
@ -23,6 +23,9 @@
|
|||||||
#include <watcher.h>
|
#include <watcher.h>
|
||||||
#include <module.h>
|
#include <module.h>
|
||||||
#include <vfo_manager.h>
|
#include <vfo_manager.h>
|
||||||
|
#include <audio.h>
|
||||||
|
#include <style.h>
|
||||||
|
#include <config.h>
|
||||||
|
|
||||||
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
|
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include <module.h>
|
#include <module.h>
|
||||||
#include <vfo_manager.h>
|
#include <vfo_manager.h>
|
||||||
#include <main_window.h>
|
#include <main_window.h>
|
||||||
|
#include <audio.h>
|
||||||
|
|
||||||
namespace mod {
|
namespace mod {
|
||||||
API_t API;
|
API_t API;
|
||||||
@ -14,6 +15,8 @@ namespace mod {
|
|||||||
|
|
||||||
void initAPI(ImGui::WaterFall* wtf) {
|
void initAPI(ImGui::WaterFall* wtf) {
|
||||||
_wtf = wtf;
|
_wtf = wtf;
|
||||||
|
|
||||||
|
// VFO Manager
|
||||||
API.registerVFO = vfoman::create;
|
API.registerVFO = vfoman::create;
|
||||||
API.setVFOOffset = vfoman::setOffset;
|
API.setVFOOffset = vfoman::setOffset;
|
||||||
API.setVFOCenterOffset = vfoman::setCenterOffset;
|
API.setVFOCenterOffset = vfoman::setCenterOffset;
|
||||||
@ -22,9 +25,23 @@ namespace mod {
|
|||||||
API.getVFOOutputBlockSize = vfoman::getOutputBlockSize;
|
API.getVFOOutputBlockSize = vfoman::getOutputBlockSize;
|
||||||
API.setVFOReference = vfoman::setReference;
|
API.setVFOReference = vfoman::setReference;
|
||||||
API.removeVFO = vfoman::remove;
|
API.removeVFO = vfoman::remove;
|
||||||
|
|
||||||
|
// GUI
|
||||||
API.getSelectedVFOName = api_getSelectedVFOName;
|
API.getSelectedVFOName = api_getSelectedVFOName;
|
||||||
API.bindVolumeVariable = bindVolumeVariable;
|
API.bindVolumeVariable = bindVolumeVariable;
|
||||||
API.unbindVolumeVariable = unbindVolumeVariable;
|
API.unbindVolumeVariable = unbindVolumeVariable;
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
API.registerMonoStream = audio::registerMonoStream;
|
||||||
|
API.registerStereoStream = audio::registerStereoStream;
|
||||||
|
API.startStream = audio::startStream;
|
||||||
|
API.stopStream = audio::stopStream;
|
||||||
|
API.removeStream = audio::removeStream;
|
||||||
|
API.bindToStreamMono = audio::bindToStreamMono;
|
||||||
|
API.bindToStreamStereo = audio::bindToStreamStereo;
|
||||||
|
API.setBlockSize = audio::setBlockSize;
|
||||||
|
API.unbindFromStreamMono = audio::unbindFromStreamMono;
|
||||||
|
API.unbindFromStreamStereo = audio::unbindFromStreamStereo;
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadModule(std::string path, std::string name) {
|
void loadModule(std::string path, std::string name) {
|
||||||
|
12
src/module.h
12
src/module.h
@ -29,10 +29,22 @@ namespace mod {
|
|||||||
int (*getVFOOutputBlockSize)(std::string name);
|
int (*getVFOOutputBlockSize)(std::string name);
|
||||||
void (*setVFOReference)(std::string name, int ref);
|
void (*setVFOReference)(std::string name, int ref);
|
||||||
void (*removeVFO)(std::string name);
|
void (*removeVFO)(std::string name);
|
||||||
|
|
||||||
std::string (*getSelectedVFOName)(void);
|
std::string (*getSelectedVFOName)(void);
|
||||||
void (*bindVolumeVariable)(float* vol);
|
void (*bindVolumeVariable)(float* vol);
|
||||||
void (*unbindVolumeVariable)(void);
|
void (*unbindVolumeVariable)(void);
|
||||||
|
|
||||||
|
float (*registerMonoStream)(dsp::stream<float>* stream, std::string name, std::string vfoName, int (*sampleRateChangeHandler)(void* ctx, float sampleRate), void* ctx);
|
||||||
|
float (*registerStereoStream)(dsp::stream<dsp::StereoFloat_t>* stream, std::string name, std::string vfoName, int (*sampleRateChangeHandler)(void* ctx, float sampleRate), void* ctx);
|
||||||
|
void (*startStream)(std::string name);
|
||||||
|
void (*stopStream)(std::string name);
|
||||||
|
void (*removeStream)(std::string name);
|
||||||
|
dsp::stream<float>* (*bindToStreamMono)(std::string name, void (*streamRemovedHandler)(void* ctx), void (*sampleRateChangeHandler)(void* ctx, float sampleRate, int blockSize), void* ctx);
|
||||||
|
dsp::stream<dsp::StereoFloat_t>* (*bindToStreamStereo)(std::string name, void (*streamRemovedHandler)(void* ctx), void (*sampleRateChangeHandler)(void* ctx, float sampleRate, int blockSize), void* ctx);
|
||||||
|
void (*setBlockSize)(std::string name, int blockSize);
|
||||||
|
void (*unbindFromStreamMono)(std::string name, dsp::stream<float>* stream);
|
||||||
|
void (*unbindFromStreamStereo)(std::string name, dsp::stream<dsp::StereoFloat_t>* stream);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
REF_LOWER,
|
REF_LOWER,
|
||||||
REF_CENTER,
|
REF_CENTER,
|
||||||
|
@ -37,7 +37,7 @@ private:
|
|||||||
dsp::HandlerSink fftHandlerSink;
|
dsp::HandlerSink fftHandlerSink;
|
||||||
|
|
||||||
// VFO
|
// VFO
|
||||||
dsp::DynamicSplitter dynSplit;
|
dsp::DynamicSplitter<dsp::complex_t> dynSplit;
|
||||||
std::map<std::string, VFO_t> vfos;
|
std::map<std::string, VFO_t> vfos;
|
||||||
|
|
||||||
float sampleRate;
|
float sampleRate;
|
||||||
|
9
src/style.h
Normal file
9
src/style.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
|
namespace style {
|
||||||
|
void setDefaultStyle();
|
||||||
|
void beginDisabled();
|
||||||
|
void endDisabled();
|
||||||
|
}
|
28
src/styles.cpp
Normal file
28
src/styles.cpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#include <style.h>
|
||||||
|
|
||||||
|
namespace style {
|
||||||
|
void setDefaultStyle() {
|
||||||
|
ImGui::GetStyle().WindowRounding = 0.0f;
|
||||||
|
ImGui::GetStyle().ChildRounding = 0.0f;
|
||||||
|
ImGui::GetStyle().FrameRounding = 0.0f;
|
||||||
|
ImGui::GetStyle().GrabRounding = 0.0f;
|
||||||
|
ImGui::GetStyle().PopupRounding = 0.0f;
|
||||||
|
ImGui::GetStyle().ScrollbarRounding = 0.0f;
|
||||||
|
|
||||||
|
ImGui::GetIO().Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 16.0f);
|
||||||
|
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
//ImGui::StyleColorsLight();
|
||||||
|
}
|
||||||
|
|
||||||
|
void beginDisabled() {
|
||||||
|
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.5f, 0.5f, 0.5f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.35f, 0.35f, 0.35f, 0.35f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void endDisabled() {
|
||||||
|
ImGui::PopItemFlag();
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
}
|
||||||
|
}
|
16
src/styles.h
16
src/styles.h
@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
void setImguiStyle(ImGuiIO& io) {
|
|
||||||
ImGui::GetStyle().WindowRounding = 0.0f;
|
|
||||||
ImGui::GetStyle().ChildRounding = 0.0f;
|
|
||||||
ImGui::GetStyle().FrameRounding = 0.0f;
|
|
||||||
ImGui::GetStyle().GrabRounding = 0.0f;
|
|
||||||
ImGui::GetStyle().PopupRounding = 0.0f;
|
|
||||||
ImGui::GetStyle().ScrollbarRounding = 0.0f;
|
|
||||||
|
|
||||||
io.Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 16.0f);
|
|
||||||
|
|
||||||
ImGui::StyleColorsDark();
|
|
||||||
//ImGui::StyleColorsLight();
|
|
||||||
}
|
|
@ -195,13 +195,19 @@ namespace ImGui {
|
|||||||
ImVec2 mousePos = ImGui::GetMousePos();
|
ImVec2 mousePos = ImGui::GetMousePos();
|
||||||
ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
|
ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
|
||||||
ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y);
|
ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y);
|
||||||
bool draging = ImGui::IsMouseDragging(ImGuiMouseButton_Left);
|
|
||||||
|
bool mouseHovered, mouseHeld;
|
||||||
|
bool mouseClicked = ImGui::ButtonBehavior(ImRect(fftAreaMin, fftAreaMax), ImGuiID("WaterfallID"), &mouseHovered, &mouseHeld,
|
||||||
|
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick);
|
||||||
|
|
||||||
|
bool draging = ImGui::IsMouseDragging(ImGuiMouseButton_Left) && ImGui::IsWindowFocused();
|
||||||
bool mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax);
|
bool mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax);
|
||||||
bool mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax);
|
bool mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax);
|
||||||
|
|
||||||
|
|
||||||
// If mouse was clicked on a VFO, select VFO and return
|
// If mouse was clicked on a VFO, select VFO and return
|
||||||
// If mouse was clicked but not on a VFO, move selected VFO to position
|
// If mouse was clicked but not on a VFO, move selected VFO to position
|
||||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
if (mouseClicked) {
|
||||||
for (auto const& [name, _vfo] : vfos) {
|
for (auto const& [name, _vfo] : vfos) {
|
||||||
if (name == selectedVFO) {
|
if (name == selectedVFO) {
|
||||||
continue;
|
continue;
|
||||||
@ -264,9 +270,11 @@ namespace ImGui {
|
|||||||
float* tempData = new float[dataWidth];
|
float* tempData = new float[dataWidth];
|
||||||
float pixel;
|
float pixel;
|
||||||
float dataRange = waterfallMax - waterfallMin;
|
float dataRange = waterfallMax - waterfallMin;
|
||||||
|
int size;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTs[i].size();
|
size = rawFFTs[i].size();
|
||||||
drawDataStart = (((float)rawFFTs[i].size() / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
|
drawDataSize = (viewBandwidth / wholeBandwidth) * size;
|
||||||
|
drawDataStart = (((float)size / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||||
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs[i], tempData);
|
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs[i], tempData);
|
||||||
for (int j = 0; j < dataWidth; j++) {
|
for (int j = 0; j < dataWidth; j++) {
|
||||||
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||||
@ -365,8 +373,8 @@ namespace ImGui {
|
|||||||
freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11);
|
freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11);
|
||||||
freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50);
|
freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50);
|
||||||
|
|
||||||
maxHSteps = dataWidth / 50;
|
maxHSteps = dataWidth / (ImGui::CalcTextSize("000.000").x + 10);
|
||||||
maxVSteps = fftHeight / 15;
|
maxVSteps = fftHeight / (ImGui::CalcTextSize("000.000").y);
|
||||||
|
|
||||||
range = findBestRange(viewBandwidth, maxHSteps);
|
range = findBestRange(viewBandwidth, maxHSteps);
|
||||||
vRange = findBestRange(fftMax - fftMin, maxVSteps);
|
vRange = findBestRange(fftMax - fftMin, maxVSteps);
|
||||||
|
1
win32/resources.rc
Normal file
1
win32/resources.rc
Normal file
@ -0,0 +1 @@
|
|||||||
|
IDR_MAINFRAME ICON "../res/icons/sdrpp.ico"
|
Loading…
Reference in New Issue
Block a user