diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e7b5ee..ce75fe7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,6 +347,7 @@ SET (cubicsdr_sources src/modules/modem/analog/ModemLSB.cpp src/modules/modem/analog/ModemUSB.cpp src/audio/AudioThread.cpp + src/audio/AudioSinkThread.cpp src/util/Gradient.cpp src/util/Timer.cpp src/util/MouseTracker.cpp @@ -451,6 +452,7 @@ SET (cubicsdr_headers src/modules/modem/analog/ModemLSB.h src/modules/modem/analog/ModemUSB.h src/audio/AudioThread.h + src/audio/AudioSinkThread.h src/util/Gradient.h src/util/Timer.h src/util/ThreadBlockingQueue.h diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp index 2315883..fc75440 100644 --- a/src/AppConfig.cpp +++ b/src/AppConfig.cpp @@ -505,6 +505,14 @@ bool AppConfig::getBookmarksVisible() { return bookmarksVisible.load(); } +void AppConfig::setRecordingPath(std::string recPath) { + recordingPath = recPath; +} + +std::string AppConfig::getRecordingPath() { + return recordingPath; +} + void AppConfig::setConfigName(std::string configName) { this->configName = configName; @@ -559,6 +567,9 @@ bool AppConfig::save() { *window_node->newChild("bookmark_visible") = bookmarksVisible.load(); } + DataNode *rec_node = cfg.rootNode()->newChild("recording"); + *rec_node->newChild("path") = recordingPath; + DataNode *devices_node = cfg.rootNode()->newChild("devices"); std::map::iterator device_config_i; @@ -741,6 +752,15 @@ bool AppConfig::load() { } } + if (cfg.rootNode()->hasAnother("recording")) { + DataNode *rec_node = cfg.rootNode()->getNext("recording"); + + if (rec_node->hasAnother("path")) { + DataNode *rec_path = cfg.rootNode()->getNext("path"); + recordingPath = rec_path->element()->toString(); + } + } + if (cfg.rootNode()->hasAnother("devices")) { DataNode *devices_node = cfg.rootNode()->getNext("devices"); diff --git a/src/AppConfig.h b/src/AppConfig.h index 75614b2..d620ee9 100644 --- a/src/AppConfig.h +++ b/src/AppConfig.h @@ -138,6 +138,8 @@ public: void setBookmarksVisible(bool state); bool getBookmarksVisible(); + void setRecordingPath(std::string recPath); + std::string getRecordingPath(); #if USE_HAMLIB int getRigModel(); @@ -185,6 +187,7 @@ private: std::atomic_int dbOffset; std::vector manualDevices; std::atomic_bool bookmarksVisible; + std::string recordingPath; #if USE_HAMLIB std::atomic_int rigModel, rigRate; std::string rigPort; diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index cea79f7..4617a91 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -412,6 +412,8 @@ AppFrame::AppFrame() : menu->AppendSeparator(); menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); menu->AppendSeparator(); + menu->Append(wxID_RECORDING_PATH, "Set Recording Path"); + menu->AppendSeparator(); menu->Append(wxID_OPEN, "&Open Session"); menu->Append(wxID_SAVE, "&Save Session"); menu->Append(wxID_SAVEAS, "Save Session &As.."); @@ -1564,6 +1566,16 @@ void AppFrame::OnMenu(wxCommandEvent& event) { } } } + else if (event.GetId() == wxID_RECORDING_PATH) { + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + + wxDirDialog recPathDialog(this, _("File Path for Recordings"), recPath, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if (recPathDialog.ShowModal() == wxID_CANCEL) { + return; + } + + wxGetApp().getConfig()->setRecordingPath(recPathDialog.GetPath().ToStdString()); + } else if (event.GetId() == wxID_LOW_PERF) { lowPerfMode = lowPerfMenuItem->IsChecked(); wxGetApp().getConfig()->setLowPerfMode(lowPerfMode); diff --git a/src/AppFrame.h b/src/AppFrame.h index 45413ce..fcee6e5 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -42,6 +42,7 @@ #define wxID_LOW_PERF 2011 #define wxID_SET_DB_OFFSET 2012 #define wxID_ABOUT_CUBICSDR 2013 +#define wxID_RECORDING_PATH 2014 #define wxID_MAIN_SPLITTER 2050 #define wxID_VIS_SPLITTER 2051 diff --git a/src/audio/AudioSinkThread.cpp b/src/audio/AudioSinkThread.cpp new file mode 100644 index 0000000..85c4e5a --- /dev/null +++ b/src/audio/AudioSinkThread.cpp @@ -0,0 +1,83 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioSinkThread.h" + +#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000) + +AudioSinkThread::AudioSinkThread() +{ + inputQueuePtr = std::make_shared(); + setInputQueue("input", inputQueuePtr); +} + +AudioSinkThread::~AudioSinkThread() +{ + +} + +void AudioSinkThread::run() +{ +#ifdef __APPLE__ + pthread_t tID = pthread_self(); // ID of this thread + int priority = sched_get_priority_max(SCHED_RR) - 1; + sched_param prio = { priority }; // scheduling priority of thread + pthread_setschedparam(tID, SCHED_RR, &prio); +#endif + + AudioThreadInputPtr inp; + AudioThreadInput inputRef; + + while (!stopping) { + if (!inputQueuePtr->pop(inp, HEARTBEAT_CHECK_PERIOD_MICROS)) { + continue; + } + + if (inputRef.channels != inp->channels || + inputRef.frequency != inp->frequency || + inputRef.inputRate != inp->inputRate || + inputRef.sampleRate != inp->sampleRate) { + + inputChanged(inputRef, inp); + + inputRef.channels = inp->channels; + inputRef.frequency = inp->frequency; + inputRef.inputRate = inp->inputRate; + inputRef.sampleRate = inp->sampleRate; + } + } + + //Thread termination, prevent fancy things to happen, lock the whole thing: + std::lock_guard lock(m_mutex); + + // Drain any remaining inputs, with a non-blocking pop + inputQueuePtr->flush(); +} + +void AudioSinkThread::terminate() +{ + IOThread::terminate(); +} + +void AudioSinkThread::sink(AudioThreadInputPtr * input) +{ + // do something with the audio data +} + +void AudioSinkThread::inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) +{ + // handle changes in stream properties +} + +void AudioSinkThread::setSinkName(std::string sinkName_in) +{ + sinkName = sinkName_in; +} + +std::string AudioSinkThread::getSinkName() +{ + return sinkName; +} + + + diff --git a/src/audio/AudioSinkThread.h b/src/audio/AudioSinkThread.h new file mode 100644 index 0000000..a8876d8 --- /dev/null +++ b/src/audio/AudioSinkThread.h @@ -0,0 +1,28 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioThread.h" + +class AudioSinkThread : public IOThread { + +public: + + AudioSinkThread(); + virtual ~AudioSinkThread(); + + virtual void run(); + virtual void terminate(); + + virtual void sink(AudioThreadInputPtr *input); + virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); + + virtual void setSinkName(std::string sinkName_in); + virtual std::string getSinkName(); + +protected: + std::recursive_mutex m_mutex; + AudioThreadInputQueuePtr inputQueuePtr; + std::string sinkName; +}; diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index 51cc0d9..a5ecfbe 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -24,7 +24,7 @@ public: std::vector data; AudioThreadInput() : - frequency(0), sampleRate(0), channels(0), peak(0) { + frequency(0), sampleRate(0), inputRate(0), channels(0), peak(0), type(0) { }