27 Commits

Author SHA1 Message Date
40f1ee5651 Merge pull request #1385 from AlexandreRouma/master
Keep new_sinks branch up to date
2024-04-18 03:14:40 +02:00
c4b0cb37a6 Merge pull request #1315 from AlexandreRouma/master
keep new_sinks branch up to date
2024-02-02 14:51:34 +01:00
cd7dabda51 convert network sink to new stream system 2024-01-30 22:04:36 +01:00
139f63ad25 limit stream configuration to creator 2024-01-30 20:28:32 +01:00
75800e0ca2 fix merge issue 2024-01-30 18:09:59 +01:00
84da183559 Merge pull request #1310 from AlexandreRouma/master
New sink/stream system
2024-01-30 18:08:41 +01:00
0144d8e8ce resolve conflict in radio 2024-01-30 18:08:17 +01:00
7ef88f23a8 resolve conflict in audio_sink 2 2024-01-30 18:06:31 +01:00
c934e41a61 resolve conflict in audio_sink 2024-01-30 18:05:28 +01:00
9dd6c8546d resolve conflict in spectran_http_client.cpp 2024-01-30 18:02:16 +01:00
191f652fc3 fix incorrect formatting #1288 2024-01-22 17:05:50 +01:00
e208511bde more work 2023-12-16 19:57:48 +01:00
348bf75281 Merge pull request #1252 from AlexandreRouma/master
Merging back modern stuff into the new sinks branch
2023-12-14 20:54:04 +01:00
cbf1d6703e more work 2023-08-20 21:06:36 +02:00
03c30f202e Merge pull request #1132 from AlexandreRouma/master
Merge back
2023-07-11 01:15:03 +02:00
25bc9f60ed more work 2023-07-10 03:48:59 +02:00
2e80882ab5 Merge pull request #1130 from AlexandreRouma/master
Merging back master changes
2023-07-09 18:35:44 +02:00
4c584847de more work 2023-07-09 18:34:52 +02:00
2a741932e0 more or less finished the new stream system 2023-07-09 04:09:18 +02:00
ff655caf31 saving work 2023-05-17 03:55:47 +02:00
0277232bdb redesign 2023-04-21 22:44:45 +02:00
2e3e2a7dca Merge pull request #1049 from AlexandreRouma/master
Merging back changes
2023-04-20 09:36:52 +02:00
271a77e4ce More work on the stream system 2023-04-20 09:35:06 +02:00
48e9708d74 added disclaimer 2023-04-10 22:52:50 +02:00
961cd3f133 beginning of documentation 2023-04-03 20:49:27 +02:00
2676190d3a Merge pull request #1035 from AlexandreRouma/master
merge back changes to master
2023-04-03 20:40:25 +02:00
8851735cb8 initial structure 2023-04-03 20:28:17 +02:00
23 changed files with 1324 additions and 290 deletions

View File

@ -16,6 +16,7 @@ namespace icons {
ImTextureID UNMUTED; ImTextureID UNMUTED;
ImTextureID NORMAL_TUNING; ImTextureID NORMAL_TUNING;
ImTextureID CENTER_TUNING; ImTextureID CENTER_TUNING;
ImTextureID ALIGN_CENTER;
GLuint loadTexture(std::string path) { GLuint loadTexture(std::string path) {
int w, h, n; int w, h, n;
@ -45,6 +46,7 @@ namespace icons {
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png"); UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png"); NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_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; return true;
} }

View File

@ -13,6 +13,7 @@ namespace icons {
extern ImTextureID UNMUTED; extern ImTextureID UNMUTED;
extern ImTextureID NORMAL_TUNING; extern ImTextureID NORMAL_TUNING;
extern ImTextureID CENTER_TUNING; extern ImTextureID CENTER_TUNING;
extern ImTextureID ALIGN_CENTER;
GLuint loadTexture(std::string path); GLuint loadTexture(std::string path);
bool load(std::string resDir); bool load(std::string resDir);

View File

@ -17,6 +17,7 @@
#include <gui/menus/display.h> #include <gui/menus/display.h>
#include <gui/menus/bandplan.h> #include <gui/menus/bandplan.h>
#include <gui/menus/sink.h> #include <gui/menus/sink.h>
#include <gui/menus/streams.h>
#include <gui/menus/vfo_color.h> #include <gui/menus/vfo_color.h>
#include <gui/menus/module_manager.h> #include <gui/menus/module_manager.h>
#include <gui/menus/theme.h> #include <gui/menus/theme.h>
@ -72,6 +73,7 @@ void MainWindow::init() {
gui::menu.registerEntry("Source", sourcemenu::draw, NULL); gui::menu.registerEntry("Source", sourcemenu::draw, NULL);
gui::menu.registerEntry("Sinks", sinkmenu::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("Band Plan", bandplanmenu::draw, NULL);
gui::menu.registerEntry("Display", displaymenu::draw, NULL); gui::menu.registerEntry("Display", displaymenu::draw, NULL);
gui::menu.registerEntry("Theme", thememenu::draw, NULL); gui::menu.registerEntry("Theme", thememenu::draw, NULL);
@ -165,6 +167,7 @@ void MainWindow::init() {
sourcemenu::init(); sourcemenu::init();
sinkmenu::init(); sinkmenu::init();
streamsmenu::init();
bandplanmenu::init(); bandplanmenu::init();
displaymenu::init(); displaymenu::init();
vfo_color_menu::init(); vfo_color_menu::init();

View 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();
}
}
};

View File

@ -0,0 +1,6 @@
#pragma once
namespace streamsmenu {
void init();
void draw(void* ctx);
};

View File

@ -5,4 +5,5 @@ namespace sigpath {
VFOManager vfoManager; VFOManager vfoManager;
SourceManager sourceManager; SourceManager sourceManager;
SinkManager sinkManager; SinkManager sinkManager;
StreamManager streamManager;
}; };

View File

@ -3,6 +3,7 @@
#include "vfo_manager.h" #include "vfo_manager.h"
#include "source.h" #include "source.h"
#include "sink.h" #include "sink.h"
#include "stream.h"
#include <module.h> #include <module.h>
namespace sigpath { namespace sigpath {
@ -10,4 +11,5 @@ namespace sigpath {
SDRPP_EXPORT VFOManager vfoManager; SDRPP_EXPORT VFOManager vfoManager;
SDRPP_EXPORT SourceManager sourceManager; SDRPP_EXPORT SourceManager sourceManager;
SDRPP_EXPORT SinkManager sinkManager; SDRPP_EXPORT SinkManager sinkManager;
SDRPP_EXPORT StreamManager streamManager;
}; };

View 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;
}

View 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;
};

View File

@ -63,6 +63,7 @@ namespace net::http {
std::string MessageHeader::getField(const std::string name) { std::string MessageHeader::getField(const std::string name) {
// TODO: Check if exists // TODO: Check if exists
// TODO: Maybe declare the set/get field functions to do type conversions automatically?
return fields[name]; return fields[name];
} }

View File

@ -24,13 +24,12 @@ namespace demod {
class Demodulator { class Demodulator {
public: public:
virtual ~Demodulator() {} 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 start() = 0;
virtual void stop() = 0; virtual void stop() = 0;
virtual void showMenu() = 0; virtual void showMenu() = 0;
virtual void setBandwidth(double bandwidth) = 0; virtual void setBandwidth(double bandwidth) = 0;
virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0; virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0;
virtual void AFSampRateChanged(double newSR) = 0;
virtual const char* getName() = 0; virtual const char* getName() = 0;
virtual double getIFSampleRate() = 0; virtual double getIFSampleRate() = 0;
virtual double getAFSampleRate() = 0; virtual double getAFSampleRate() = 0;

View File

@ -7,13 +7,13 @@ namespace demod {
public: public:
AM() {} AM() {}
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth);
} }
~AM() { stop(); } ~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; this->name = name;
_config = config; _config = config;
@ -68,8 +68,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO ============= // ============= INFO =============
const char* getName() { return "AM"; } const char* getName() { return "AM"; }

View File

@ -7,15 +7,15 @@ namespace demod {
public: public:
CW() {} CW() {}
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth);
} }
~CW() { ~CW() {
stop(); 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->name = name;
this->_config = config; this->_config = config;
this->afbwChangeHandler = afbwChangeHandler; this->afbwChangeHandler = afbwChangeHandler;
@ -74,8 +74,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO ============= // ============= INFO =============
const char* getName() { return "CW"; } const char* getName() { return "CW"; }

View File

@ -7,15 +7,15 @@ namespace demod {
public: public:
DSB() {} DSB() {}
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth);
} }
~DSB() { ~DSB() {
stop(); 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->name = name;
_config = config; _config = config;
@ -61,8 +61,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO ============= // ============= INFO =============
const char* getName() { return "DSB"; } const char* getName() { return "DSB"; }

View File

@ -7,15 +7,15 @@ namespace demod {
public: public:
LSB() {} LSB() {}
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth);
} }
~LSB() { ~LSB() {
stop(); 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->name = name;
_config = config; _config = config;
@ -61,8 +61,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO ============= // ============= INFO =============
const char* getName() { return "LSB"; } const char* getName() { return "LSB"; }

View File

@ -7,13 +7,13 @@ namespace demod {
public: public:
NFM() {} NFM() {}
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth);
} }
~NFM() { stop(); } ~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->name = name;
this->_config = config; this->_config = config;
@ -57,8 +57,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO ============= // ============= INFO =============
const char* getName() { return "FM"; } const char* getName() { return "FM"; }

View File

@ -7,17 +7,18 @@ namespace demod {
public: public:
RAW() {} RAW() {}
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth);
} }
~RAW() { ~RAW() {
stop(); 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->name = name;
audioSampleRate = audioSR; audioSampleRate = 48000;
// TODO: This needs to be selectable
// Define structure // Define structure
c2s.init(input); c2s.init(input);
@ -39,10 +40,6 @@ namespace demod {
c2s.setInput(input); c2s.setInput(input);
} }
void AFSampRateChanged(double newSR) {
audioSampleRate = newSR;
}
// ============= INFO ============= // ============= INFO =============
const char* getName() { return "RAW"; } const char* getName() { return "RAW"; }

View File

@ -8,15 +8,15 @@ namespace demod {
public: public:
USB() {} USB() {}
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth);
} }
~USB() { ~USB() {
stop(); 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->name = name;
_config = config; _config = config;
@ -62,8 +62,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO ============= // ============= INFO =============
const char* getName() { return "USB"; } const char* getName() { return "USB"; }

View File

@ -16,8 +16,8 @@ namespace demod {
public: public:
WFM() : diag(0.5, 4096) {} 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) { WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) : diag(0.5, 4096) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth);
} }
~WFM() { ~WFM() {
@ -25,7 +25,7 @@ namespace demod {
gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler); 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; this->name = name;
_config = config; _config = config;
@ -252,8 +252,6 @@ namespace demod {
demod.setInput(input); demod.setInput(input);
} }
void AFSampRateChanged(double newSR) {}
// ============= INFO ============= // ============= INFO =============
const char* getName() { return "WFM"; } const char* getName() { return "WFM"; }

View File

@ -81,30 +81,16 @@ public:
// Initialize audio DSP chain // Initialize audio DSP chain
afChain.init(&dummyAudioStream); afChain.init(&dummyAudioStream);
resamp.init(NULL, 250000.0, 48000.0);
deemp.init(NULL, 50e-6, 48000.0); deemp.init(NULL, 50e-6, 48000.0);
afChain.addBlock(&resamp, true);
afChain.addBlock(&deemp, false); afChain.addBlock(&deemp, false);
// Initialize the sink // Initialize the sink
srChangeHandler.ctx = this; stream = sigpath::streamManager.createStream(name, afChain.out, 48000);
srChangeHandler.handler = sampleRateChangeHandler;
stream.init(afChain.out, &srChangeHandler, audioSampleRate);
sigpath::sinkManager.registerStream(name, &stream);
// Select the demodulator // Select the demodulator
selectDemodByID((DemodID)selectedDemodID); 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 // Register the menu
gui::menu.registerEntry(name, menuHandler, this, this); gui::menu.registerEntry(name, menuHandler, this, this);
@ -115,11 +101,10 @@ public:
~RadioModule() { ~RadioModule() {
core::modComManager.unregisterInterface(name); core::modComManager.unregisterInterface(name);
gui::menu.removeEntry(name); gui::menu.removeEntry(name);
stream.stop();
if (enabled) { if (enabled) {
disable(); disable();
} }
sigpath::sinkManager.unregisterStream(name); sigpath::streamManager.destroyStream(stream);
} }
void postInit() {} void postInit() {}
@ -131,9 +116,7 @@ public:
vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler); vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
} }
ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); }); ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); });
ifChain.start();
selectDemodByID((DemodID)selectedDemodID); selectDemodByID((DemodID)selectedDemodID);
afChain.start();
} }
void disable() { void disable() {
@ -141,6 +124,7 @@ public:
ifChain.stop(); ifChain.stop();
if (selectedDemod) { selectedDemod->stop(); } if (selectedDemod) { selectedDemod->stop(); }
afChain.stop(); afChain.stop();
stream->stopDSP();
if (vfo) { sigpath::vfoManager.deleteVFO(vfo); } if (vfo) { sigpath::vfoManager.deleteVFO(vfo); }
vfo = NULL; vfo = NULL;
} }
@ -313,7 +297,7 @@ private:
bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth()); bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth());
// Initialize // Initialize
demod->init(name, &config, ifChain.out, bw, stream.getSampleRate()); demod->init(name, &config, ifChain.out, bw);
return demod; return demod;
} }
@ -337,22 +321,34 @@ private:
} }
void selectDemod(demod::Demodulator* demod) { void selectDemod(demod::Demodulator* demod) {
// Stopcurrently selected demodulator and select new // Stop the IF chain
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); ifChain.stop();
// Stop the current demodulator
if (selectedDemod) { if (selectedDemod) {
selectedDemod->stop(); 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; delete selectedDemod;
} }
selectedDemod = demod;
// Give the demodulator the most recent audio SR // Select the new demodulator
selectedDemod->AFSampRateChanged(audioSampleRate); selectedDemod = demod;
// Set the demodulator's input // Set the demodulator's input
selectedDemod->setInput(ifChain.out); selectedDemod->setInput(ifChain.out);
// Set AF chain's input // 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 // Load config
bandwidth = selectedDemod->getDefaultBandwidth(); bandwidth = selectedDemod->getDefaultBandwidth();
@ -440,21 +436,30 @@ private:
// Configure AF chain // Configure AF chain
if (postProcEnabled) { if (postProcEnabled) {
// Configure resampler // Configure resampler
afChain.stop(); deemp.setSamplerate(selectedDemod->getAFSampleRate());
resamp.setInSamplerate(selectedDemod->getAFSampleRate());
setAudioSampleRate(audioSampleRate);
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Configure deemphasis // Configure deemphasis
setDeemphasisMode(deempModes[deempId]); setDeemphasisMode(deempModes[deempId]);
} }
else { else {
// Disable everything if post processing is disabled // 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 // Start new demodulator
selectedDemod->start(); selectedDemod->start();
// Start the AF chain
afChain.start();
// Start the audio stream
stream->startDSP();
} }
@ -470,37 +475,12 @@ private:
config.release(true); 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) { void setDeemphasisMode(DeemphasisMode mode) {
deempId = deempModes.valueId(mode); deempId = deempModes.valueId(mode);
if (!postProcEnabled || !selectedDemod) { return; } if (!postProcEnabled || !selectedDemod) { return; }
bool deempEnabled = (mode != DEEMP_MODE_NONE); bool deempEnabled = (mode != DEEMP_MODE_NONE);
if (deempEnabled) { deemp.setTau(deempTaus[mode]); } 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 // Save config
config.acquire(); config.acquire();
@ -584,11 +564,6 @@ private:
_this->setBandwidth(newBw); _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) { static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) {
RadioModule* _this = (RadioModule*)ctx; RadioModule* _this = (RadioModule*)ctx;
if (!_this->selectedDemod) { return; } if (!_this->selectedDemod) { return; }
@ -643,7 +618,6 @@ private:
// Handlers // Handlers
EventHandler<double> onUserChangedBandwidthHandler; EventHandler<double> onUserChangedBandwidthHandler;
EventHandler<float> srChangeHandler;
EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged; EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged;
EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged; EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged;
@ -658,10 +632,9 @@ private:
// Audio chain // Audio chain
dsp::stream<dsp::stereo_t> dummyAudioStream; dsp::stream<dsp::stereo_t> dummyAudioStream;
dsp::chain<dsp::stereo_t> afChain; dsp::chain<dsp::stereo_t> afChain;
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
dsp::filter::Deemphasis<dsp::stereo_t> deemp; dsp::filter::Deemphasis<dsp::stereo_t> deemp;
SinkManager::Stream stream; std::shared_ptr<MasterStream> stream;
demod::Demodulator* selectedDemod = NULL; demod::Demodulator* selectedDemod = NULL;

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -2,10 +2,10 @@
#include <module.h> #include <module.h>
#include <gui/gui.h> #include <gui/gui.h>
#include <signal_path/signal_path.h> #include <signal_path/signal_path.h>
#include <signal_path/sink.h>
#include <dsp/buffer/packer.h> #include <dsp/buffer/packer.h>
#include <dsp/convert/stereo_to_mono.h> #include <dsp/convert/stereo_to_mono.h>
#include <utils/flog.h> #include <utils/flog.h>
#include <utils/optionlist.h>
#include <RtAudio.h> #include <RtAudio.h>
#include <config.h> #include <config.h>
#include <core.h> #include <core.h>
@ -16,36 +16,51 @@ SDRPP_MOD_INFO{
/* Name: */ "audio_sink", /* Name: */ "audio_sink",
/* Description: */ "Audio sink module for SDR++", /* Description: */ "Audio sink module for SDR++",
/* Author: */ "Ryzerth", /* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0, /* Version: */ 0, 2, 0,
/* Max instances */ 1 /* Max instances */ 1
}; };
ConfigManager config; 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: public:
AudioSink(SinkManager::Stream* stream, std::string streamName) { AudioSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
_stream = stream; Sink(entry, stream, name, id, stringId)
_streamName = streamName; {
s2m.init(_stream->sinkOut); s2m.init(stream);
monoPacker.init(&s2m.out, 512); monoPacker.init(&s2m.out, 512);
stereoPacker.init(_stream->sinkOut, 512); stereoPacker.init(stream, 512);
#if RTAUDIO_VERSION_MAJOR >= 6 #if RTAUDIO_VERSION_MAJOR >= 6
audio.setErrorCallback(&errorCallback); audio.setErrorCallback(&errorCallback);
#endif #endif
#if RTAUDIO_VERSION_MAJOR >= 6
audio.setErrorCallback(&errorCallback);
#endif
// Load config (TODO)
bool created = false; bool created = false;
std::string device = ""; std::string device = "";
config.acquire(); // config.acquire();
if (!config.conf.contains(_streamName)) { // if (config.conf.contains(streamName)) {
created = true; // if (!config.conf[streamName].is_array()) {
config.conf[_streamName]["device"] = ""; // json tmp = config.conf[streamName];
config.conf[_streamName]["devices"] = json({}); // config.conf[streamName] = json::array();
} // config.conf[streamName][0] = tmp;
device = config.conf[_streamName]["device"]; // modified = true;
config.release(created); // }
// if (config.conf[streamName].contains((int)id)) {
// device = config.conf[streamName][(int)id]["device"];
// }
// }
// config.release(modified);
// List devices
RtAudio::DeviceInfo info; RtAudio::DeviceInfo info;
#if RTAUDIO_VERSION_MAJOR >= 6 #if RTAUDIO_VERSION_MAJOR >= 6
for (int i : audio.getDeviceIds()) { for (int i : audio.getDeviceIds()) {
@ -60,15 +75,13 @@ public:
#endif #endif
if (info.outputChannels == 0) { continue; } if (info.outputChannels == 0) { continue; }
if (info.isDefaultOutput) { defaultDevId = devList.size(); } if (info.isDefaultOutput) { defaultDevId = devList.size(); }
devList.push_back(info); devList.define(i, info.name, info);
deviceIds.push_back(i);
txtDevList += info.name;
txtDevList += '\0';
} }
catch (const std::exception& e) { catch (const std::exception& e) {
flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what()); flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what());
} }
} }
selectByName(device); selectByName(device);
} }
@ -102,67 +115,92 @@ public:
} }
void selectById(int id) { void selectById(int id) {
// Update ID
devId = id; devId = id;
bool created = false; selectedDevName = devList[id].name;
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);
sampleRates = devList[id].sampleRates; // List samplerates and select default SR
sampleRatesTxt = "";
char buf[256]; char buf[256];
bool found = false; sampleRates.clear();
unsigned int defaultId = 0; const auto& srList = devList[id].sampleRates;
unsigned int defaultSr = devList[id].preferredSampleRate; unsigned int defaultSr = devList[id].preferredSampleRate;
for (int i = 0; i < sampleRates.size(); i++) { for (auto& sr : srList) {
if (sampleRates[i] == sampleRate) { if (sr == defaultSr) {
found = true; srId = sampleRates.size();
srId = i; sampleRate = sr;
} }
if (sampleRates[i] == defaultSr) { sprintf(buf, "%d", sr);
defaultId = i; sampleRates.define(sr, buf, sr);
}
sprintf(buf, "%d", sampleRates[i]);
sampleRatesTxt += buf;
sampleRatesTxt += '\0';
}
if (!found) {
sampleRate = defaultSr;
srId = defaultId;
} }
_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(); } if (running) { doStop(); }
// Update stream samplerate
entry->setSamplerate(sampleRate);
// Start the DSP
entry->startDSP();
// Start the sink
if (running) { doStart(); } if (running) { doStart(); }
} }
void menuHandler() { void showMenu() {
float menuWidth = ImGui::GetContentRegionAvail().x; float menuWidth = ImGui::GetContentRegionAvail().x;
ImGui::SetNextItemWidth(menuWidth); 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); selectById(devId);
config.acquire(); // config.acquire();
config.conf[_streamName]["device"] = devList[devId].name; // config.conf[streamName]["device"] = devList[devId].name;
config.release(true); // config.release(true);
} }
ImGui::SetNextItemWidth(menuWidth); 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]; sampleRate = sampleRates[srId];
_stream->setSampleRate(sampleRate);
if (running) { // Lock the sink
doStop(); auto lck = entry->getLock();
doStart();
} // Stop the sink DSP
config.acquire(); // TODO: Only if the sink DSP is running, otherwise you risk starting it when it shouldn't
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate; entry->stopDSP();
config.release(true);
// 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: private:
bool doStart() { bool doStart() {
RtAudio::StreamParameters parameters; RtAudio::StreamParameters parameters;
parameters.deviceId = deviceIds[devId]; parameters.deviceId = devList.key(devId);
parameters.nChannels = 2; parameters.nChannels = 2;
unsigned int bufferFrames = sampleRate / 60; unsigned int bufferFrames = sampleRate / 60;
RtAudio::StreamOptions opts; RtAudio::StreamOptions opts;
opts.flags = RTAUDIO_MINIMIZE_LATENCY; opts.flags = RTAUDIO_MINIMIZE_LATENCY;
opts.streamName = _streamName; opts.streamName = streamName;
try { try {
audio.openStream(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts); audio.openStream(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
@ -229,44 +267,34 @@ private:
return 0; return 0;
} }
SinkManager::Stream* _stream;
dsp::convert::StereoToMono s2m; dsp::convert::StereoToMono s2m;
dsp::buffer::Packer<float> monoPacker; dsp::buffer::Packer<float> monoPacker;
dsp::buffer::Packer<dsp::stereo_t> stereoPacker; dsp::buffer::Packer<dsp::stereo_t> stereoPacker;
std::string _streamName;
int srId = 0; int srId = 0;
int devCount;
int devId = 0; int devId = 0;
bool running = false; bool running = false;
std::string selectedDevName;
unsigned int defaultDevId = 0; unsigned int defaultDevId = 0;
std::vector<RtAudio::DeviceInfo> devList; OptionList<unsigned int, RtAudio::DeviceInfo> devList;
std::vector<unsigned int> deviceIds; OptionList<unsigned int, unsigned int> sampleRates;
std::string txtDevList;
std::vector<unsigned int> sampleRates;
std::string sampleRatesTxt;
unsigned int sampleRate = 48000; unsigned int sampleRate = 48000;
RtAudio audio; RtAudio audio;
}; };
class AudioSinkModule : public ModuleManager::Instance { class AudioSinkModule : public ModuleManager::Instance, public SinkProvider {
public: public:
AudioSinkModule(std::string name) { AudioSinkModule(std::string name) {
this->name = name; this->name = name;
provider.create = create_sink; sigpath::streamManager.registerSinkProvider("Audio", this);
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("Audio", provider);
} }
~AudioSinkModule() { ~AudioSinkModule() {
// Unregister sink, this will automatically stop and delete all instances of the audio sink sigpath::streamManager.unregisterSinkProvider(this);
sigpath::sinkManager.unregisterSinkProvider("Audio");
} }
void postInit() {} void postInit() {}
@ -283,14 +311,13 @@ public:
return enabled; return enabled;
} }
private: std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) {
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { return std::make_unique<AudioSink>(entry, stream, name, id, stringId);
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
} }
private:
std::string name; std::string name;
bool enabled = true; bool enabled = true;
SinkManager::SinkProvider provider;
}; };
MOD_EXPORT void _INIT_() { MOD_EXPORT void _INIT_() {

View File

@ -3,13 +3,14 @@
#include <module.h> #include <module.h>
#include <gui/gui.h> #include <gui/gui.h>
#include <signal_path/signal_path.h> #include <signal_path/signal_path.h>
#include <signal_path/sink.h> #include <signal_path/stream.h>
#include <dsp/buffer/packer.h> #include <dsp/buffer/packer.h>
#include <dsp/convert/stereo_to_mono.h> #include <dsp/convert/stereo_to_mono.h>
#include <dsp/sink/handler_sink.h> #include <dsp/sink/handler_sink.h>
#include <utils/flog.h> #include <utils/flog.h>
#include <config.h> #include <config.h>
#include <gui/style.h> #include <gui/style.h>
#include <utils/optionlist.h>
#include <core.h> #include <core.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str()) #define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -18,84 +19,97 @@ SDRPP_MOD_INFO{
/* Name: */ "network_sink", /* Name: */ "network_sink",
/* Description: */ "Network sink module for SDR++", /* Description: */ "Network sink module for SDR++",
/* Author: */ "Ryzerth", /* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0, /* Version: */ 0, 2, 0,
/* Max instances */ 1 /* Max instances */ 1
}; };
ConfigManager config; ConfigManager config;
enum { enum SinkMode {
SINK_MODE_TCP, SINK_MODE_TCP,
SINK_MODE_UDP SINK_MODE_UDP
}; };
const char* sinkModesTxt = "TCP\0UDP\0"; const char* sinkModesTxt = "TCP\0UDP\0";
class NetworkSink : SinkManager::Sink { class NetworkSink : public Sink {
public: public:
NetworkSink(SinkManager::Stream* stream, std::string streamName) { NetworkSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
_stream = stream; Sink(entry, stream, name, id, stringId)
_streamName = streamName; {
// Define modes
modes.define("TCP", SINK_MODE_TCP);
modes.define("UDP", SINK_MODE_UDP);
// Load config // Create a list of sample rates
config.acquire(); std::vector<int> srList;
if (!config.conf.contains(_streamName)) { for (int sr = 12000; sr <= 200000; sr += 12000) {
config.conf[_streamName]["hostname"] = "localhost"; srList.push_back(sr);
config.conf[_streamName]["port"] = 7355; }
config.conf[_streamName]["protocol"] = SINK_MODE_UDP; // UDP for (int sr = 11025; sr <= 192000; sr += 11025) {
config.conf[_streamName]["sampleRate"] = 48000.0; srList.push_back(sr);
config.conf[_streamName]["stereo"] = false;
config.conf[_streamName]["listening"] = false;
} }
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]; netBuf = new int16_t[STREAM_BUFFER_SIZE];
packer.init(_stream->sinkOut, 512); // Init DSP
packer.init(stream, 512);
s2m.init(&packer.out); s2m.init(&packer.out);
monoSink.init(&s2m.out, monoHandler, this); monoSink.init(&s2m.out, monoHandler, this);
stereoSink.init(&packer.out, stereoHandler, this); stereoSink.init(&packer.out, stereoHandler, this);
// Load config
// Create a list of sample rates config.acquire();
for (int sr = 12000; sr < 200000; sr += 12000) { bool startNow = false;
sampleRates.push_back(sr); if (config.conf[stringId].contains("hostname")) {
std::string host = config.conf[stringId]["hostname"];
strcpy(hostname, host.c_str());
} }
for (int sr = 11025; sr < 192000; sr += 11025) { if (config.conf[stringId].contains("port")) {
sampleRates.push_back(sr); port = config.conf[stringId]["port"];
} }
if (config.conf[stringId].contains("mode")) {
// Sort sample rate list std::string modeStr = config.conf[stringId]["mode"];
std::sort(sampleRates.begin(), sampleRates.end(), [](double a, double b) { return (a < b); }); if (modes.keyExists(modeStr)) {
mode = modes.value(modes.keyId(modeStr));
// Generate text list for UI }
char buffer[128]; else {
int id = 0; mode = SINK_MODE_TCP;
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) { if (config.conf[stringId].contains("samplerate")) {
srId = _48kId; int nSr = config.conf[stringId]["samplerate"];
sampleRate = 48000.0; if (samplerates.keyExists(nSr)) {
sampleRate = samplerates.value(samplerates.keyId(nSr));
}
else {
sampleRate = 48000;
}
} }
_stream->setSampleRate(sampleRate); if (config.conf[stringId].contains("stereo")) {
stereo = config.conf[stringId]["stereo"];
}
if (config.conf[stringId].contains("running")) {
startNow = config.conf[stringId]["running"];
}
config.release();
// Set mode ID
modeId = modes.valueId(mode);
// Set samplerate ID
srId = samplerates.valueId(sampleRate);
// Start if needed // Start if needed
if (startNow) { startServer(); } if (startNow) { startServer(); }
@ -122,30 +136,30 @@ public:
running = false; running = false;
} }
void menuHandler() { void showMenu() {
float menuWidth = ImGui::GetContentRegionAvail().x; float menuWidth = ImGui::GetContentRegionAvail().x;
bool listening = (listener && listener->isListening()) || (conn && conn->isOpen()); bool listening = (listener && listener->isListening()) || (conn && conn->isOpen());
if (listening) { style::beginDisabled(); } 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.acquire();
config.conf[_streamName]["hostname"] = hostname; config.conf[stringId]["hostname"] = hostname;
config.release(true); config.release(true);
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); 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.acquire();
config.conf[_streamName]["port"] = port; config.conf[stringId]["port"] = port;
config.release(true); config.release(true);
} }
ImGui::LeftLabel("Protocol"); ImGui::LeftLabel("Protocol");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); 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.acquire();
config.conf[_streamName]["protocol"] = modeId; config.conf[stringId]["mode"] = modeId;
config.release(true); config.release(true);
} }
@ -153,33 +167,33 @@ public:
ImGui::LeftLabel("Samplerate"); ImGui::LeftLabel("Samplerate");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(CONCAT("##_network_sink_sr_", _streamName), &srId, sampleRatesTxt.c_str())) { if (ImGui::Combo(CONCAT("##_network_sink_sr_", stringId), &srId, samplerates.txt)) {
sampleRate = sampleRates[srId]; sampleRate = samplerates.value(srId);
_stream->setSampleRate(sampleRate); entry->setSamplerate(sampleRate);
packer.setSampleCount(sampleRate / 60); packer.setSampleCount(sampleRate / 60);
config.acquire(); config.acquire();
config.conf[_streamName]["sampleRate"] = sampleRate; config.conf[stringId]["samplerate"] = sampleRate;
config.release(true); config.release(true);
} }
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", _streamName), &stereo)) { if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", stringId), &stereo)) {
stop(); stop();
start(); start();
config.acquire(); config.acquire();
config.conf[_streamName]["stereo"] = stereo; config.conf[stringId]["stereo"] = stereo;
config.release(true); 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(); stopServer();
config.acquire(); config.acquire();
config.conf[_streamName]["listening"] = false; config.conf[stringId]["running"] = false;
config.release(true); 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(); startServer();
config.acquire(); config.acquire();
config.conf[_streamName]["listening"] = true; config.conf[stringId]["running"] = true;
config.release(true); config.release(true);
} }
@ -271,47 +285,40 @@ private:
_this->listener->acceptAsync(clientHandler, _this); _this->listener->acceptAsync(clientHandler, _this);
} }
SinkManager::Stream* _stream; // DSP
dsp::buffer::Packer<dsp::stereo_t> packer; dsp::buffer::Packer<dsp::stereo_t> packer;
dsp::convert::StereoToMono s2m; dsp::convert::StereoToMono s2m;
dsp::sink::Handler<float> monoSink; dsp::sink::Handler<float> monoSink;
dsp::sink::Handler<dsp::stereo_t> stereoSink; dsp::sink::Handler<dsp::stereo_t> stereoSink;
std::string _streamName; OptionList<std::string, SinkMode> modes;
OptionList<int, double> samplerates;
int srId = 0;
bool running = false;
char hostname[1024]; char hostname[1024];
int port = 4242; int port = 7355;
SinkMode mode = SINK_MODE_TCP;
int modeId = 1; int modeId;
int sampleRate = 48000;
std::vector<unsigned int> sampleRates; int srId;
std::string sampleRatesTxt;
unsigned int sampleRate = 48000;
bool stereo = false; bool stereo = false;
bool running = false;
int16_t* netBuf; int16_t* netBuf;
net::Listener listener; net::Listener listener;
net::Conn conn; net::Conn conn;
std::mutex connMtx; std::mutex connMtx;
}; };
class NetworkSinkModule : public ModuleManager::Instance { class NetworkSinkModule : public ModuleManager::Instance, SinkProvider {
public: public:
NetworkSinkModule(std::string name) { NetworkSinkModule(std::string name) {
this->name = name; // Register self as provider
provider.create = create_sink; sigpath::streamManager.registerSinkProvider("Network", this);
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("Network", provider);
} }
~NetworkSinkModule() { ~NetworkSinkModule() {
// Unregister sink, this will automatically stop and delete all instances of the audio sink // Unregister self
sigpath::sinkManager.unregisterSinkProvider("Network"); sigpath::streamManager.unregisterSinkProvider(this);
} }
void postInit() {} void postInit() {}
@ -328,14 +335,13 @@ public:
return enabled; return enabled;
} }
private: std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) {
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { return std::make_unique<NetworkSink>(entry, stream, name, id, stringId);
return (SinkManager::Sink*)(new NetworkSink(stream, streamName));
} }
private:
std::string name; std::string name;
bool enabled = true; bool enabled = true;
SinkManager::SinkProvider provider;
}; };
MOD_EXPORT void _INIT_() { MOD_EXPORT void _INIT_() {