From 25bc9f60ed31fa0d25d201ed616420c9eab59f90 Mon Sep 17 00:00:00 2001 From: AlexandreRouma Date: Mon, 10 Jul 2023 03:48:59 +0200 Subject: [PATCH] more work --- CMakeLists.txt | 2 - core/src/gui/icons.cpp | 2 + core/src/gui/icons.h | 3 +- core/src/gui/menus/streams.cpp | 110 +++++++++++-- core/src/signal_path/stream.cpp | 30 +++- core/src/signal_path/stream.h | 24 ++- decoder_modules/radio/src/radio_module.h | 3 + root/res/icons/align_center.png | Bin 0 -> 9022 bytes sink_modules/audio_sink/src/main.cpp | 199 ++++++++++++----------- 9 files changed, 259 insertions(+), 114 deletions(-) create mode 100644 root/res/icons/align_center.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 18a3da1c..a9c67193 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) project(sdrpp) diff --git a/core/src/gui/icons.cpp b/core/src/gui/icons.cpp index 2204903e..fba1c577 100644 --- a/core/src/gui/icons.cpp +++ b/core/src/gui/icons.cpp @@ -16,6 +16,7 @@ namespace icons { ImTextureID UNMUTED; ImTextureID NORMAL_TUNING; ImTextureID CENTER_TUNING; + ImTextureID ALIGN_CENTER; GLuint loadTexture(std::string path) { int w, h, n; @@ -45,6 +46,7 @@ namespace icons { UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png"); NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png"); CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png"); + ALIGN_CENTER = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/align_center.png"); return true; } diff --git a/core/src/gui/icons.h b/core/src/gui/icons.h index 4a9f7fce..8dca22e2 100644 --- a/core/src/gui/icons.h +++ b/core/src/gui/icons.h @@ -13,7 +13,8 @@ namespace icons { extern ImTextureID UNMUTED; extern ImTextureID NORMAL_TUNING; extern ImTextureID CENTER_TUNING; - + extern ImTextureID ALIGN_CENTER; + GLuint loadTexture(std::string path); bool load(std::string resDir); } \ No newline at end of file diff --git a/core/src/gui/menus/streams.cpp b/core/src/gui/menus/streams.cpp index 3e0eedf2..1ec586fc 100644 --- a/core/src/gui/menus/streams.cpp +++ b/core/src/gui/menus/streams.cpp @@ -3,20 +3,57 @@ #include #include #include +#include +#include #define CONCAT(a, b) ((std::string(a) + b).c_str()) namespace streamsmenu { std::vector sinksToBeRemoved; + std::recursive_mutex sinkTypesMtx; + OptionList sinkTypes; + + std::map selectedSinkTypeId; + std::map addSinkTypeId; + + void updateSinkTypeList(const std::string& removed = "") { + std::lock_guard 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) { float menuWidth = ImGui::GetContentRegionAvail().x; auto lck = sigpath::streamManager.getStreamsLock(); - auto streams = sigpath::streamManager.getStreams(); + const auto& streams = sigpath::streamManager.getStreams(); int count = 0; int maxCount = streams.size(); @@ -26,35 +63,88 @@ namespace streamsmenu { ImGui::Text("%s", name.c_str()); // 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 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 + + 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(); - 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); } + ImGui::Spacing(); } lck2.unlock(); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); + float tableWidth = ImGui::GetContentRegionAvail().x; + ImGui::Spacing(); int ssds = 0; - ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &ssds, "Test 1\0Test 2\0"); + int startCur = ImGui::GetCursorPosX(); + { + std::lock_guard lck(sinkTypesMtx); + ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &ssds, sinkTypes.txt); + } + ImGui::SameLine(); - ImGui::FillWidth(); - if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name))) { + if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) { stream->addSink("Audio"); } + ImGui::Spacing(); ImGui::EndTable(); diff --git a/core/src/signal_path/stream.cpp b/core/src/signal_path/stream.cpp index e6e64735..8a969269 100644 --- a/core/src/signal_path/stream.cpp +++ b/core/src/signal_path/stream.cpp @@ -1,11 +1,12 @@ #include "stream.h" #include -Sink::Sink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID id) : +Sink::Sink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID id, const std::string& stringId) : entry(entry), stream(stream), streamName(name), - id(id) + id(id), + stringId(stringId) {} void Sink::showMenu() {} @@ -17,6 +18,12 @@ SinkEntry::SinkEntry(StreamManager* manager, AudioStream* parentStream, const st { 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); @@ -44,7 +51,7 @@ void SinkEntry::setType(const std::string& type) { auto lck2 = manager->getSinkTypesLock(); // 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()) { this->type.clear(); throw SinkEntryCreateException("Invalid sink type"); @@ -53,7 +60,7 @@ void SinkEntry::setType(const std::string& type) { // Create sink this->type = 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 { @@ -111,6 +118,15 @@ void SinkEntry::stopSink() { sink->stop(); } +std::lock_guard SinkEntry::getLock() { + return std::lock_guard(mtx); +} + +void SinkEntry::setSamplerate(double samplerate) { + std::lock_guard lck(mtx); + resamp.setOutSamplerate(samplerate); +} + void SinkEntry::startDSP() { std::lock_guard lck(mtx); resamp.start(); @@ -136,6 +152,10 @@ void SinkEntry::setInputSamplerate(double samplerate) { resamp.setInSamplerate(samplerate); } +std::string SinkEntry::getStringID() { + return stringId; +} + AudioStream::AudioStream(StreamManager* manager, const std::string& name, dsp::stream* stream, double samplerate) : manager(manager), name(name) @@ -449,7 +469,7 @@ void StreamManager::unregisterSinkProvider(SinkProvider* provider) { for (auto& [name, stream] : streams) { // Aquire lock on sink list 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 std::vector toRemove; diff --git a/core/src/signal_path/stream.h b/core/src/signal_path/stream.h index 44fd7fca..6821e2df 100644 --- a/core/src/signal_path/stream.h +++ b/core/src/signal_path/stream.h @@ -19,7 +19,7 @@ class SinkEntry; class Sink { public: - Sink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID id); + Sink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID id, const std::string& stringId); virtual ~Sink() {} virtual void start() = 0; @@ -31,6 +31,7 @@ protected: dsp::stream* const stream; const std::string streamName; const SinkID id; + const std::string stringId; }; class SinkProvider { @@ -41,7 +42,7 @@ public: * @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; + virtual std::unique_ptr createSink(SinkEntry* entry, dsp::stream* 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. @@ -62,6 +63,7 @@ class StreamManager; // TODO: Would be cool to have data and audio sinks instead of just audio. class SinkEntry { friend AudioStream; + friend Sink; public: SinkEntry(StreamManager* manager, AudioStream* parentStream, const std::string& type, SinkID id, double inputSamplerate); @@ -124,6 +126,12 @@ public: */ 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 NewEvent onTypeChanged; // Emmited when volume of the sink was changed @@ -133,11 +141,17 @@ public: // Emitted when the panning of the sink was changed NewEvent 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 getLock(); + void startDSP(); + void stopDSP(); + void setSamplerate(double samplerate); + private: void startSink(); void stopSink(); - void startDSP(); - void stopDSP(); + void destroy(bool forgetSettings); void setInputSamplerate(double samplerate); @@ -153,6 +167,8 @@ private: double inputSamplerate; AudioStream* const parentStream; StreamManager* const manager; + + std::string stringId; float volume = 1.0f; bool muted = false; diff --git a/decoder_modules/radio/src/radio_module.h b/decoder_modules/radio/src/radio_module.h index 057e4e55..b04a1468 100644 --- a/decoder_modules/radio/src/radio_module.h +++ b/decoder_modules/radio/src/radio_module.h @@ -446,6 +446,9 @@ private: afChain.disableAllBlocks([=](dsp::stream* out){ stream->setInput(out); }); } + // Update audo samplerate + stream->setSamplerate(selectedDemod->getAFSampleRate()); + // Start the IF chain ifChain.start(); diff --git a/root/res/icons/align_center.png b/root/res/icons/align_center.png new file mode 100644 index 0000000000000000000000000000000000000000..ed9caa977ddb8c19639b30cdbd77970ed182c018 GIT binary patch literal 9022 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 uH|I=Ch-0Me-eN4-GobX(=oOuG4F4Z0yQKaA literal 0 HcmV?d00001 diff --git a/sink_modules/audio_sink/src/main.cpp b/sink_modules/audio_sink/src/main.cpp index 15b7c260..3a6bc434 100644 --- a/sink_modules/audio_sink/src/main.cpp +++ b/sink_modules/audio_sink/src/main.cpp @@ -2,10 +2,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -22,26 +22,37 @@ SDRPP_MOD_INFO{ ConfigManager config; -class AudioSink : SinkManager::Sink { +bool operator==(const RtAudio::DeviceInfo& a, const RtAudio::DeviceInfo& b) { + return a.name == b.name; +} + +class AudioSink : public Sink { public: - AudioSink(SinkManager::Stream* stream, std::string streamName) { - _stream = stream; - _streamName = streamName; - s2m.init(_stream->sinkOut); + AudioSink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID id, const std::string& stringId) : + Sink(entry, stream, name, id, stringId) + { + s2m.init(stream); monoPacker.init(&s2m.out, 512); - stereoPacker.init(_stream->sinkOut, 512); + stereoPacker.init(stream, 512); - bool created = false; + // Convert config to the new format and get selected device + bool modified = false; std::string device = ""; - config.acquire(); - if (!config.conf.contains(_streamName)) { - created = true; - config.conf[_streamName]["device"] = ""; - config.conf[_streamName]["devices"] = json({}); - } - device = config.conf[_streamName]["device"]; - config.release(created); + // config.acquire(); + // if (config.conf.contains(streamName)) { + // if (!config.conf[streamName].is_array()) { + // json tmp = config.conf[streamName]; + // config.conf[streamName] = json::array(); + // config.conf[streamName][0] = tmp; + // modified = true; + // } + // if (config.conf[streamName].contains((int)id)) { + // device = config.conf[streamName][(int)id]["device"]; + // } + // } + // config.release(modified); + // List devices int count = audio.getDeviceCount(); RtAudio::DeviceInfo info; for (int i = 0; i < count; i++) { @@ -50,15 +61,13 @@ public: if (!info.probed) { continue; } if (info.outputChannels == 0) { continue; } if (info.isDefaultOutput) { defaultDevId = devList.size(); } - devList.push_back(info); - deviceIds.push_back(i); - txtDevList += info.name; - txtDevList += '\0'; + devList.define(i, info.name, info); } catch (std::exception e) { flog::error("AudioSinkModule Error getting audio device info: {0}", e.what()); } } + selectByName(device); } @@ -92,79 +101,104 @@ public: } void selectById(int id) { + // Update ID devId = id; - bool created = false; - config.acquire(); - if (!config.conf[_streamName]["devices"].contains(devList[id].name)) { - created = true; - config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate; - } - sampleRate = config.conf[_streamName]["devices"][devList[id].name]; - config.release(created); + selectedDevName = devList[id].name; - sampleRates = devList[id].sampleRates; - sampleRatesTxt = ""; + // List samplerates and select default SR char buf[256]; - bool found = false; - unsigned int defaultId = 0; + sampleRates.clear(); + const auto& srList = devList[id].sampleRates; unsigned int defaultSr = devList[id].preferredSampleRate; - for (int i = 0; i < sampleRates.size(); i++) { - if (sampleRates[i] == sampleRate) { - found = true; - srId = i; + for (auto& sr : srList) { + if (sr == defaultSr) { + srId = sampleRates.size(); + sampleRate = sr; } - if (sampleRates[i] == defaultSr) { - defaultId = i; - } - sprintf(buf, "%d", sampleRates[i]); - sampleRatesTxt += buf; - sampleRatesTxt += '\0'; - } - if (!found) { - sampleRate = defaultSr; - srId = defaultId; + sprintf(buf, "%d", sr); + sampleRates.define(sr, buf, sr); } + + // // 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(); - _stream->setSampleRate(sampleRate); + // Lock the sink + auto lck = entry->getLock(); + // Stop the sink DSP + // TODO: Only if the sink DSP is running, otherwise you risk starting it when it shouldn't + entry->stopDSP(); + + // Stop the sink if (running) { doStop(); } + + // Update stream samplerate + entry->setSamplerate(sampleRate); + + // Start the DSP + entry->startDSP(); + + // Start the sink if (running) { doStart(); } } - void menuHandler() { + void showMenu() { float menuWidth = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(menuWidth); - if (ImGui::Combo(("##_audio_sink_dev_" + _streamName).c_str(), &devId, txtDevList.c_str())) { + if (ImGui::Combo(("##_audio_sink_dev_" + stringId).c_str(), &devId, devList.txt)) { selectById(devId); - config.acquire(); - config.conf[_streamName]["device"] = devList[devId].name; - config.release(true); + // config.acquire(); + // config.conf[streamName]["device"] = devList[devId].name; + // config.release(true); } ImGui::SetNextItemWidth(menuWidth); - if (ImGui::Combo(("##_audio_sink_sr_" + _streamName).c_str(), &srId, sampleRatesTxt.c_str())) { + if (ImGui::Combo(("##_audio_sink_sr_" + stringId).c_str(), &srId, sampleRates.txt)) { sampleRate = sampleRates[srId]; - _stream->setSampleRate(sampleRate); - if (running) { - doStop(); - doStart(); - } - config.acquire(); - config.conf[_streamName]["devices"][devList[devId].name] = sampleRate; - config.release(true); + + // Lock the sink + auto lck = entry->getLock(); + + // Stop the sink DSP + // TODO: Only if the sink DSP is running, otherwise you risk starting it when it shouldn't + entry->stopDSP(); + + // Stop the sink + if (running) { doStop(); } + + // Update stream samplerate + entry->setSamplerate(sampleRate); + + // Start the DSP + entry->startDSP(); + + // Start the sink + if (running) { doStart(); } + + // config.acquire(); + // config.conf[streamName]["devices"][devList[devId].name] = sampleRate; + // config.release(true); } } private: bool doStart() { RtAudio::StreamParameters parameters; - parameters.deviceId = deviceIds[devId]; + parameters.deviceId = devList.key(devId); parameters.nChannels = 2; unsigned int bufferFrames = sampleRate / 60; RtAudio::StreamOptions opts; opts.flags = RTAUDIO_MINIMIZE_LATENCY; - opts.streamName = _streamName; + opts.streamName = streamName; try { audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts); @@ -198,57 +232,39 @@ private: int count = _this->stereoPacker.out.read(); 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)); _this->stereoPacker.out.flush(); return 0; } - SinkManager::Stream* _stream; dsp::convert::StereoToMono s2m; dsp::buffer::Packer monoPacker; dsp::buffer::Packer stereoPacker; - std::string _streamName; - int srId = 0; - int devCount; int devId = 0; bool running = false; + std::string selectedDevName; unsigned int defaultDevId = 0; - std::vector devList; - std::vector deviceIds; - std::string txtDevList; + OptionList devList; + OptionList sampleRates; - std::vector sampleRates; - std::string sampleRatesTxt; unsigned int sampleRate = 48000; RtAudio audio; }; -class AudioSinkModule : public ModuleManager::Instance { +class AudioSinkModule : public ModuleManager::Instance, public SinkProvider { public: AudioSinkModule(std::string name) { this->name = name; - provider.create = create_sink; - provider.ctx = this; - - sigpath::sinkManager.registerSinkProvider("Audio", provider); + sigpath::streamManager.registerSinkProvider("Audio", this); } ~AudioSinkModule() { - // Unregister sink, this will automatically stop and delete all instances of the audio sink - sigpath::sinkManager.unregisterSinkProvider("Audio"); + sigpath::streamManager.unregisterSinkProvider(this); } void postInit() {} @@ -265,14 +281,13 @@ public: return enabled; } -private: - static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { - return (SinkManager::Sink*)(new AudioSink(stream, streamName)); + std::unique_ptr createSink(SinkEntry* entry, dsp::stream* stream, const std::string& name, SinkID id, const std::string& stringId) { + return std::make_unique(entry, stream, name, id, stringId); } +private: std::string name; bool enabled = true; - SinkManager::SinkProvider provider; }; MOD_EXPORT void _INIT_() { @@ -294,4 +309,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { MOD_EXPORT void _END_() { config.disableAutoSave(); config.save(); -} +} \ No newline at end of file