diff --git a/CMakeLists.txt b/CMakeLists.txt index a9c67193..18a3da1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,5 @@ +set(CMAKE_BUILD_TYPE "Debug") # TODO: Make sure this gets properly disabled when going back to normal builds + cmake_minimum_required(VERSION 3.13) project(sdrpp) diff --git a/core/src/gui/main_window.cpp b/core/src/gui/main_window.cpp index 93e90469..635c7605 100644 --- a/core/src/gui/main_window.cpp +++ b/core/src/gui/main_window.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,7 @@ void MainWindow::init() { gui::menu.registerEntry("Source", sourcemenu::draw, NULL); gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL); + gui::menu.registerEntry("Streams", streamsmenu::draw, NULL); gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL); gui::menu.registerEntry("Display", displaymenu::draw, NULL); gui::menu.registerEntry("Theme", thememenu::draw, NULL); @@ -165,6 +167,7 @@ void MainWindow::init() { sourcemenu::init(); sinkmenu::init(); + streamsmenu::init(); bandplanmenu::init(); displaymenu::init(); vfo_color_menu::init(); diff --git a/core/src/gui/menus/streams.cpp b/core/src/gui/menus/streams.cpp new file mode 100644 index 00000000..4cea3bf3 --- /dev/null +++ b/core/src/gui/menus/streams.cpp @@ -0,0 +1,74 @@ +#include "streams.h" +#include +#include +#include +#include + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +namespace streamsmenu { + std::vector sinksToBeRemoved; + + void init() { + + } + + void draw(void* ctx) { + float menuWidth = ImGui::GetContentRegionAvail().x; + auto lck = sigpath::streamManager.getStreamsLock(); + 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 | ImGuiTableFlags_RowBg)) { + auto lck2 = stream->getSinksLock(); + auto sinks = stream->getSinks(); + for (auto& [id, sink] : sinks) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + + sink->showMenu(); + + ImGui::FillWidth(); + if (ImGui::Button(CONCAT("Remove##sdrpp_streams_remove_type_", name))) { + sinksToBeRemoved.push_back(id); + } + } + lck2.unlock(); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + + int ssds = 0; + ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &ssds, "Test 1\0Test 2\0"); + ImGui::SameLine(); + ImGui::FillWidth(); + if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name))) { + stream->addSink("Sink 2"); + } + + 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(); + } + } +}; \ No newline at end of file diff --git a/core/src/gui/menus/streams.h b/core/src/gui/menus/streams.h new file mode 100644 index 00000000..5b261a3a --- /dev/null +++ b/core/src/gui/menus/streams.h @@ -0,0 +1,6 @@ +#pragma once + +namespace streamsmenu { + void init(); + void draw(void* ctx); +}; \ No newline at end of file diff --git a/core/src/signal_path/signal_path.cpp b/core/src/signal_path/signal_path.cpp index a6695234..0e8f1fb9 100644 --- a/core/src/signal_path/signal_path.cpp +++ b/core/src/signal_path/signal_path.cpp @@ -5,4 +5,5 @@ namespace sigpath { VFOManager vfoManager; SourceManager sourceManager; SinkManager sinkManager; + StreamManager streamManager; }; \ No newline at end of file diff --git a/core/src/signal_path/signal_path.h b/core/src/signal_path/signal_path.h index 0c719384..f7621542 100644 --- a/core/src/signal_path/signal_path.h +++ b/core/src/signal_path/signal_path.h @@ -3,6 +3,7 @@ #include "vfo_manager.h" #include "source.h" #include "sink.h" +#include "stream.h" #include namespace sigpath { @@ -10,4 +11,5 @@ namespace sigpath { SDRPP_EXPORT VFOManager vfoManager; SDRPP_EXPORT SourceManager sourceManager; SDRPP_EXPORT SinkManager sinkManager; + SDRPP_EXPORT StreamManager streamManager; }; \ No newline at end of file diff --git a/core/src/signal_path/stream.cpp b/core/src/signal_path/stream.cpp index 2f2bf28c..9601154c 100644 --- a/core/src/signal_path/stream.cpp +++ b/core/src/signal_path/stream.cpp @@ -1,286 +1,455 @@ -// #include "stream.h" -// #include +#include "stream.h" +#include -// Sink::Sink(dsp::stream* stream, const std::string& name, SinkID id) : -// stream(stream), -// streamName(name), -// id(id) -// {} +Sink::Sink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID id) : + entry(entry), + stream(stream), + streamName(name), + id(id) +{} -// void Sink::showMenu() {} +void Sink::showMenu() {} -// AudioStream::AudioStream(StreamManager* manager, const std::string& name, dsp::stream* stream, double samplerate) : -// manager(manager), -// name(name) -// { -// this->samplerate = samplerate; - -// // Initialize DSP -// split.init(stream); -// split.start(); -// } - -// void AudioStream::setInput(dsp::stream* stream, double samplerate = 0.0) { -// std::lock_guard lck1(mtx); - -// // If all that's needed is to set the input, do it and return -// if (samplerate == 0.0) { -// split.setInput(stream); -// return; -// } - -// // Lock sink list -// { -// std::unique_lock lck2(sinksMtx); -// this->samplerate = samplerate; - -// // Stop DSP -// 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 -// for (auto& [id, sink] : sinks) { -// sink->startDSP(); -// } -// split.start(); -// } -// } - -// SinkID AudioStream::addSink(const std::string& type, SinkID id = -1) { -// std::unique_lock lck(sinksMtx); - -// // Find a free ID if not provided -// if (id < 0) { -// for (id = 0;; id++) { -// if (sinks.find(id) != sinks.end()) { continue; } -// } -// } -// 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; -// } -// } +SinkEntry::SinkEntry(StreamManager* manager, AudioStream* parentStream, const std::string& type, SinkID id, double inputSamplerate) : + manager(manager), + parentStream(parentStream), + id(id) +{ + this->type = type; + this->inputSamplerate = inputSamplerate; -// // Create sink entry -// std::shared_ptr sink; -// try { -// sink = std::make_shared(type, id, samplerate); -// } -// catch (SinkEntryCreateException e) { -// flog::error("Tried to create sink for stream '{}' with ID '{}': {}", name, id, e.what()); -// return -1; -// } + // Initialize DSP + resamp.init(&input, inputSamplerate, inputSamplerate); + volumeAdjust.init(&resamp.out, 1.0f, false); -// // Start the sink and DSP -// sink->startSink(); -// sink->startDSP(); + // Initialize the sink + setType(type); +} -// // Bind the sinks's input -// split.bindStream(&sink->input); +std::string SinkEntry::getType() { + std::lock_guard lck(mtx); + return type; +} -// // Add sink to list -// sinks[id] = sink; +void SinkEntry::setType(const std::string& type) { + // Get unique lock on the entry + std::lock_guard lck(mtx); -// // Release lock and emit event -// lck.unlock(); -// onSinkAdded(sink); -// } + // Delete existing sink + if (sink) { + provider->destroySink(std::move(sink)); + } -// void AudioStream::removeSink(SinkID id, bool forgetSettings = true) { -// // Acquire shared lock -// std::shared_ptr sink; -// { -// std::shared_lock lck(sinksMtx); + // Get shared lock on sink types + auto lck2 = manager->getSinkTypesLock(); -// // Check that the ID exists -// if (sinks.find(id) == sinks.end()) { -// flog::error("Tried to remove sink with unknown ID: {}", id); -// return; -// } + // Get the provider or throw error + auto types = manager->getSinkTypes(); + if (std::find(types.begin(), types.end(), type) == types.end()) { + this->type.clear(); + throw SinkEntryCreateException("Invalid sink type"); + } -// // Get sink -// sink = sinks[id]; -// } + // Create sink + this->type = type; + provider = manager->providers[type]; + sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id); +} + +SinkID SinkEntry::getID() const { + return id; +} + +float SinkEntry::getVolume() { + std::lock_guard lck(mtx); + return volume; +} + +void SinkEntry::setVolume(float volume) { + std::lock_guard lck(mtx); + this->volume = volume; + volumeAdjust.setVolume(volume); + onVolumeChanged(volume); +} + +bool SinkEntry::getMuted() { + std::lock_guard lck(mtx); + return muted; +} + +void SinkEntry::setMuted(bool muted) { + std::lock_guard lck(mtx); + this->muted = muted; + volumeAdjust.setMuted(muted); + onMutedChanged(muted); +} + +float SinkEntry::getPanning() { + std::lock_guard lck(mtx); + return panning; +} + +void SinkEntry::setPanning(float panning) { + std::lock_guard lck(mtx); + this->panning = panning; + // TODO + onPanningChanged(panning); +} + +void SinkEntry::showMenu() { + std::lock_guard lck(mtx); + sink->showMenu(); +} + +void SinkEntry::startSink() { + std::lock_guard lck(mtx); + sink->start(); +} + +void SinkEntry::stopSink() { + std::lock_guard lck(mtx); + sink->stop(); +} + +void SinkEntry::startDSP() { + std::lock_guard lck(mtx); + resamp.start(); + volumeAdjust.start(); +} + +void SinkEntry::stopDSP() { + std::lock_guard lck(mtx); + resamp.stop(); + volumeAdjust.stop(); +} + +void SinkEntry::destroy(bool forgetSettings) { + std::lock_guard lck(mtx); + if (sink) { + provider->destroySink(std::move(sink)); + } + type.clear(); +} + +void SinkEntry::setInputSamplerate(double samplerate) { + std::lock_guard lck(mtx); + resamp.setInSamplerate(samplerate); +} + +AudioStream::AudioStream(StreamManager* manager, const std::string& name, dsp::stream* stream, double samplerate) : + manager(manager), + name(name) +{ + this->samplerate = samplerate; + + // Initialize DSP + split.init(stream); + split.start(); +} + +AudioStream::~AudioStream() { + // Copy sink IDs + std::vector ids; + for (auto& [id, sink] : sinks) { + ids.push_back(id); + } + + // Remove them all + for (auto& id : ids) { + removeSink(id, false); + } +} + +void AudioStream::setInput(dsp::stream* stream, double samplerate) { + std::unique_lock 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 + 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 + for (auto& [id, sink] : sinks) { + sink->startDSP(); + } + split.start(); +} + +void AudioStream::setSamplerate(double samplerate) { + std::unique_lock lck(sinksMtx); + + // Update samplerate + this->samplerate = samplerate; + + // Stop DSP + split.stop(); + for (auto& [id, sink] : sinks) { + sink->stopDSP(); + } + + // Set samplerate + for (auto& [id, sink] : sinks) { + sink->setInputSamplerate(samplerate); + } + + // Start DSP + for (auto& [id, sink] : sinks) { + sink->startDSP(); + } + split.start(); +} + +const std::string& AudioStream::getName() const { + return name; +} + +SinkID AudioStream::addSink(const std::string& type, SinkID id) { + std::unique_lock 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; + } + } -// // Emit event -// onSinkRemove(sink); + // Create sink entry + std::shared_ptr sink; + try { + sink = std::make_shared(manager, this, type, id, samplerate); + } + catch (SinkEntryCreateException e) { + flog::error("Tried to create sink for stream '{}' with ID '{}': {}", name, id, e.what()); + return -1; + } -// // Acquire unique lock -// { -// std::unique_lock lck(sinksMtx); + // Start the sink and DSP + sink->startSink(); + sink->startDSP(); -// // 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; -// } + // Bind the sinks's input + split.bindStream(&sink->input); -// // Remove from list -// sinks.erase(id); + // Add sink to list + sinks[id] = sink; -// // Unbind the sink's steam -// split.unbindStream(&sink->input); + // Release lock and emit event + lck.unlock(); + onSinkAdded(sink); + + return id; +} + +void AudioStream::removeSink(SinkID id, bool forgetSettings) { + // Acquire shared lock + std::shared_ptr sink; + { + std::shared_lock 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 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(); -// } -// } + // Stop the sink and DSP + sink->stopDSP(); + sink->stopSink(); -// std::shared_lock AudioStream::getSinksLock() { -// return std::shared_lock(sinksMtx); -// } + // Delete instance + sink->destroy(forgetSettings); + } +} -// const std::map>& AudioStream::getSinks() const { -// return sinks; -// } +std::shared_lock AudioStream::getSinksLock() { + return std::shared_lock(sinksMtx); +} -// std::shared_ptr StreamManager::createStream(const std::string& name, dsp::stream* stream, double samplerate) { -// std::unique_lock lck(streamsMtx); +const std::map>& AudioStream::getSinks() const { + return sinks; +} -// // 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; -// } +std::shared_ptr StreamManager::createStream(const std::string& name, dsp::stream* stream, double samplerate) { + std::unique_lock lck(streamsMtx); -// // Create and save stream -// auto newStream = std::make_shared(this, name, stream, samplerate); -// streams[name] = newStream; + // 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; + } -// // Release lock and emit event -// lck.unlock(); -// onStreamCreated(newStream); -// } + // Create and save stream + auto newStream = std::make_shared(this, name, stream, samplerate); + streams[name] = newStream; -// void StreamManager::destroyStream(std::shared_ptr& stream) { -// // Emit event -// onStreamDestroy(stream); + // Release lock and emit event + lck.unlock(); + onStreamCreated(newStream); -// // Aquire complete lock on the stream list -// { -// std::unique_lock lck(streamsMtx); + return newStream; +} -// // Get iterator of the stream -// auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::shared_ptr& s) { -// return s == stream; -// }); -// if (it == streams.end()) { -// flog::error("Tried to delete a stream using an invalid pointer. Stream not found in list"); -// return; -// } +void StreamManager::destroyStream(std::shared_ptr& stream) { + // Emit event + onStreamDestroy(stream); -// // Delete entry from list -// flog::debug("Stream pointer uses, should be 2 and is {}", stream.use_count()); -// streams.erase(it); -// } + // Aquire complete lock on the stream list + { + std::unique_lock lck(streamsMtx); -// // Reset passed pointer -// stream.reset(); -// } + // Get iterator of the stream + auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair> 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; + } -// std::shared_lock StreamManager::getStreamsLock() { -// return std::shared_lock(streamsMtx); -// } + // Delete entry from list + flog::debug("Stream pointer uses, should be 2 and is {}", (int)stream.use_count()); + streams.erase(it); + } -// const std::map>& StreamManager::getStreams() const { -// return streams; -// } + // Reset passed pointer + stream.reset(); +} -// void StreamManager::registerSinkProvider(const std::string& name, SinkProvider* provider) { -// std::unique_lock lck(providersMtx); +std::shared_lock StreamManager::getStreamsLock() { + return std::shared_lock(streamsMtx); +} -// // 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; -// } +const std::map>& StreamManager::getStreams() const { + return streams; +} -// // Add provider to the list and sort name list -// providers[name] = provider; -// sinkTypes.push_back(name); -// std::sort(sinkTypes.begin(), sinkTypes.end()); +void StreamManager::registerSinkProvider(const std::string& name, SinkProvider* provider) { + std::unique_lock lck(providersMtx); -// // Release lock and emit event -// lck.unlock(); -// onSinkProviderRegistered(name); -// } + // 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; + } -// void StreamManager::unregisterSinkProvider(SinkProvider* provider) { -// // Get provider name for event -// std::string type; -// { -// std::shared_lock lck(providersMtx); -// auto it = std::find_if(providers.begin(), providers.end(), [&provider](SinkProvider* p) { -// p == provider; -// }); -// if (it == providers.end()) { -// flog::error("Tried to unregister sink provider using invalid pointer"); -// return; -// } -// type = (*it).first; -// } + // Add provider to the list and sort name list + providers[name] = provider; + sinkTypes.push_back(name); + std::sort(sinkTypes.begin(), sinkTypes.end()); -// // Emit event -// onSinkProviderUnregister(type); + // Release lock and emit event + lck.unlock(); + onSinkProviderRegistered(name); +} -// // Acquire shared lock on streams -// { -// std::shared_lock lck(streamsMtx); -// for (auto& [name, stream] : streams) { -// std::vector toRemove; +void StreamManager::unregisterSinkProvider(SinkProvider* provider) { + // Get provider name for event + std::string type; + { + std::shared_lock lck(providersMtx); + auto it = std::find_if(providers.begin(), providers.end(), [&provider](std::pair e) { + return e.second == provider; + }); + if (it == providers.end()) { + flog::error("Tried to unregister sink provider using invalid pointer"); + return; + } + type = (*it).first; + } -// // Aquire lock on sink list -// auto sLock = stream->getSinksLock(); -// auto sinks = stream->getSinks(); + // Emit event + onSinkProviderUnregister(type); -// // Find all sinks with the type that is about to be removed -// for (auto& [id, sink] : sinks) { -// if (sink->getType() != type) { continue; } -// toRemove.push_back(id); -// } + // Acquire shared lock on streams + { + std::unique_lock lck1(providersMtx); + std::shared_lock lck2(streamsMtx); + for (auto& [name, stream] : streams) { + // Aquire lock on sink list + auto sLock = stream->getSinksLock(); + auto sinks = stream->getSinks(); -// // Remove them all (TODO: THERE IS RACE CONDITION IF A SINK IS CHANGED AFTER LISTING) -// for (auto& id : toRemove) { -// stream->removeSink(id); -// } -// } -// } + // Find all sinks with the type that is about to be removed + std::vector toRemove; + for (auto& [id, sink] : sinks) { + if (sink->getType() != type) { continue; } + toRemove.push_back(id); + } -// // Remove from the lists -// { -// std::unique_lock lck(providersMtx); -// if (providers.find(type) != providers.end()) { -// providers.erase(type); -// } -// else { -// flog::error("Could not remove sink provider from list"); -// } + // Remove them all (TODO: THERE IS RACE CONDITION IF A SINK IS CHANGED AFTER LISTING) + sLock.unlock(); + for (auto& id : toRemove) { + stream->removeSink(id); + } + } -// 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"); -// } -// } -// } + // Remove from the lists + if (providers.find(type) != providers.end()) { + providers.erase(type); + } + else { + flog::error("Could not remove sink provider from list"); + } -// std::shared_lock StreamManager::getSinkTypesLock() { -// return std::shared_lock(providersMtx); -// } + 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"); + } + } +} -// const std::vector& StreamManager::getSinkTypes() const { -// return sinkTypes; -// } \ No newline at end of file +std::shared_lock StreamManager::getSinkTypesLock() { + return std::shared_lock(providersMtx); +} + +const std::vector& StreamManager::getSinkTypes() const { + // TODO: This allows code to modify the names... + return sinkTypes; +} \ No newline at end of file diff --git a/core/src/signal_path/stream.h b/core/src/signal_path/stream.h index 35651233..54d86f91 100644 --- a/core/src/signal_path/stream.h +++ b/core/src/signal_path/stream.h @@ -15,35 +15,61 @@ class AudioStream; using SinkID = int; +class SinkEntry; + class Sink { public: - Sink(dsp::stream* stream, const std::string& name, SinkID id); + Sink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID id); + virtual ~Sink() {} virtual void start() = 0; virtual void stop() = 0; virtual void showMenu(); -private: +protected: + SinkEntry* const entry; dsp::stream* const stream; const std::string streamName; const SinkID id; }; +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 createSink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID index) = 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.reset(); + } +}; + class SinkEntryCreateException : public std::runtime_error { public: SinkEntryCreateException(const char* what) : std::runtime_error(what) {} }; +class StreamManager; + +// TODO: Would be cool to have data and audio sinks instead of just audio. class SinkEntry { friend AudioStream; - SinkEntry(const std::string& type, SinkID id, double inputSamplerate); public: - + SinkEntry(StreamManager* manager, AudioStream* parentStream, const std::string& type, SinkID id, double inputSamplerate); + /** * Get the type of the sink. * @return Type of the sink. */ - const std::string& getType(); + std::string getType(); /** * Change the type of the sink. @@ -55,13 +81,13 @@ public: * Get the ID of the sink. * @return ID of the sink. */ - SinkID getID(); + SinkID getID() const; /** * Get sink volume. * @return Volume as value between 0.0 and 1.0. */ - float getVolume() const; + float getVolume(); /** * Set sink volume. @@ -73,7 +99,7 @@ public: * Check if the sink is muted. * @return True if muted, false if not. */ - bool getMuted() const; + bool getMuted(); /** * Set wether or not the sink is muted @@ -85,7 +111,7 @@ public: * Get sink panning. * @return Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively. */ - float getPanning() const; + float getPanning(); /** * Set sink panning. @@ -93,6 +119,11 @@ public: */ void setPanning(float panning); + /** + * Show the sink type-specific menu. + */ + void showMenu(); + // Emitted when the type of the sink was changed NewEvent onTypeChanged; // Emmited when volume of the sink was changed @@ -107,15 +138,23 @@ private: void stopSink(); void startDSP(); void stopDSP(); + void destroy(bool forgetSettings); void setInputSamplerate(double samplerate); + std::recursive_mutex mtx; dsp::stream input; dsp::multirate::RationalResampler resamp; dsp::audio::Volume volumeAdjust; + + SinkProvider* provider = NULL; std::unique_ptr sink; - double inputSamplerate; + std::string type; const SinkID id; + double inputSamplerate; + AudioStream* const parentStream; + StreamManager* const manager; + float volume = 1.0f; bool muted = false; float panning = 0.0f; @@ -125,8 +164,9 @@ class StreamManager; class AudioStream { friend StreamManager; - AudioStream(StreamManager* manager, const std::string& name, dsp::stream* stream, double samplerate); public: + AudioStream(StreamManager* manager, const std::string& name, dsp::stream* stream, double samplerate); + ~AudioStream(); /** * Set DSP stream input. @@ -135,6 +175,8 @@ public: */ void setInput(dsp::stream* stream, double samplerate = 0.0); + // TODO: There must be a way to pre-stop things to avoid having weird shit happen + /** * Set the samplerate of the input stream. * @param samplerate Samplerate in Hz. @@ -147,9 +189,6 @@ public: */ const std::string& getName() const; - // TODO: USING AN INDEX IS BAD! - // THIS IS BECAUSE THEY WILL CHANGE AS SOME ARE REMOVED OR ADDED - /** * Add a sink to the stream. * @param type Type of the sink. @@ -185,8 +224,7 @@ public: NewEvent> onSinkRemove; private: - std::recursive_mutex mtx; - const StreamManager* manager; + StreamManager* const manager; const std::string name; double samplerate; dsp::routing::Splitter split; @@ -195,23 +233,8 @@ private: std::shared_mutex sinksMtx; }; -class SinkProvider { -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 createSink(dsp::stream* stream, const std::string& name, int index) = 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) = 0; -}; - class StreamManager { + friend SinkEntry; public: /** * Create an audio stream. @@ -278,9 +301,6 @@ private: std::map> streams; std::shared_mutex streamsMtx; - // TODO: Switch all this shit to a recursive mutex RW locks are shit - // Or maybe not actually - std::map providers; std::vector sinkTypes; std::shared_mutex providersMtx;