mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-11-14 22:42:52 +01:00
more work
This commit is contained in:
parent
2e80882ab5
commit
25bc9f60ed
@ -1,5 +1,3 @@
|
|||||||
set(CMAKE_BUILD_TYPE "Debug") # TODO: Make sure this gets properly disabled when going back to normal builds
|
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.13)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
project(sdrpp)
|
project(sdrpp)
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -3,20 +3,57 @@
|
|||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <utils/flog.h>
|
#include <utils/flog.h>
|
||||||
#include <gui/style.h>
|
#include <gui/style.h>
|
||||||
|
#include <gui/icons.h>
|
||||||
|
#include <utils/optionlist.h>
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||||
|
|
||||||
namespace streamsmenu {
|
namespace streamsmenu {
|
||||||
std::vector<SinkID> sinksToBeRemoved;
|
std::vector<SinkID> sinksToBeRemoved;
|
||||||
|
|
||||||
void init() {
|
std::recursive_mutex sinkTypesMtx;
|
||||||
|
OptionList<std::string, std::string> sinkTypes;
|
||||||
|
|
||||||
|
std::map<std::string, int> selectedSinkTypeId;
|
||||||
|
std::map<std::string, int> addSinkTypeId;
|
||||||
|
|
||||||
|
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 selected ID of each drop down
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSinkProviderUnregister(const std::string& type) {
|
||||||
|
// Update the list
|
||||||
|
updateSinkTypeList(type);
|
||||||
|
|
||||||
|
// 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) {
|
void draw(void* ctx) {
|
||||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||||
auto lck = sigpath::streamManager.getStreamsLock();
|
auto lck = sigpath::streamManager.getStreamsLock();
|
||||||
auto streams = sigpath::streamManager.getStreams();
|
const auto& streams = sigpath::streamManager.getStreams();
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int maxCount = streams.size();
|
int maxCount = streams.size();
|
||||||
@ -26,35 +63,88 @@ namespace streamsmenu {
|
|||||||
ImGui::Text("%s", name.c_str());
|
ImGui::Text("%s", name.c_str());
|
||||||
|
|
||||||
// Display ever sink
|
// Display ever sink
|
||||||
if (ImGui::BeginTable(CONCAT("sdrpp_streams_tbl_", name), 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
if (ImGui::BeginTable(CONCAT("sdrpp_streams_tbl_", name), 1, ImGuiTableFlags_Borders)) {
|
||||||
auto lck2 = stream->getSinksLock();
|
auto lck2 = stream->getSinksLock();
|
||||||
auto sinks = stream->getSinks();
|
auto sinks = stream->getSinks();
|
||||||
for (auto& [id, sink] : sinks) {
|
for (auto& [id, sink] : sinks) {
|
||||||
|
std::string sid = sink->getStringID();
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
float tableWidth = ImGui::GetContentRegionAvail().x;
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
|
||||||
|
// Sink type
|
||||||
|
|
||||||
|
|
||||||
sink->showMenu();
|
sink->showMenu();
|
||||||
|
float vol = sink->getVolume();
|
||||||
|
bool muted = sink->getMuted();
|
||||||
|
float pan = sink->getPanning();
|
||||||
|
bool linked = true;
|
||||||
|
|
||||||
// Volume slider + mute button
|
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();
|
ImGui::FillWidth();
|
||||||
if (ImGui::Button(CONCAT("Remove##sdrpp_streams_remove_type_", name))) {
|
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);
|
sinksToBeRemoved.push_back(id);
|
||||||
}
|
}
|
||||||
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
lck2.unlock();
|
lck2.unlock();
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
float tableWidth = ImGui::GetContentRegionAvail().x;
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
int ssds = 0;
|
int ssds = 0;
|
||||||
ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &ssds, "Test 1\0Test 2\0");
|
int startCur = ImGui::GetCursorPosX();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lck(sinkTypesMtx);
|
||||||
|
ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &ssds, sinkTypes.txt);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::FillWidth();
|
if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
|
||||||
if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name))) {
|
|
||||||
stream->addSink("Audio");
|
stream->addSink("Audio");
|
||||||
}
|
}
|
||||||
|
ImGui::Spacing();
|
||||||
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include <utils/flog.h>
|
#include <utils/flog.h>
|
||||||
|
|
||||||
Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id) :
|
Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
|
||||||
entry(entry),
|
entry(entry),
|
||||||
stream(stream),
|
stream(stream),
|
||||||
streamName(name),
|
streamName(name),
|
||||||
id(id)
|
id(id),
|
||||||
|
stringId(stringId)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void Sink::showMenu() {}
|
void Sink::showMenu() {}
|
||||||
@ -18,6 +19,12 @@ SinkEntry::SinkEntry(StreamManager* manager, AudioStream* parentStream, const st
|
|||||||
this->type = type;
|
this->type = type;
|
||||||
this->inputSamplerate = inputSamplerate;
|
this->inputSamplerate = inputSamplerate;
|
||||||
|
|
||||||
|
// Generate string ID
|
||||||
|
stringId = parentStream->getName();
|
||||||
|
char buf[16];
|
||||||
|
sprintf(buf, "%d", (int)id);
|
||||||
|
stringId += buf;
|
||||||
|
|
||||||
// Initialize DSP
|
// Initialize DSP
|
||||||
resamp.init(&input, inputSamplerate, inputSamplerate);
|
resamp.init(&input, inputSamplerate, inputSamplerate);
|
||||||
volumeAdjust.init(&resamp.out, 1.0f, false);
|
volumeAdjust.init(&resamp.out, 1.0f, false);
|
||||||
@ -44,7 +51,7 @@ void SinkEntry::setType(const std::string& type) {
|
|||||||
auto lck2 = manager->getSinkTypesLock();
|
auto lck2 = manager->getSinkTypesLock();
|
||||||
|
|
||||||
// Get the provider or throw error
|
// Get the provider or throw error
|
||||||
auto types = manager->getSinkTypes();
|
const auto& types = manager->getSinkTypes();
|
||||||
if (std::find(types.begin(), types.end(), type) == types.end()) {
|
if (std::find(types.begin(), types.end(), type) == types.end()) {
|
||||||
this->type.clear();
|
this->type.clear();
|
||||||
throw SinkEntryCreateException("Invalid sink type");
|
throw SinkEntryCreateException("Invalid sink type");
|
||||||
@ -53,7 +60,7 @@ void SinkEntry::setType(const std::string& type) {
|
|||||||
// Create sink
|
// Create sink
|
||||||
this->type = type;
|
this->type = type;
|
||||||
provider = manager->providers[type];
|
provider = manager->providers[type];
|
||||||
sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id);
|
sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id, stringId);
|
||||||
}
|
}
|
||||||
|
|
||||||
SinkID SinkEntry::getID() const {
|
SinkID SinkEntry::getID() const {
|
||||||
@ -111,6 +118,15 @@ void SinkEntry::stopSink() {
|
|||||||
sink->stop();
|
sink->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::recursive_mutex> SinkEntry::getLock() {
|
||||||
|
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() {
|
void SinkEntry::startDSP() {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||||
resamp.start();
|
resamp.start();
|
||||||
@ -136,6 +152,10 @@ void SinkEntry::setInputSamplerate(double samplerate) {
|
|||||||
resamp.setInSamplerate(samplerate);
|
resamp.setInSamplerate(samplerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string SinkEntry::getStringID() {
|
||||||
|
return stringId;
|
||||||
|
}
|
||||||
|
|
||||||
AudioStream::AudioStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
|
AudioStream::AudioStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
|
||||||
manager(manager),
|
manager(manager),
|
||||||
name(name)
|
name(name)
|
||||||
@ -449,7 +469,7 @@ void StreamManager::unregisterSinkProvider(SinkProvider* provider) {
|
|||||||
for (auto& [name, stream] : streams) {
|
for (auto& [name, stream] : streams) {
|
||||||
// Aquire lock on sink list
|
// Aquire lock on sink list
|
||||||
auto sLock = stream->getSinksLock();
|
auto sLock = stream->getSinksLock();
|
||||||
auto sinks = stream->getSinks();
|
const auto& sinks = stream->getSinks();
|
||||||
|
|
||||||
// Find all sinks with the type that is about to be removed
|
// Find all sinks with the type that is about to be removed
|
||||||
std::vector<SinkID> toRemove;
|
std::vector<SinkID> toRemove;
|
||||||
|
@ -19,7 +19,7 @@ class SinkEntry;
|
|||||||
|
|
||||||
class Sink {
|
class Sink {
|
||||||
public:
|
public:
|
||||||
Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id);
|
Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId);
|
||||||
virtual ~Sink() {}
|
virtual ~Sink() {}
|
||||||
|
|
||||||
virtual void start() = 0;
|
virtual void start() = 0;
|
||||||
@ -31,6 +31,7 @@ protected:
|
|||||||
dsp::stream<dsp::stereo_t>* const stream;
|
dsp::stream<dsp::stereo_t>* const stream;
|
||||||
const std::string streamName;
|
const std::string streamName;
|
||||||
const SinkID id;
|
const SinkID id;
|
||||||
|
const std::string stringId;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SinkProvider {
|
class SinkProvider {
|
||||||
@ -41,7 +42,7 @@ public:
|
|||||||
* @param name Name of the audio stream.
|
* @param name Name of the audio stream.
|
||||||
* @param index Index of the sink in the menu. Should be use to keep settings.
|
* @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 index) = 0;
|
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.
|
* Destroy a sink instance. This function is so that the provide knows at all times how many instances there are.
|
||||||
@ -62,6 +63,7 @@ class StreamManager;
|
|||||||
// TODO: Would be cool to have data and audio sinks instead of just audio.
|
// TODO: Would be cool to have data and audio sinks instead of just audio.
|
||||||
class SinkEntry {
|
class SinkEntry {
|
||||||
friend AudioStream;
|
friend AudioStream;
|
||||||
|
friend Sink;
|
||||||
public:
|
public:
|
||||||
SinkEntry(StreamManager* manager, AudioStream* parentStream, const std::string& type, SinkID id, double inputSamplerate);
|
SinkEntry(StreamManager* manager, AudioStream* parentStream, const std::string& type, SinkID id, double inputSamplerate);
|
||||||
|
|
||||||
@ -124,6 +126,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
void showMenu();
|
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();
|
||||||
|
|
||||||
// Emitted when the type of the sink was changed
|
// Emitted when the type of the sink was changed
|
||||||
NewEvent<const std::string&> onTypeChanged;
|
NewEvent<const std::string&> onTypeChanged;
|
||||||
// Emmited when volume of the sink was changed
|
// Emmited when volume of the sink was changed
|
||||||
@ -133,11 +141,17 @@ public:
|
|||||||
// Emitted when the panning of the sink was changed
|
// Emitted when the panning of the sink was changed
|
||||||
NewEvent<float> onPanningChanged;
|
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();
|
||||||
|
void startDSP();
|
||||||
|
void stopDSP();
|
||||||
|
void setSamplerate(double samplerate);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startSink();
|
void startSink();
|
||||||
void stopSink();
|
void stopSink();
|
||||||
void startDSP();
|
|
||||||
void stopDSP();
|
|
||||||
void destroy(bool forgetSettings);
|
void destroy(bool forgetSettings);
|
||||||
void setInputSamplerate(double samplerate);
|
void setInputSamplerate(double samplerate);
|
||||||
|
|
||||||
@ -154,6 +168,8 @@ private:
|
|||||||
AudioStream* const parentStream;
|
AudioStream* const parentStream;
|
||||||
StreamManager* const manager;
|
StreamManager* const manager;
|
||||||
|
|
||||||
|
std::string stringId;
|
||||||
|
|
||||||
float volume = 1.0f;
|
float volume = 1.0f;
|
||||||
bool muted = false;
|
bool muted = false;
|
||||||
float panning = 0.0f;
|
float panning = 0.0f;
|
||||||
|
@ -446,6 +446,9 @@ private:
|
|||||||
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
|
// Start the IF chain
|
||||||
ifChain.start();
|
ifChain.start();
|
||||||
|
|
||||||
|
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 <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>
|
||||||
@ -22,26 +22,37 @@ SDRPP_MOD_INFO{
|
|||||||
|
|
||||||
ConfigManager config;
|
ConfigManager config;
|
||||||
|
|
||||||
class AudioSink : SinkManager::Sink {
|
bool operator==(const RtAudio::DeviceInfo& a, const RtAudio::DeviceInfo& b) {
|
||||||
public:
|
return a.name == b.name;
|
||||||
AudioSink(SinkManager::Stream* stream, std::string streamName) {
|
|
||||||
_stream = stream;
|
|
||||||
_streamName = streamName;
|
|
||||||
s2m.init(_stream->sinkOut);
|
|
||||||
monoPacker.init(&s2m.out, 512);
|
|
||||||
stereoPacker.init(_stream->sinkOut, 512);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
class AudioSink : public Sink {
|
||||||
|
public:
|
||||||
|
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, 512);
|
||||||
|
|
||||||
|
// Convert config to the new format and get selected device
|
||||||
|
bool modified = false;
|
||||||
|
std::string device = "";
|
||||||
|
// 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
|
||||||
int count = audio.getDeviceCount();
|
int count = audio.getDeviceCount();
|
||||||
RtAudio::DeviceInfo info;
|
RtAudio::DeviceInfo info;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
@ -50,15 +61,13 @@ public:
|
|||||||
if (!info.probed) { continue; }
|
if (!info.probed) { continue; }
|
||||||
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 (std::exception e) {
|
catch (std::exception e) {
|
||||||
flog::error("AudioSinkModule Error getting audio device info: {0}", e.what());
|
flog::error("AudioSinkModule Error getting audio device info: {0}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectByName(device);
|
selectByName(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,79 +101,104 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
|
audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
|
||||||
@ -198,57 +232,39 @@ private:
|
|||||||
int count = _this->stereoPacker.out.read();
|
int count = _this->stereoPacker.out.read();
|
||||||
if (count < 0) { return 0; }
|
if (count < 0) { return 0; }
|
||||||
|
|
||||||
// For debug purposes only...
|
|
||||||
// if (nBufferFrames != count) { flog::warn("Buffer size mismatch, wanted {0}, was asked for {1}", count, nBufferFrames); }
|
|
||||||
// for (int i = 0; i < count; i++) {
|
|
||||||
// if (_this->stereoPacker.out.readBuf[i].l == NAN || _this->stereoPacker.out.readBuf[i].r == NAN) { flog::error("NAN in audio data"); }
|
|
||||||
// if (_this->stereoPacker.out.readBuf[i].l == INFINITY || _this->stereoPacker.out.readBuf[i].r == INFINITY) { flog::error("INFINITY in audio data"); }
|
|
||||||
// if (_this->stereoPacker.out.readBuf[i].l == -INFINITY || _this->stereoPacker.out.readBuf[i].r == -INFINITY) { flog::error("-INFINITY in audio data"); }
|
|
||||||
// }
|
|
||||||
|
|
||||||
memcpy(outputBuffer, _this->stereoPacker.out.readBuf, nBufferFrames * sizeof(dsp::stereo_t));
|
memcpy(outputBuffer, _this->stereoPacker.out.readBuf, nBufferFrames * sizeof(dsp::stereo_t));
|
||||||
_this->stereoPacker.out.flush();
|
_this->stereoPacker.out.flush();
|
||||||
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() {}
|
||||||
@ -265,14 +281,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_() {
|
||||||
|
Loading…
Reference in New Issue
Block a user