mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-07-09 10:35:21 +02:00
Compare commits
27 Commits
new_source
...
new_sinks
Author | SHA1 | Date | |
---|---|---|---|
40f1ee5651 | |||
c4b0cb37a6 | |||
cd7dabda51 | |||
139f63ad25 | |||
75800e0ca2 | |||
84da183559 | |||
0144d8e8ce | |||
7ef88f23a8 | |||
c934e41a61 | |||
9dd6c8546d | |||
191f652fc3 | |||
e208511bde | |||
348bf75281 | |||
cbf1d6703e | |||
03c30f202e | |||
25bc9f60ed | |||
2e80882ab5 | |||
4c584847de | |||
2a741932e0 | |||
ff655caf31 | |||
0277232bdb | |||
2e3e2a7dca | |||
271a77e4ce | |||
48e9708d74 | |||
961cd3f133 | |||
2676190d3a | |||
8851735cb8 |
@ -16,6 +16,7 @@ namespace icons {
|
||||
ImTextureID UNMUTED;
|
||||
ImTextureID NORMAL_TUNING;
|
||||
ImTextureID CENTER_TUNING;
|
||||
ImTextureID ALIGN_CENTER;
|
||||
|
||||
GLuint loadTexture(std::string path) {
|
||||
int w, h, n;
|
||||
@ -45,6 +46,7 @@ namespace icons {
|
||||
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
|
||||
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
|
||||
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png");
|
||||
ALIGN_CENTER = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/align_center.png");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace icons {
|
||||
extern ImTextureID UNMUTED;
|
||||
extern ImTextureID NORMAL_TUNING;
|
||||
extern ImTextureID CENTER_TUNING;
|
||||
extern ImTextureID ALIGN_CENTER;
|
||||
|
||||
GLuint loadTexture(std::string path);
|
||||
bool load(std::string resDir);
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <gui/menus/display.h>
|
||||
#include <gui/menus/bandplan.h>
|
||||
#include <gui/menus/sink.h>
|
||||
#include <gui/menus/streams.h>
|
||||
#include <gui/menus/vfo_color.h>
|
||||
#include <gui/menus/module_manager.h>
|
||||
#include <gui/menus/theme.h>
|
||||
@ -72,6 +73,7 @@ void MainWindow::init() {
|
||||
|
||||
gui::menu.registerEntry("Source", sourcemenu::draw, NULL);
|
||||
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Streams", streamsmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
|
||||
gui::menu.registerEntry("Theme", thememenu::draw, NULL);
|
||||
@ -165,6 +167,7 @@ void MainWindow::init() {
|
||||
|
||||
sourcemenu::init();
|
||||
sinkmenu::init();
|
||||
streamsmenu::init();
|
||||
bandplanmenu::init();
|
||||
displaymenu::init();
|
||||
vfo_color_menu::init();
|
||||
|
177
core/src/gui/menus/streams.cpp
Normal file
177
core/src/gui/menus/streams.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
#include "streams.h"
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <imgui.h>
|
||||
#include <utils/flog.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/icons.h>
|
||||
#include <utils/optionlist.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
namespace streamsmenu {
|
||||
std::vector<SinkID> sinksToBeRemoved;
|
||||
|
||||
std::recursive_mutex sinkTypesMtx;
|
||||
OptionList<std::string, std::string> sinkTypes;
|
||||
|
||||
std::map<std::string, int> selectedSinkTypeId;
|
||||
std::map<std::string, int> addSinkTypeId;
|
||||
|
||||
int addType = 0;
|
||||
|
||||
void updateSinkTypeList(const std::string& removed = "") {
|
||||
std::lock_guard<std::recursive_mutex> lck1(sinkTypesMtx);
|
||||
auto lck2 = sigpath::streamManager.getSinkTypesLock();
|
||||
const auto& types = sigpath::streamManager.getSinkTypes();
|
||||
sinkTypes.clear();
|
||||
for (const auto& type : types) {
|
||||
if (type == removed) { continue; }
|
||||
sinkTypes.define(type, type, type);
|
||||
}
|
||||
}
|
||||
|
||||
void onSinkProviderRegistered(const std::string& type) {
|
||||
// Update the list
|
||||
updateSinkTypeList();
|
||||
|
||||
// Update the ID of the Add dropdown
|
||||
// TODO
|
||||
|
||||
// Update the selected ID of each drop down
|
||||
// TODO
|
||||
}
|
||||
|
||||
void onSinkProviderUnregister(const std::string& type) {
|
||||
// Update the list
|
||||
updateSinkTypeList(type);
|
||||
|
||||
// Update the ID of the Add dropdown
|
||||
// TODO
|
||||
|
||||
// Update the selected ID of each drop down
|
||||
// TODO
|
||||
}
|
||||
|
||||
void init() {
|
||||
sigpath::streamManager.onSinkProviderRegistered.bind(onSinkProviderRegistered);
|
||||
sigpath::streamManager.onSinkProviderUnregister.bind(onSinkProviderUnregister);
|
||||
updateSinkTypeList();
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
auto lck = sigpath::streamManager.getStreamsLock();
|
||||
const auto& streams = sigpath::streamManager.getStreams();
|
||||
|
||||
int count = 0;
|
||||
int maxCount = streams.size();
|
||||
for (auto& [name, stream] : streams) {
|
||||
// Stream name
|
||||
ImGui::SetCursorPosX((menuWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f));
|
||||
ImGui::Text("%s", name.c_str());
|
||||
|
||||
// Display ever sink
|
||||
if (ImGui::BeginTable(CONCAT("sdrpp_streams_tbl_", name), 1, ImGuiTableFlags_Borders)) {
|
||||
auto lck2 = stream->getSinksLock();
|
||||
auto sinks = stream->getSinks();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
std::string sid = sink->getStringID();
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
float tableWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Sink type
|
||||
int ttttt = 0;
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(CONCAT("##sdrpp_streams_type_", sid), &ttttt, sinkTypes.txt)) {
|
||||
|
||||
}
|
||||
|
||||
sink->showMenu();
|
||||
float vol = sink->getVolume();
|
||||
bool muted = sink->getMuted();
|
||||
float pan = sink->getPanning();
|
||||
bool linked = true;
|
||||
|
||||
float height = ImGui::GetTextLineHeightWithSpacing() + 2;
|
||||
ImGui::PushID(ImGui::GetID(("sdrpp_streams_center_btn_" + sid).c_str()));
|
||||
if (ImGui::ImageButton(icons::ALIGN_CENTER, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
sink->setPanning(0.0f);
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_pan_", sid), &pan, -1, 1, "")) {
|
||||
sink->setPanning(pan);
|
||||
}
|
||||
|
||||
if (muted) {
|
||||
ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + sid).c_str()));
|
||||
if (ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
sink->setMuted(false);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
else {
|
||||
ImGui::PushID(ImGui::GetID(("sdrpp_mute_btn_" + sid).c_str()));
|
||||
if (ImGui::ImageButton(icons::UNMUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
sink->setMuted(true);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_vol_", sid), &vol, 0, 1, "")) {
|
||||
sink->setVolume(vol);
|
||||
}
|
||||
|
||||
|
||||
int startCur = ImGui::GetCursorPosX();
|
||||
if (ImGui::Checkbox(CONCAT("Link volume##sdrpp_streams_vol_", sid), &linked)) {
|
||||
// TODO
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(CONCAT("Remove##sdrpp_streams_remove_type_", sid), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
|
||||
sinksToBeRemoved.push_back(id);
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
lck2.unlock();
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
float tableWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::Spacing();
|
||||
int startCur = ImGui::GetCursorPosX();
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lck(sinkTypesMtx);
|
||||
ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &addType, sinkTypes.txt);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
|
||||
stream->addSink(sinkTypes.value(addType));
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
|
||||
// Remove sinks that need to be removed
|
||||
if (!sinksToBeRemoved.empty()) {
|
||||
for (auto& id : sinksToBeRemoved) {
|
||||
stream->removeSink(id);
|
||||
}
|
||||
sinksToBeRemoved.clear();
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count < maxCount) {
|
||||
ImGui::Spacing();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
}
|
||||
};
|
6
core/src/gui/menus/streams.h
Normal file
6
core/src/gui/menus/streams.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace streamsmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
};
|
@ -5,4 +5,5 @@ namespace sigpath {
|
||||
VFOManager vfoManager;
|
||||
SourceManager sourceManager;
|
||||
SinkManager sinkManager;
|
||||
StreamManager streamManager;
|
||||
};
|
@ -3,6 +3,7 @@
|
||||
#include "vfo_manager.h"
|
||||
#include "source.h"
|
||||
#include "sink.h"
|
||||
#include "stream.h"
|
||||
#include <module.h>
|
||||
|
||||
namespace sigpath {
|
||||
@ -10,4 +11,5 @@ namespace sigpath {
|
||||
SDRPP_EXPORT VFOManager vfoManager;
|
||||
SDRPP_EXPORT SourceManager sourceManager;
|
||||
SDRPP_EXPORT SinkManager sinkManager;
|
||||
SDRPP_EXPORT StreamManager streamManager;
|
||||
};
|
519
core/src/signal_path/stream.cpp
Normal file
519
core/src/signal_path/stream.cpp
Normal file
@ -0,0 +1,519 @@
|
||||
#include "stream.h"
|
||||
#include <utils/flog.h>
|
||||
|
||||
Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
|
||||
entry(entry),
|
||||
stream(stream),
|
||||
streamName(name),
|
||||
id(id),
|
||||
stringId(stringId)
|
||||
{}
|
||||
|
||||
void Sink::showMenu() {}
|
||||
|
||||
SinkEntry::SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate) :
|
||||
manager(manager),
|
||||
parentStream(parentStream),
|
||||
id(id)
|
||||
{
|
||||
this->type = type;
|
||||
this->inputSamplerate = inputSamplerate;
|
||||
|
||||
// Generate string ID
|
||||
stringId = parentStream->getName();
|
||||
char buf[16];
|
||||
sprintf(buf, "%d", (int)id);
|
||||
stringId += buf;
|
||||
|
||||
// Initialize DSP
|
||||
resamp.init(&input, inputSamplerate, inputSamplerate);
|
||||
volumeAdjust.init(&resamp.out, 1.0f, false);
|
||||
|
||||
// Initialize the sink
|
||||
setType(type);
|
||||
}
|
||||
|
||||
std::string SinkEntry::getType() const {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
return type;
|
||||
}
|
||||
|
||||
void SinkEntry::setType(const std::string& type) {
|
||||
// Get unique lock on the entry
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
|
||||
// Delete existing sink
|
||||
if (sink) {
|
||||
provider->destroySink(std::move(sink));
|
||||
}
|
||||
|
||||
// Get shared lock on sink types
|
||||
auto lck2 = manager->getSinkTypesLock();
|
||||
|
||||
// Get the provider or throw error
|
||||
const auto& types = manager->getSinkTypes();
|
||||
if (std::find(types.begin(), types.end(), type) == types.end()) {
|
||||
this->type.clear();
|
||||
throw SinkEntryCreateException("Invalid sink type");
|
||||
}
|
||||
|
||||
// Create sink
|
||||
this->type = type;
|
||||
provider = manager->providers[type];
|
||||
sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id, stringId);
|
||||
}
|
||||
|
||||
SinkID SinkEntry::getID() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
float SinkEntry::getVolume() const {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
return volume;
|
||||
}
|
||||
|
||||
void SinkEntry::setVolume(float volume) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
this->volume = volume;
|
||||
volumeAdjust.setVolume(volume);
|
||||
onVolumeChanged(volume);
|
||||
}
|
||||
|
||||
bool SinkEntry::getMuted() const {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
return muted;
|
||||
}
|
||||
|
||||
void SinkEntry::setMuted(bool muted) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
this->muted = muted;
|
||||
volumeAdjust.setMuted(muted);
|
||||
onMutedChanged(muted);
|
||||
}
|
||||
|
||||
float SinkEntry::getPanning() const {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
return panning;
|
||||
}
|
||||
|
||||
void SinkEntry::setPanning(float panning) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
this->panning = panning;
|
||||
// TODO
|
||||
onPanningChanged(panning);
|
||||
}
|
||||
|
||||
void SinkEntry::showMenu() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
sink->showMenu();
|
||||
}
|
||||
|
||||
void SinkEntry::startSink() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
sink->start();
|
||||
}
|
||||
|
||||
void SinkEntry::stopSink() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
sink->stop();
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> SinkEntry::getLock() const {
|
||||
return std::lock_guard<std::recursive_mutex>(mtx);
|
||||
}
|
||||
|
||||
void SinkEntry::setSamplerate(double samplerate) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
resamp.setOutSamplerate(samplerate);
|
||||
}
|
||||
|
||||
void SinkEntry::startDSP() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
resamp.start();
|
||||
volumeAdjust.start();
|
||||
}
|
||||
|
||||
void SinkEntry::stopDSP() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
resamp.stop();
|
||||
volumeAdjust.stop();
|
||||
}
|
||||
|
||||
void SinkEntry::destroy(bool forgetSettings) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
if (sink) {
|
||||
provider->destroySink(std::move(sink));
|
||||
}
|
||||
type.clear();
|
||||
}
|
||||
|
||||
void SinkEntry::setInputSamplerate(double samplerate) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
resamp.setInSamplerate(samplerate);
|
||||
}
|
||||
|
||||
std::string SinkEntry::getStringID() const {
|
||||
return stringId;
|
||||
}
|
||||
|
||||
Stream::Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
|
||||
manager(manager),
|
||||
name(name)
|
||||
{
|
||||
this->samplerate = samplerate;
|
||||
|
||||
// Initialize DSP
|
||||
split.init(stream);
|
||||
}
|
||||
|
||||
Stream::~Stream() {
|
||||
// Copy sink IDs
|
||||
std::vector<SinkID> ids;
|
||||
for (auto& [id, sink] : sinks) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
|
||||
// Remove them all
|
||||
for (auto& id : ids) {
|
||||
removeSink(id, false);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& Stream::getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
SinkID Stream::addSink(const std::string& type, SinkID id) {
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Find a free ID if not provided
|
||||
if (id < 0) {
|
||||
for (id = 0; sinks.find(id) != sinks.end(); id++);
|
||||
}
|
||||
else {
|
||||
// Check that the provided ID is valid
|
||||
if (sinks.find(id) != sinks.end()) {
|
||||
flog::error("Tried to create sink for stream '{}' with existing ID: {}", name, id);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Create sink entry
|
||||
std::shared_ptr<SinkEntry> sink;
|
||||
try {
|
||||
sink = std::make_shared<SinkEntry>(manager, this, type, id, samplerate);
|
||||
}
|
||||
catch (SinkEntryCreateException e) {
|
||||
flog::error("Tried to create sink for stream '{}' with ID '{}': {}", name, id, e.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start the sink and DSP
|
||||
sink->startSink();
|
||||
if (running) { sink->startDSP(); }
|
||||
|
||||
// Bind the sinks's input
|
||||
split.bindStream(&sink->input);
|
||||
|
||||
// Add sink to list
|
||||
sinks[id] = sink;
|
||||
|
||||
// Release lock and emit event
|
||||
lck.unlock();
|
||||
onSinkAdded(sink);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Stream::removeSink(SinkID id, bool forgetSettings) {
|
||||
// Acquire shared lock
|
||||
std::shared_ptr<SinkEntry> sink;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Check that the ID exists
|
||||
if (sinks.find(id) == sinks.end()) {
|
||||
flog::error("Tried to remove sink with unknown ID: {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get sink
|
||||
sink = sinks[id];
|
||||
}
|
||||
|
||||
// Emit event
|
||||
onSinkRemove(sink);
|
||||
|
||||
// Acquire unique lock
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Check that it's still in the list
|
||||
if (sinks.find(id) == sinks.end()) {
|
||||
flog::error("Tried to remove sink with unknown ID: {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from list
|
||||
sinks.erase(id);
|
||||
|
||||
// Unbind the sink's steam
|
||||
split.unbindStream(&sink->input);
|
||||
|
||||
// Stop the sink and DSP
|
||||
sink->stopDSP();
|
||||
sink->stopSink();
|
||||
|
||||
// Delete instance
|
||||
sink->destroy(forgetSettings);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> Stream::getSinksLock() {
|
||||
return std::shared_lock<std::shared_mutex>(sinksMtx);
|
||||
}
|
||||
|
||||
const std::map<SinkID, std::shared_ptr<SinkEntry>>& Stream::getSinks() const {
|
||||
return sinks;
|
||||
}
|
||||
|
||||
MasterStream::MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
|
||||
Stream(manager, name, stream, samplerate)
|
||||
{}
|
||||
|
||||
void MasterStream::setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate) {
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// If all that's needed is to set the input, do it and return
|
||||
if (samplerate == 0.0) {
|
||||
split.setInput(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update samplerate
|
||||
this->samplerate = samplerate;
|
||||
|
||||
// Stop DSP
|
||||
if (running) {
|
||||
split.stop();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->stopDSP();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set input and samplerate
|
||||
split.setInput(stream);
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->setInputSamplerate(samplerate);
|
||||
}
|
||||
|
||||
// Start DSP
|
||||
if (running) {
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->startDSP();
|
||||
}
|
||||
split.start();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterStream::setSamplerate(double samplerate) {
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Update samplerate
|
||||
this->samplerate = samplerate;
|
||||
|
||||
// TODO: Maybe simply disallow while running?
|
||||
|
||||
// Stop DSP if it was running
|
||||
if (running) {
|
||||
split.stop();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->stopDSP();
|
||||
}
|
||||
}
|
||||
|
||||
// Set samplerate
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->setInputSamplerate(samplerate);
|
||||
}
|
||||
|
||||
// Start DSP if it was running
|
||||
if (running) {
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->startDSP();
|
||||
}
|
||||
split.start();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterStream::startDSP() {
|
||||
// TODO: Maybe add a different mutex for the stream?
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Check if already running
|
||||
if (running) { return; }
|
||||
|
||||
// Start all DSP
|
||||
split.start();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->startDSP();
|
||||
}
|
||||
running = true;
|
||||
}
|
||||
|
||||
void MasterStream::stopDSP() {
|
||||
// TODO: Maybe add a different mutex for the stream?
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Check if already running
|
||||
if (!running) { return; }
|
||||
|
||||
// Start all DSP
|
||||
split.stop();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->stopDSP();
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
|
||||
std::shared_ptr<MasterStream> StreamManager::createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) {
|
||||
std::unique_lock<std::shared_mutex> lck(streamsMtx);
|
||||
|
||||
// Check that no stream with that name already exists
|
||||
if (streams.find(name) != streams.end()) {
|
||||
flog::error("Tried to created stream with an existing name: {}", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create and save stream
|
||||
std::shared_ptr<MasterStream> newStream(new MasterStream(this, name, stream, samplerate));
|
||||
streams[name] = newStream;
|
||||
|
||||
// Release lock and emit event
|
||||
lck.unlock();
|
||||
onStreamCreated(newStream);
|
||||
|
||||
return newStream;
|
||||
}
|
||||
|
||||
void StreamManager::destroyStream(std::shared_ptr<MasterStream>& stream) {
|
||||
// Emit event
|
||||
onStreamDestroy(stream);
|
||||
|
||||
// Aquire complete lock on the stream list
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lck(streamsMtx);
|
||||
|
||||
// Get iterator of the stream
|
||||
auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair<const std::string, std::shared_ptr<Stream>> e) {
|
||||
return e.second == stream;
|
||||
});
|
||||
if (it == streams.end()) {
|
||||
flog::error("Tried to delete a stream using an invalid pointer. Stream not found in list");
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete entry from list
|
||||
flog::debug("Stream pointer uses, should be 2 and is {}", (int)stream.use_count());
|
||||
streams.erase(it);
|
||||
}
|
||||
|
||||
// Reset passed pointer
|
||||
stream.reset();
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> StreamManager::getStreamsLock() {
|
||||
return std::shared_lock<std::shared_mutex>(streamsMtx);
|
||||
}
|
||||
|
||||
const std::map<std::string, std::shared_ptr<Stream>>& StreamManager::getStreams() const {
|
||||
return streams;
|
||||
}
|
||||
|
||||
void StreamManager::registerSinkProvider(const std::string& name, SinkProvider* provider) {
|
||||
std::unique_lock<std::shared_mutex> lck(providersMtx);
|
||||
|
||||
// Check that a provider with that name doesn't already exist
|
||||
if (providers.find(name) != providers.end()) {
|
||||
flog::error("Tried to register a sink provider with an existing name: {}", name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add provider to the list and sort name list
|
||||
providers[name] = provider;
|
||||
sinkTypes.push_back(name);
|
||||
std::sort(sinkTypes.begin(), sinkTypes.end());
|
||||
|
||||
// Release lock and emit event
|
||||
lck.unlock();
|
||||
onSinkProviderRegistered(name);
|
||||
}
|
||||
|
||||
void StreamManager::unregisterSinkProvider(SinkProvider* provider) {
|
||||
// Get provider name for event
|
||||
std::string type;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lck(providersMtx);
|
||||
auto it = std::find_if(providers.begin(), providers.end(), [&provider](std::pair<const std::string, SinkProvider *> e) {
|
||||
return e.second == provider;
|
||||
});
|
||||
if (it == providers.end()) {
|
||||
flog::error("Tried to unregister sink provider using invalid pointer");
|
||||
return;
|
||||
}
|
||||
type = (*it).first;
|
||||
}
|
||||
|
||||
// Emit event
|
||||
onSinkProviderUnregister(type);
|
||||
|
||||
// Acquire shared lock on streams
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lck1(providersMtx);
|
||||
std::shared_lock<std::shared_mutex> lck2(streamsMtx);
|
||||
for (auto& [name, stream] : streams) {
|
||||
// Aquire lock on sink list
|
||||
auto sLock = stream->getSinksLock();
|
||||
const auto& sinks = stream->getSinks();
|
||||
|
||||
// Find all sinks with the type that is about to be removed
|
||||
std::vector<SinkID> toRemove;
|
||||
for (auto& [id, sink] : sinks) {
|
||||
if (sink->getType() != type) { continue; }
|
||||
toRemove.push_back(id);
|
||||
}
|
||||
|
||||
// Remove them all (TODO: THERE IS RACE CONDITION IF A SINK IS CHANGED AFTER LISTING)
|
||||
sLock.unlock();
|
||||
for (auto& id : toRemove) {
|
||||
stream->removeSink(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from the lists
|
||||
if (providers.find(type) != providers.end()) {
|
||||
providers.erase(type);
|
||||
}
|
||||
else {
|
||||
flog::error("Could not remove sink provider from list");
|
||||
}
|
||||
|
||||
auto it = std::find(sinkTypes.begin(), sinkTypes.end(), type);
|
||||
if (it != sinkTypes.end()) {
|
||||
sinkTypes.erase(it);
|
||||
}
|
||||
else {
|
||||
flog::error("Could not remove sink provider from sink type list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> StreamManager::getSinkTypesLock() {
|
||||
return std::shared_lock<std::shared_mutex>(providersMtx);
|
||||
}
|
||||
|
||||
const std::vector<std::string>& StreamManager::getSinkTypes() const {
|
||||
// TODO: This allows code to modify the names...
|
||||
return sinkTypes;
|
||||
}
|
334
core/src/signal_path/stream.h
Normal file
334
core/src/signal_path/stream.h
Normal file
@ -0,0 +1,334 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <dsp/routing/splitter.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/audio/volume.h>
|
||||
#include <utils/new_event.h>
|
||||
#include <shared_mutex>
|
||||
#include <stdexcept>
|
||||
|
||||
class SinkEntry;
|
||||
class Stream;
|
||||
class MasterStream;
|
||||
class StreamManager;
|
||||
|
||||
using SinkID = int;
|
||||
|
||||
class Sink {
|
||||
public:
|
||||
Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId);
|
||||
virtual ~Sink() {}
|
||||
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void showMenu();
|
||||
|
||||
protected:
|
||||
SinkEntry* const entry;
|
||||
dsp::stream<dsp::stereo_t>* const stream;
|
||||
const std::string streamName;
|
||||
const SinkID id;
|
||||
const std::string stringId;
|
||||
};
|
||||
|
||||
class SinkProvider {
|
||||
friend Sink;
|
||||
public:
|
||||
/**
|
||||
* Create a sink instance.
|
||||
* @param name Name of the audio stream.
|
||||
* @param index Index of the sink in the menu. Should be use to keep settings.
|
||||
*/
|
||||
virtual std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) = 0;
|
||||
|
||||
/**
|
||||
* Destroy a sink instance. This function is so that the provide knows at all times how many instances there are.
|
||||
* @param sink Instance of the sink.
|
||||
*/
|
||||
virtual void destroySink(std::unique_ptr<Sink> sink) {
|
||||
sink.reset();
|
||||
}
|
||||
};
|
||||
|
||||
class SinkEntryCreateException : public std::runtime_error {
|
||||
public:
|
||||
SinkEntryCreateException(const char* what) : std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
// TODO: Would be cool to have data and audio sinks instead of just audio.
|
||||
class SinkEntry {
|
||||
friend Sink;
|
||||
friend Stream;
|
||||
friend MasterStream;
|
||||
public:
|
||||
SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate);
|
||||
|
||||
/**
|
||||
* Get the type of the sink.
|
||||
* @return Type of the sink.
|
||||
*/
|
||||
std::string getType() const;
|
||||
|
||||
/**
|
||||
* Change the type of the sink.
|
||||
* @param type New sink type.
|
||||
*/
|
||||
void setType(const std::string& type);
|
||||
|
||||
/**
|
||||
* Get the ID of the sink.
|
||||
* @return ID of the sink.
|
||||
*/
|
||||
SinkID getID() const;
|
||||
|
||||
/**
|
||||
* Get sink volume.
|
||||
* @return Volume as value between 0.0 and 1.0.
|
||||
*/
|
||||
float getVolume() const;
|
||||
|
||||
/**
|
||||
* Set sink volume.
|
||||
* @param volume Volume as value between 0.0 and 1.0.
|
||||
*/
|
||||
void setVolume(float volume);
|
||||
|
||||
/**
|
||||
* Check if the sink is muted.
|
||||
* @return True if muted, false if not.
|
||||
*/
|
||||
bool getMuted() const;
|
||||
|
||||
/**
|
||||
* Set wether or not the sink is muted
|
||||
* @param muted True to mute, false to unmute.
|
||||
*/
|
||||
void setMuted(bool muted);
|
||||
|
||||
/**
|
||||
* Get sink panning.
|
||||
* @return Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively.
|
||||
*/
|
||||
float getPanning() const;
|
||||
|
||||
/**
|
||||
* Set sink panning.
|
||||
* @param panning Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively.
|
||||
*/
|
||||
void setPanning(float panning);
|
||||
|
||||
/**
|
||||
* Show the sink type-specific menu.
|
||||
*/
|
||||
void showMenu();
|
||||
|
||||
/**
|
||||
* Get the string form ID unique to both the sink and stream. Be used to reference settings.
|
||||
* @return Unique string ID.
|
||||
*/
|
||||
std::string getStringID() const;
|
||||
|
||||
// Emitted when the type of the sink was changed
|
||||
NewEvent<const std::string&> onTypeChanged;
|
||||
// Emmited when volume of the sink was changed
|
||||
NewEvent<float> onVolumeChanged;
|
||||
// Emitted when the muted state of the sink was changed
|
||||
NewEvent<bool> onMutedChanged;
|
||||
// Emitted when the panning of the sink was changed
|
||||
NewEvent<float> onPanningChanged;
|
||||
|
||||
// TODO: Need to allow the sink to change the entry samplerate and start/stop the DSP
|
||||
// This will also require allowing it to get a lock on the sink so others don't attempt to mess with it.
|
||||
std::lock_guard<std::recursive_mutex> getLock() const;
|
||||
void startDSP();
|
||||
void stopDSP();
|
||||
void setSamplerate(double samplerate);
|
||||
|
||||
private:
|
||||
void startSink();
|
||||
void stopSink();
|
||||
|
||||
void destroy(bool forgetSettings);
|
||||
void setInputSamplerate(double samplerate);
|
||||
|
||||
mutable std::recursive_mutex mtx;
|
||||
dsp::stream<dsp::stereo_t> input;
|
||||
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
|
||||
dsp::audio::Volume volumeAdjust;
|
||||
|
||||
SinkProvider* provider = NULL;
|
||||
std::unique_ptr<Sink> sink;
|
||||
std::string type;
|
||||
const SinkID id;
|
||||
double inputSamplerate;
|
||||
Stream* const parentStream;
|
||||
StreamManager* const manager;
|
||||
|
||||
std::string stringId;
|
||||
|
||||
float volume = 1.0f;
|
||||
bool muted = false;
|
||||
float panning = 0.0f;
|
||||
};
|
||||
|
||||
class Stream {
|
||||
protected:
|
||||
Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
||||
public:
|
||||
~Stream();
|
||||
|
||||
/**
|
||||
* Get the name of the stream.
|
||||
* @return Name of the stream.
|
||||
*/
|
||||
const std::string& getName() const;
|
||||
|
||||
/**
|
||||
* Add a sink to the stream.
|
||||
* @param type Type of the sink.
|
||||
* @param id ID of the sink. Optional, -1 if automatic.
|
||||
* @return ID of the new sink or -1 on error.
|
||||
*/
|
||||
SinkID addSink(const std::string& type, SinkID id = -1);
|
||||
|
||||
/**
|
||||
* Remove a sink from a stream.
|
||||
* @param id ID of the sink.
|
||||
* @param forgetSettings Forget the settings for the sink.
|
||||
*/
|
||||
void removeSink(SinkID id, bool forgetSettings = true);
|
||||
|
||||
/**
|
||||
* Aquire a lock for the sink list.
|
||||
* @return Shared lock for the sink list.
|
||||
*/
|
||||
std::shared_lock<std::shared_mutex> getSinksLock();
|
||||
|
||||
/**
|
||||
* Get the list of all sinks belonging to this stream.
|
||||
* @return Sink list.
|
||||
*/
|
||||
const std::map<SinkID, std::shared_ptr<SinkEntry>>& getSinks() const;
|
||||
|
||||
// Emitted when the samplerate of the stream was changed
|
||||
NewEvent<double> onSamplerateChanged;
|
||||
// Emitted when a sink was added
|
||||
NewEvent<std::shared_ptr<SinkEntry>> onSinkAdded;
|
||||
// Emitted when a sink is being removed
|
||||
NewEvent<std::shared_ptr<SinkEntry>> onSinkRemove;
|
||||
|
||||
protected:
|
||||
StreamManager* const manager;
|
||||
const std::string name;
|
||||
double samplerate;
|
||||
dsp::routing::Splitter<dsp::stereo_t> split;
|
||||
bool running = false;
|
||||
|
||||
std::map<SinkID, std::shared_ptr<SinkEntry>> sinks;
|
||||
std::shared_mutex sinksMtx;
|
||||
};
|
||||
|
||||
class MasterStream : public Stream {
|
||||
friend StreamManager;
|
||||
MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
||||
public:
|
||||
/**
|
||||
* Set DSP stream input.
|
||||
* @param stream DSP stream.
|
||||
* @param samplerate New samplerate (optional, 0.0 if not used).
|
||||
*/
|
||||
void setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate = 0.0);
|
||||
|
||||
/**
|
||||
* Set the samplerate of the input stream.
|
||||
* @param samplerate Samplerate in Hz.
|
||||
*/
|
||||
void setSamplerate(double samplerate);
|
||||
|
||||
/**
|
||||
* Start the DSP.
|
||||
*/
|
||||
void startDSP();
|
||||
|
||||
/**
|
||||
* Stop the DSP.
|
||||
*/
|
||||
void stopDSP();
|
||||
};
|
||||
|
||||
class StreamManager {
|
||||
friend SinkEntry;
|
||||
public:
|
||||
/**
|
||||
* Create an audio stream.
|
||||
* @param name Name of the stream.
|
||||
* @param stream DSP stream that outputs the audio.
|
||||
* @param samplerate Samplerate of the audio data.
|
||||
* @return Audio stream instance.
|
||||
*/
|
||||
std::shared_ptr<MasterStream> createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
||||
|
||||
/**
|
||||
* Destroy an audio stream.
|
||||
* @param stream Stream to destroy. The passed shared pointer will be automatically reset.
|
||||
*/
|
||||
void destroyStream(std::shared_ptr<MasterStream>& stream);
|
||||
|
||||
/**
|
||||
* Aquire a lock for the stream list.
|
||||
* @return Shared lock for the stream list.
|
||||
*/
|
||||
std::shared_lock<std::shared_mutex> getStreamsLock();
|
||||
|
||||
/**
|
||||
* Get a list of streams and their associated names.
|
||||
* @return Map of names to stream instance.
|
||||
*/
|
||||
const std::map<std::string, std::shared_ptr<Stream>>& getStreams() const;
|
||||
|
||||
/**
|
||||
* Register a sink provider.
|
||||
* @param name Name of the sink type.
|
||||
* @param provider Sink provider instance.
|
||||
*/
|
||||
void registerSinkProvider(const std::string& name, SinkProvider* provider);
|
||||
|
||||
/**
|
||||
* Unregister a sink provider.
|
||||
* @param name Name of the sink type.
|
||||
*/
|
||||
void unregisterSinkProvider(SinkProvider* provider);
|
||||
|
||||
/**
|
||||
* Aquire a lock for the sink type list.
|
||||
* @return Shared lock for the sink type list.
|
||||
*/
|
||||
std::shared_lock<std::shared_mutex> getSinkTypesLock();
|
||||
|
||||
/**
|
||||
* Get a list of sink types.
|
||||
* @return List of sink type names in alphabetical order.
|
||||
*/
|
||||
const std::vector<std::string>& getSinkTypes() const;
|
||||
|
||||
// Emitted when a stream was created
|
||||
NewEvent<std::shared_ptr<Stream>> onStreamCreated;
|
||||
// Emitted when a stream is about to be destroyed
|
||||
NewEvent<std::shared_ptr<Stream>> onStreamDestroy;
|
||||
// Emitted when a sink provider was registered
|
||||
NewEvent<const std::string&> onSinkProviderRegistered;
|
||||
// Emitted when a sink provider is about to be unregistered
|
||||
NewEvent<const std::string&> onSinkProviderUnregister;
|
||||
|
||||
private:
|
||||
std::map<std::string, std::shared_ptr<Stream>> streams;
|
||||
std::shared_mutex streamsMtx;
|
||||
|
||||
std::map<std::string, SinkProvider*> providers;
|
||||
std::vector<std::string> sinkTypes;
|
||||
std::shared_mutex providersMtx;
|
||||
};
|
@ -63,6 +63,7 @@ namespace net::http {
|
||||
|
||||
std::string MessageHeader::getField(const std::string name) {
|
||||
// TODO: Check if exists
|
||||
// TODO: Maybe declare the set/get field functions to do type conversions automatically?
|
||||
return fields[name];
|
||||
}
|
||||
|
||||
|
@ -24,13 +24,12 @@ namespace demod {
|
||||
class Demodulator {
|
||||
public:
|
||||
virtual ~Demodulator() {}
|
||||
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) = 0;
|
||||
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) = 0;
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void showMenu() = 0;
|
||||
virtual void setBandwidth(double bandwidth) = 0;
|
||||
virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0;
|
||||
virtual void AFSampRateChanged(double newSR) = 0;
|
||||
virtual const char* getName() = 0;
|
||||
virtual double getIFSampleRate() = 0;
|
||||
virtual double getAFSampleRate() = 0;
|
||||
|
@ -7,13 +7,13 @@ namespace demod {
|
||||
public:
|
||||
AM() {}
|
||||
|
||||
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
}
|
||||
|
||||
~AM() { stop(); }
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@ -68,8 +68,6 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "AM"; }
|
||||
|
@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
CW() {}
|
||||
|
||||
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
}
|
||||
|
||||
~CW() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
this->name = name;
|
||||
this->_config = config;
|
||||
this->afbwChangeHandler = afbwChangeHandler;
|
||||
@ -74,8 +74,6 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "CW"; }
|
||||
|
@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
DSB() {}
|
||||
|
||||
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
}
|
||||
|
||||
~DSB() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@ -61,8 +61,6 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "DSB"; }
|
||||
|
@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
LSB() {}
|
||||
|
||||
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
}
|
||||
|
||||
~LSB() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@ -61,8 +61,6 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "LSB"; }
|
||||
|
@ -7,13 +7,13 @@ namespace demod {
|
||||
public:
|
||||
NFM() {}
|
||||
|
||||
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
}
|
||||
|
||||
~NFM() { stop(); }
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
this->name = name;
|
||||
this->_config = config;
|
||||
|
||||
@ -57,8 +57,6 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "FM"; }
|
||||
|
@ -7,17 +7,18 @@ namespace demod {
|
||||
public:
|
||||
RAW() {}
|
||||
|
||||
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
}
|
||||
|
||||
~RAW() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
this->name = name;
|
||||
audioSampleRate = audioSR;
|
||||
audioSampleRate = 48000;
|
||||
// TODO: This needs to be selectable
|
||||
|
||||
// Define structure
|
||||
c2s.init(input);
|
||||
@ -39,10 +40,6 @@ namespace demod {
|
||||
c2s.setInput(input);
|
||||
}
|
||||
|
||||
void AFSampRateChanged(double newSR) {
|
||||
audioSampleRate = newSR;
|
||||
}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "RAW"; }
|
||||
|
@ -8,15 +8,15 @@ namespace demod {
|
||||
public:
|
||||
USB() {}
|
||||
|
||||
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
}
|
||||
|
||||
~USB() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@ -62,8 +62,6 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "USB"; }
|
||||
|
@ -16,8 +16,8 @@ namespace demod {
|
||||
public:
|
||||
WFM() : diag(0.5, 4096) {}
|
||||
|
||||
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) : diag(0.5, 4096) {
|
||||
init(name, config, input, bandwidth);
|
||||
}
|
||||
|
||||
~WFM() {
|
||||
@ -25,7 +25,7 @@ namespace demod {
|
||||
gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler);
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@ -252,8 +252,6 @@ namespace demod {
|
||||
demod.setInput(input);
|
||||
}
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "WFM"; }
|
||||
|
@ -81,30 +81,16 @@ public:
|
||||
// Initialize audio DSP chain
|
||||
afChain.init(&dummyAudioStream);
|
||||
|
||||
resamp.init(NULL, 250000.0, 48000.0);
|
||||
deemp.init(NULL, 50e-6, 48000.0);
|
||||
|
||||
afChain.addBlock(&resamp, true);
|
||||
afChain.addBlock(&deemp, false);
|
||||
|
||||
// Initialize the sink
|
||||
srChangeHandler.ctx = this;
|
||||
srChangeHandler.handler = sampleRateChangeHandler;
|
||||
stream.init(afChain.out, &srChangeHandler, audioSampleRate);
|
||||
sigpath::sinkManager.registerStream(name, &stream);
|
||||
stream = sigpath::streamManager.createStream(name, afChain.out, 48000);
|
||||
|
||||
// Select the demodulator
|
||||
selectDemodByID((DemodID)selectedDemodID);
|
||||
|
||||
// Start IF chain
|
||||
ifChain.start();
|
||||
|
||||
// Start AF chain
|
||||
afChain.start();
|
||||
|
||||
// Start stream, the rest was started when selecting the demodulator
|
||||
stream.start();
|
||||
|
||||
// Register the menu
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
|
||||
@ -115,11 +101,10 @@ public:
|
||||
~RadioModule() {
|
||||
core::modComManager.unregisterInterface(name);
|
||||
gui::menu.removeEntry(name);
|
||||
stream.stop();
|
||||
if (enabled) {
|
||||
disable();
|
||||
}
|
||||
sigpath::sinkManager.unregisterStream(name);
|
||||
sigpath::streamManager.destroyStream(stream);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
@ -131,9 +116,7 @@ public:
|
||||
vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
|
||||
}
|
||||
ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); });
|
||||
ifChain.start();
|
||||
selectDemodByID((DemodID)selectedDemodID);
|
||||
afChain.start();
|
||||
}
|
||||
|
||||
void disable() {
|
||||
@ -141,6 +124,7 @@ public:
|
||||
ifChain.stop();
|
||||
if (selectedDemod) { selectedDemod->stop(); }
|
||||
afChain.stop();
|
||||
stream->stopDSP();
|
||||
if (vfo) { sigpath::vfoManager.deleteVFO(vfo); }
|
||||
vfo = NULL;
|
||||
}
|
||||
@ -313,7 +297,7 @@ private:
|
||||
bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth());
|
||||
|
||||
// Initialize
|
||||
demod->init(name, &config, ifChain.out, bw, stream.getSampleRate());
|
||||
demod->init(name, &config, ifChain.out, bw);
|
||||
|
||||
return demod;
|
||||
}
|
||||
@ -337,22 +321,34 @@ private:
|
||||
}
|
||||
|
||||
void selectDemod(demod::Demodulator* demod) {
|
||||
// Stopcurrently selected demodulator and select new
|
||||
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
// Stop the IF chain
|
||||
ifChain.stop();
|
||||
|
||||
// Stop the current demodulator
|
||||
if (selectedDemod) {
|
||||
selectedDemod->stop();
|
||||
}
|
||||
|
||||
// Stop AF chain
|
||||
afChain.stop();
|
||||
|
||||
// Stop audio stream's DSP
|
||||
stream->stopDSP();
|
||||
|
||||
// Destroy the old demodulator
|
||||
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
if (selectedDemod) {
|
||||
delete selectedDemod;
|
||||
}
|
||||
selectedDemod = demod;
|
||||
|
||||
// Give the demodulator the most recent audio SR
|
||||
selectedDemod->AFSampRateChanged(audioSampleRate);
|
||||
// Select the new demodulator
|
||||
selectedDemod = demod;
|
||||
|
||||
// Set the demodulator's input
|
||||
selectedDemod->setInput(ifChain.out);
|
||||
|
||||
// Set AF chain's input
|
||||
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
|
||||
// Load config
|
||||
bandwidth = selectedDemod->getDefaultBandwidth();
|
||||
@ -440,21 +436,30 @@ private:
|
||||
// Configure AF chain
|
||||
if (postProcEnabled) {
|
||||
// Configure resampler
|
||||
afChain.stop();
|
||||
resamp.setInSamplerate(selectedDemod->getAFSampleRate());
|
||||
setAudioSampleRate(audioSampleRate);
|
||||
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
deemp.setSamplerate(selectedDemod->getAFSampleRate());
|
||||
|
||||
// Configure deemphasis
|
||||
setDeemphasisMode(deempModes[deempId]);
|
||||
}
|
||||
else {
|
||||
// Disable everything if post processing is disabled
|
||||
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
}
|
||||
|
||||
// Update audo samplerate
|
||||
stream->setSamplerate(selectedDemod->getAFSampleRate());
|
||||
|
||||
// Start the IF chain
|
||||
ifChain.start();
|
||||
|
||||
// Start new demodulator
|
||||
selectedDemod->start();
|
||||
|
||||
// Start the AF chain
|
||||
afChain.start();
|
||||
|
||||
// Start the audio stream
|
||||
stream->startDSP();
|
||||
}
|
||||
|
||||
|
||||
@ -470,37 +475,12 @@ private:
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setAudioSampleRate(double sr) {
|
||||
audioSampleRate = sr;
|
||||
if (!selectedDemod) { return; }
|
||||
selectedDemod->AFSampRateChanged(audioSampleRate);
|
||||
if (!postProcEnabled && vfo) {
|
||||
// If postproc is disabled, IF SR = AF SR
|
||||
minBandwidth = selectedDemod->getMinBandwidth();
|
||||
maxBandwidth = selectedDemod->getMaxBandwidth();
|
||||
bandwidth = selectedDemod->getIFSampleRate();
|
||||
vfo->setBandwidthLimits(minBandwidth, maxBandwidth, selectedDemod->getBandwidthLocked());
|
||||
vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth);
|
||||
return;
|
||||
}
|
||||
|
||||
afChain.stop();
|
||||
|
||||
// Configure resampler
|
||||
resamp.setOutSamplerate(audioSampleRate);
|
||||
|
||||
// Configure deemphasis sample rate
|
||||
deemp.setSamplerate(audioSampleRate);
|
||||
|
||||
afChain.start();
|
||||
}
|
||||
|
||||
void setDeemphasisMode(DeemphasisMode mode) {
|
||||
deempId = deempModes.valueId(mode);
|
||||
if (!postProcEnabled || !selectedDemod) { return; }
|
||||
bool deempEnabled = (mode != DEEMP_MODE_NONE);
|
||||
if (deempEnabled) { deemp.setTau(deempTaus[mode]); }
|
||||
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
@ -584,11 +564,6 @@ private:
|
||||
_this->setBandwidth(newBw);
|
||||
}
|
||||
|
||||
static void sampleRateChangeHandler(float sampleRate, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
_this->setAudioSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
if (!_this->selectedDemod) { return; }
|
||||
@ -643,7 +618,6 @@ private:
|
||||
|
||||
// Handlers
|
||||
EventHandler<double> onUserChangedBandwidthHandler;
|
||||
EventHandler<float> srChangeHandler;
|
||||
EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged;
|
||||
EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged;
|
||||
|
||||
@ -658,10 +632,9 @@ private:
|
||||
// Audio chain
|
||||
dsp::stream<dsp::stereo_t> dummyAudioStream;
|
||||
dsp::chain<dsp::stereo_t> afChain;
|
||||
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
|
||||
dsp::filter::Deemphasis<dsp::stereo_t> deemp;
|
||||
|
||||
SinkManager::Stream stream;
|
||||
std::shared_ptr<MasterStream> stream;
|
||||
|
||||
demod::Demodulator* selectedDemod = NULL;
|
||||
|
||||
|
BIN
root/res/icons/align_center.png
Normal file
BIN
root/res/icons/align_center.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
@ -2,10 +2,10 @@
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <signal_path/sink.h>
|
||||
#include <dsp/buffer/packer.h>
|
||||
#include <dsp/convert/stereo_to_mono.h>
|
||||
#include <utils/flog.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <RtAudio.h>
|
||||
#include <config.h>
|
||||
#include <core.h>
|
||||
@ -16,36 +16,51 @@ SDRPP_MOD_INFO{
|
||||
/* Name: */ "audio_sink",
|
||||
/* Description: */ "Audio sink module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Version: */ 0, 2, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
class AudioSink : SinkManager::Sink {
|
||||
bool operator==(const RtAudio::DeviceInfo& a, const RtAudio::DeviceInfo& b) {
|
||||
return a.name == b.name;
|
||||
}
|
||||
|
||||
class AudioSink : public Sink {
|
||||
public:
|
||||
AudioSink(SinkManager::Stream* stream, std::string streamName) {
|
||||
_stream = stream;
|
||||
_streamName = streamName;
|
||||
s2m.init(_stream->sinkOut);
|
||||
AudioSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
|
||||
Sink(entry, stream, name, id, stringId)
|
||||
{
|
||||
s2m.init(stream);
|
||||
monoPacker.init(&s2m.out, 512);
|
||||
stereoPacker.init(_stream->sinkOut, 512);
|
||||
stereoPacker.init(stream, 512);
|
||||
|
||||
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||
audio.setErrorCallback(&errorCallback);
|
||||
#endif
|
||||
|
||||
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||
audio.setErrorCallback(&errorCallback);
|
||||
#endif
|
||||
|
||||
// Load config (TODO)
|
||||
bool created = false;
|
||||
std::string device = "";
|
||||
config.acquire();
|
||||
if (!config.conf.contains(_streamName)) {
|
||||
created = true;
|
||||
config.conf[_streamName]["device"] = "";
|
||||
config.conf[_streamName]["devices"] = json({});
|
||||
}
|
||||
device = config.conf[_streamName]["device"];
|
||||
config.release(created);
|
||||
// config.acquire();
|
||||
// if (config.conf.contains(streamName)) {
|
||||
// if (!config.conf[streamName].is_array()) {
|
||||
// json tmp = config.conf[streamName];
|
||||
// config.conf[streamName] = json::array();
|
||||
// config.conf[streamName][0] = tmp;
|
||||
// modified = true;
|
||||
// }
|
||||
// if (config.conf[streamName].contains((int)id)) {
|
||||
// device = config.conf[streamName][(int)id]["device"];
|
||||
// }
|
||||
// }
|
||||
// config.release(modified);
|
||||
|
||||
// List devices
|
||||
RtAudio::DeviceInfo info;
|
||||
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||
for (int i : audio.getDeviceIds()) {
|
||||
@ -60,15 +75,13 @@ public:
|
||||
#endif
|
||||
if (info.outputChannels == 0) { continue; }
|
||||
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
|
||||
devList.push_back(info);
|
||||
deviceIds.push_back(i);
|
||||
txtDevList += info.name;
|
||||
txtDevList += '\0';
|
||||
devList.define(i, info.name, info);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
selectByName(device);
|
||||
}
|
||||
|
||||
@ -102,67 +115,92 @@ public:
|
||||
}
|
||||
|
||||
void selectById(int id) {
|
||||
// Update ID
|
||||
devId = id;
|
||||
bool created = false;
|
||||
config.acquire();
|
||||
if (!config.conf[_streamName]["devices"].contains(devList[id].name)) {
|
||||
created = true;
|
||||
config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate;
|
||||
}
|
||||
sampleRate = config.conf[_streamName]["devices"][devList[id].name];
|
||||
config.release(created);
|
||||
selectedDevName = devList[id].name;
|
||||
|
||||
sampleRates = devList[id].sampleRates;
|
||||
sampleRatesTxt = "";
|
||||
// List samplerates and select default SR
|
||||
char buf[256];
|
||||
bool found = false;
|
||||
unsigned int defaultId = 0;
|
||||
sampleRates.clear();
|
||||
const auto& srList = devList[id].sampleRates;
|
||||
unsigned int defaultSr = devList[id].preferredSampleRate;
|
||||
for (int i = 0; i < sampleRates.size(); i++) {
|
||||
if (sampleRates[i] == sampleRate) {
|
||||
found = true;
|
||||
srId = i;
|
||||
for (auto& sr : srList) {
|
||||
if (sr == defaultSr) {
|
||||
srId = sampleRates.size();
|
||||
sampleRate = sr;
|
||||
}
|
||||
if (sampleRates[i] == defaultSr) {
|
||||
defaultId = i;
|
||||
}
|
||||
sprintf(buf, "%d", sampleRates[i]);
|
||||
sampleRatesTxt += buf;
|
||||
sampleRatesTxt += '\0';
|
||||
}
|
||||
if (!found) {
|
||||
sampleRate = defaultSr;
|
||||
srId = defaultId;
|
||||
sprintf(buf, "%d", sr);
|
||||
sampleRates.define(sr, buf, sr);
|
||||
}
|
||||
|
||||
_stream->setSampleRate(sampleRate);
|
||||
// // Load config
|
||||
// config.acquire();
|
||||
// if (config.conf[streamName][(int)id].contains(selectedDevName)) {
|
||||
// unsigned int wantedSr = config.conf[streamName][id][selectedDevName];
|
||||
// if (sampleRates.keyExists(wantedSr)) {
|
||||
// srId = sampleRates.keyId(wantedSr);
|
||||
// sampleRate = sampleRates[srId];
|
||||
// }
|
||||
// }
|
||||
// config.release();
|
||||
|
||||
// Lock the sink
|
||||
auto lck = entry->getLock();
|
||||
|
||||
// Stop the sink DSP
|
||||
// TODO: Only if the sink DSP is running, otherwise you risk starting it when it shouldn't
|
||||
entry->stopDSP();
|
||||
|
||||
// Stop the sink
|
||||
if (running) { doStop(); }
|
||||
|
||||
// Update stream samplerate
|
||||
entry->setSamplerate(sampleRate);
|
||||
|
||||
// Start the DSP
|
||||
entry->startDSP();
|
||||
|
||||
// Start the sink
|
||||
if (running) { doStart(); }
|
||||
}
|
||||
|
||||
void menuHandler() {
|
||||
void showMenu() {
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(("##_audio_sink_dev_" + _streamName).c_str(), &devId, txtDevList.c_str())) {
|
||||
if (ImGui::Combo(("##_audio_sink_dev_" + stringId).c_str(), &devId, devList.txt)) {
|
||||
selectById(devId);
|
||||
config.acquire();
|
||||
config.conf[_streamName]["device"] = devList[devId].name;
|
||||
config.release(true);
|
||||
// config.acquire();
|
||||
// config.conf[streamName]["device"] = devList[devId].name;
|
||||
// config.release(true);
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(("##_audio_sink_sr_" + _streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
|
||||
if (ImGui::Combo(("##_audio_sink_sr_" + stringId).c_str(), &srId, sampleRates.txt)) {
|
||||
sampleRate = sampleRates[srId];
|
||||
_stream->setSampleRate(sampleRate);
|
||||
if (running) {
|
||||
doStop();
|
||||
doStart();
|
||||
}
|
||||
config.acquire();
|
||||
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
|
||||
config.release(true);
|
||||
|
||||
// Lock the sink
|
||||
auto lck = entry->getLock();
|
||||
|
||||
// Stop the sink DSP
|
||||
// TODO: Only if the sink DSP is running, otherwise you risk starting it when it shouldn't
|
||||
entry->stopDSP();
|
||||
|
||||
// Stop the sink
|
||||
if (running) { doStop(); }
|
||||
|
||||
// Update stream samplerate
|
||||
entry->setSamplerate(sampleRate);
|
||||
|
||||
// Start the DSP
|
||||
entry->startDSP();
|
||||
|
||||
// Start the sink
|
||||
if (running) { doStart(); }
|
||||
|
||||
// config.acquire();
|
||||
// config.conf[streamName]["devices"][devList[devId].name] = sampleRate;
|
||||
// config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,12 +223,12 @@ public:
|
||||
private:
|
||||
bool doStart() {
|
||||
RtAudio::StreamParameters parameters;
|
||||
parameters.deviceId = deviceIds[devId];
|
||||
parameters.deviceId = devList.key(devId);
|
||||
parameters.nChannels = 2;
|
||||
unsigned int bufferFrames = sampleRate / 60;
|
||||
RtAudio::StreamOptions opts;
|
||||
opts.flags = RTAUDIO_MINIMIZE_LATENCY;
|
||||
opts.streamName = _streamName;
|
||||
opts.streamName = streamName;
|
||||
|
||||
try {
|
||||
audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
|
||||
@ -229,44 +267,34 @@ private:
|
||||
return 0;
|
||||
}
|
||||
|
||||
SinkManager::Stream* _stream;
|
||||
dsp::convert::StereoToMono s2m;
|
||||
dsp::buffer::Packer<float> monoPacker;
|
||||
dsp::buffer::Packer<dsp::stereo_t> stereoPacker;
|
||||
|
||||
std::string _streamName;
|
||||
|
||||
int srId = 0;
|
||||
int devCount;
|
||||
int devId = 0;
|
||||
bool running = false;
|
||||
std::string selectedDevName;
|
||||
|
||||
unsigned int defaultDevId = 0;
|
||||
|
||||
std::vector<RtAudio::DeviceInfo> devList;
|
||||
std::vector<unsigned int> deviceIds;
|
||||
std::string txtDevList;
|
||||
OptionList<unsigned int, RtAudio::DeviceInfo> devList;
|
||||
OptionList<unsigned int, unsigned int> sampleRates;
|
||||
|
||||
std::vector<unsigned int> sampleRates;
|
||||
std::string sampleRatesTxt;
|
||||
unsigned int sampleRate = 48000;
|
||||
|
||||
RtAudio audio;
|
||||
};
|
||||
|
||||
class AudioSinkModule : public ModuleManager::Instance {
|
||||
class AudioSinkModule : public ModuleManager::Instance, public SinkProvider {
|
||||
public:
|
||||
AudioSinkModule(std::string name) {
|
||||
this->name = name;
|
||||
provider.create = create_sink;
|
||||
provider.ctx = this;
|
||||
|
||||
sigpath::sinkManager.registerSinkProvider("Audio", provider);
|
||||
sigpath::streamManager.registerSinkProvider("Audio", this);
|
||||
}
|
||||
|
||||
~AudioSinkModule() {
|
||||
// Unregister sink, this will automatically stop and delete all instances of the audio sink
|
||||
sigpath::sinkManager.unregisterSinkProvider("Audio");
|
||||
sigpath::streamManager.unregisterSinkProvider(this);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
@ -283,14 +311,13 @@ public:
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
|
||||
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
|
||||
std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) {
|
||||
return std::make_unique<AudioSink>(entry, stream, name, id, stringId);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
SinkManager::SinkProvider provider;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
|
@ -3,13 +3,14 @@
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <signal_path/sink.h>
|
||||
#include <signal_path/stream.h>
|
||||
#include <dsp/buffer/packer.h>
|
||||
#include <dsp/convert/stereo_to_mono.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <utils/flog.h>
|
||||
#include <config.h>
|
||||
#include <gui/style.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <core.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
@ -18,84 +19,97 @@ SDRPP_MOD_INFO{
|
||||
/* Name: */ "network_sink",
|
||||
/* Description: */ "Network sink module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Version: */ 0, 2, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
enum {
|
||||
enum SinkMode {
|
||||
SINK_MODE_TCP,
|
||||
SINK_MODE_UDP
|
||||
};
|
||||
|
||||
const char* sinkModesTxt = "TCP\0UDP\0";
|
||||
|
||||
class NetworkSink : SinkManager::Sink {
|
||||
class NetworkSink : public Sink {
|
||||
public:
|
||||
NetworkSink(SinkManager::Stream* stream, std::string streamName) {
|
||||
_stream = stream;
|
||||
_streamName = streamName;
|
||||
NetworkSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
|
||||
Sink(entry, stream, name, id, stringId)
|
||||
{
|
||||
// Define modes
|
||||
modes.define("TCP", SINK_MODE_TCP);
|
||||
modes.define("UDP", SINK_MODE_UDP);
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
if (!config.conf.contains(_streamName)) {
|
||||
config.conf[_streamName]["hostname"] = "localhost";
|
||||
config.conf[_streamName]["port"] = 7355;
|
||||
config.conf[_streamName]["protocol"] = SINK_MODE_UDP; // UDP
|
||||
config.conf[_streamName]["sampleRate"] = 48000.0;
|
||||
config.conf[_streamName]["stereo"] = false;
|
||||
config.conf[_streamName]["listening"] = false;
|
||||
// Create a list of sample rates
|
||||
std::vector<int> srList;
|
||||
for (int sr = 12000; sr <= 200000; sr += 12000) {
|
||||
srList.push_back(sr);
|
||||
}
|
||||
for (int sr = 11025; sr <= 192000; sr += 11025) {
|
||||
srList.push_back(sr);
|
||||
}
|
||||
std::string host = config.conf[_streamName]["hostname"];
|
||||
strcpy(hostname, host.c_str());
|
||||
port = config.conf[_streamName]["port"];
|
||||
modeId = config.conf[_streamName]["protocol"];
|
||||
sampleRate = config.conf[_streamName]["sampleRate"];
|
||||
stereo = config.conf[_streamName]["stereo"];
|
||||
bool startNow = config.conf[_streamName]["listening"];
|
||||
config.release(true);
|
||||
|
||||
// Sort sample rate list
|
||||
std::sort(srList.begin(), srList.end(), [](double a, double b) { return (a < b); });
|
||||
|
||||
// Define samplerate options
|
||||
for (int sr : srList) {
|
||||
char buf[16];
|
||||
sprintf(buf, "%d", sr);
|
||||
samplerates.define(sr, buf, sr);
|
||||
}
|
||||
|
||||
// Allocate buffer
|
||||
netBuf = new int16_t[STREAM_BUFFER_SIZE];
|
||||
|
||||
packer.init(_stream->sinkOut, 512);
|
||||
// Init DSP
|
||||
packer.init(stream, 512);
|
||||
s2m.init(&packer.out);
|
||||
monoSink.init(&s2m.out, monoHandler, this);
|
||||
stereoSink.init(&packer.out, stereoHandler, this);
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
bool startNow = false;
|
||||
if (config.conf[stringId].contains("hostname")) {
|
||||
std::string host = config.conf[stringId]["hostname"];
|
||||
strcpy(hostname, host.c_str());
|
||||
}
|
||||
if (config.conf[stringId].contains("port")) {
|
||||
port = config.conf[stringId]["port"];
|
||||
}
|
||||
if (config.conf[stringId].contains("mode")) {
|
||||
std::string modeStr = config.conf[stringId]["mode"];
|
||||
if (modes.keyExists(modeStr)) {
|
||||
mode = modes.value(modes.keyId(modeStr));
|
||||
}
|
||||
else {
|
||||
mode = SINK_MODE_TCP;
|
||||
}
|
||||
}
|
||||
if (config.conf[stringId].contains("samplerate")) {
|
||||
int nSr = config.conf[stringId]["samplerate"];
|
||||
if (samplerates.keyExists(nSr)) {
|
||||
sampleRate = samplerates.value(samplerates.keyId(nSr));
|
||||
}
|
||||
else {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
}
|
||||
if (config.conf[stringId].contains("stereo")) {
|
||||
stereo = config.conf[stringId]["stereo"];
|
||||
}
|
||||
if (config.conf[stringId].contains("running")) {
|
||||
startNow = config.conf[stringId]["running"];
|
||||
}
|
||||
config.release();
|
||||
|
||||
// Create a list of sample rates
|
||||
for (int sr = 12000; sr < 200000; sr += 12000) {
|
||||
sampleRates.push_back(sr);
|
||||
}
|
||||
for (int sr = 11025; sr < 192000; sr += 11025) {
|
||||
sampleRates.push_back(sr);
|
||||
}
|
||||
// Set mode ID
|
||||
modeId = modes.valueId(mode);
|
||||
|
||||
// Sort sample rate list
|
||||
std::sort(sampleRates.begin(), sampleRates.end(), [](double a, double b) { return (a < b); });
|
||||
|
||||
// Generate text list for UI
|
||||
char buffer[128];
|
||||
int id = 0;
|
||||
int _48kId;
|
||||
bool found = false;
|
||||
for (auto sr : sampleRates) {
|
||||
sprintf(buffer, "%d", (int)sr);
|
||||
sampleRatesTxt += buffer;
|
||||
sampleRatesTxt += '\0';
|
||||
if (sr == sampleRate) {
|
||||
srId = id;
|
||||
found = true;
|
||||
}
|
||||
if (sr == 48000.0) { _48kId = id; }
|
||||
id++;
|
||||
}
|
||||
if (!found) {
|
||||
srId = _48kId;
|
||||
sampleRate = 48000.0;
|
||||
}
|
||||
_stream->setSampleRate(sampleRate);
|
||||
// Set samplerate ID
|
||||
srId = samplerates.valueId(sampleRate);
|
||||
|
||||
// Start if needed
|
||||
if (startNow) { startServer(); }
|
||||
@ -122,30 +136,30 @@ public:
|
||||
running = false;
|
||||
}
|
||||
|
||||
void menuHandler() {
|
||||
void showMenu() {
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
bool listening = (listener && listener->isListening()) || (conn && conn->isOpen());
|
||||
|
||||
if (listening) { style::beginDisabled(); }
|
||||
if (ImGui::InputText(CONCAT("##_network_sink_host_", _streamName), hostname, 1023)) {
|
||||
if (ImGui::InputText(CONCAT("##_network_sink_host_", stringId), hostname, 1023)) {
|
||||
config.acquire();
|
||||
config.conf[_streamName]["hostname"] = hostname;
|
||||
config.conf[stringId]["hostname"] = hostname;
|
||||
config.release(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::InputInt(CONCAT("##_network_sink_port_", _streamName), &port, 0, 0)) {
|
||||
if (ImGui::InputInt(CONCAT("##_network_sink_port_", stringId), &port, 0, 0)) {
|
||||
config.acquire();
|
||||
config.conf[_streamName]["port"] = port;
|
||||
config.conf[stringId]["port"] = port;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
ImGui::LeftLabel("Protocol");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo(CONCAT("##_network_sink_mode_", _streamName), &modeId, sinkModesTxt)) {
|
||||
if (ImGui::Combo(CONCAT("##_network_sink_mode_", stringId), &modeId, sinkModesTxt)) {
|
||||
config.acquire();
|
||||
config.conf[_streamName]["protocol"] = modeId;
|
||||
config.conf[stringId]["mode"] = modeId;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
@ -153,33 +167,33 @@ public:
|
||||
|
||||
ImGui::LeftLabel("Samplerate");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo(CONCAT("##_network_sink_sr_", _streamName), &srId, sampleRatesTxt.c_str())) {
|
||||
sampleRate = sampleRates[srId];
|
||||
_stream->setSampleRate(sampleRate);
|
||||
if (ImGui::Combo(CONCAT("##_network_sink_sr_", stringId), &srId, samplerates.txt)) {
|
||||
sampleRate = samplerates.value(srId);
|
||||
entry->setSamplerate(sampleRate);
|
||||
packer.setSampleCount(sampleRate / 60);
|
||||
config.acquire();
|
||||
config.conf[_streamName]["sampleRate"] = sampleRate;
|
||||
config.conf[stringId]["samplerate"] = sampleRate;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", _streamName), &stereo)) {
|
||||
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", stringId), &stereo)) {
|
||||
stop();
|
||||
start();
|
||||
config.acquire();
|
||||
config.conf[_streamName]["stereo"] = stereo;
|
||||
config.conf[stringId]["stereo"] = stereo;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) {
|
||||
if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) {
|
||||
stopServer();
|
||||
config.acquire();
|
||||
config.conf[_streamName]["listening"] = false;
|
||||
config.conf[stringId]["running"] = false;
|
||||
config.release(true);
|
||||
}
|
||||
else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) {
|
||||
else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) {
|
||||
startServer();
|
||||
config.acquire();
|
||||
config.conf[_streamName]["listening"] = true;
|
||||
config.conf[stringId]["running"] = true;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
@ -271,47 +285,40 @@ private:
|
||||
_this->listener->acceptAsync(clientHandler, _this);
|
||||
}
|
||||
|
||||
SinkManager::Stream* _stream;
|
||||
// DSP
|
||||
dsp::buffer::Packer<dsp::stereo_t> packer;
|
||||
dsp::convert::StereoToMono s2m;
|
||||
dsp::sink::Handler<float> monoSink;
|
||||
dsp::sink::Handler<dsp::stereo_t> stereoSink;
|
||||
|
||||
std::string _streamName;
|
||||
|
||||
int srId = 0;
|
||||
bool running = false;
|
||||
OptionList<std::string, SinkMode> modes;
|
||||
OptionList<int, double> samplerates;
|
||||
|
||||
char hostname[1024];
|
||||
int port = 4242;
|
||||
|
||||
int modeId = 1;
|
||||
|
||||
std::vector<unsigned int> sampleRates;
|
||||
std::string sampleRatesTxt;
|
||||
unsigned int sampleRate = 48000;
|
||||
int port = 7355;
|
||||
SinkMode mode = SINK_MODE_TCP;
|
||||
int modeId;
|
||||
int sampleRate = 48000;
|
||||
int srId;
|
||||
bool stereo = false;
|
||||
bool running = false;
|
||||
|
||||
int16_t* netBuf;
|
||||
|
||||
net::Listener listener;
|
||||
net::Conn conn;
|
||||
std::mutex connMtx;
|
||||
};
|
||||
|
||||
class NetworkSinkModule : public ModuleManager::Instance {
|
||||
class NetworkSinkModule : public ModuleManager::Instance, SinkProvider {
|
||||
public:
|
||||
NetworkSinkModule(std::string name) {
|
||||
this->name = name;
|
||||
provider.create = create_sink;
|
||||
provider.ctx = this;
|
||||
|
||||
sigpath::sinkManager.registerSinkProvider("Network", provider);
|
||||
// Register self as provider
|
||||
sigpath::streamManager.registerSinkProvider("Network", this);
|
||||
}
|
||||
|
||||
~NetworkSinkModule() {
|
||||
// Unregister sink, this will automatically stop and delete all instances of the audio sink
|
||||
sigpath::sinkManager.unregisterSinkProvider("Network");
|
||||
// Unregister self
|
||||
sigpath::streamManager.unregisterSinkProvider(this);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
@ -328,14 +335,13 @@ public:
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
|
||||
return (SinkManager::Sink*)(new NetworkSink(stream, streamName));
|
||||
std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) {
|
||||
return std::make_unique<NetworkSink>(entry, stream, name, id, stringId);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
SinkManager::SinkProvider provider;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
|
Reference in New Issue
Block a user