From a398bc57f18784718d821798627691e28455eb7d Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Sun, 24 Sep 2017 23:50:08 -0400 Subject: [PATCH 01/22] Initial AudioSink thread base and recoring preference --- CMakeLists.txt | 2 + src/AppConfig.cpp | 20 +++++++++ src/AppConfig.h | 3 ++ src/AppFrame.cpp | 12 +++++ src/AppFrame.h | 1 + src/audio/AudioSinkThread.cpp | 83 +++++++++++++++++++++++++++++++++++ src/audio/AudioSinkThread.h | 28 ++++++++++++ src/audio/AudioThread.h | 2 +- 8 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/audio/AudioSinkThread.cpp create mode 100644 src/audio/AudioSinkThread.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e7b5ee6..ce75fe7f 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 2315883b..fc754401 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 75614b2a..d620ee90 100644 --- a/src/AppConfig.h +++ b/src/AppConfig.h @@ -138,6 +138,8 @@ class AppConfig { void setBookmarksVisible(bool state); bool getBookmarksVisible(); + void setRecordingPath(std::string recPath); + std::string getRecordingPath(); #if USE_HAMLIB int getRigModel(); @@ -185,6 +187,7 @@ class AppConfig { 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 cea79f7b..4617a91e 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 45413ce8..fcee6e5b 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 00000000..85c4e5a0 --- /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 00000000..a8876d8f --- /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 51cc0d95..a5ecfbea 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -24,7 +24,7 @@ class AudioThreadInput { std::vector data; AudioThreadInput() : - frequency(0), sampleRate(0), channels(0), peak(0) { + frequency(0), sampleRate(0), inputRate(0), channels(0), peak(0), type(0) { } From c202d99a2aef9711a92c43ae25cf66b795a7a2ef Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 26 Sep 2017 23:25:55 -0400 Subject: [PATCH 02/22] initial audio sink file and thread handling rough-in --- CMakeLists.txt | 6 ++++ src/audio/AudioFile.cpp | 27 ++++++++++++++++++ src/audio/AudioFile.h | 26 +++++++++++++++++ src/audio/AudioFileWAV.cpp | 47 +++++++++++++++++++++++++++++++ src/audio/AudioFileWAV.h | 26 +++++++++++++++++ src/audio/AudioSinkFileThread.cpp | 34 ++++++++++++++++++++++ src/audio/AudioSinkFileThread.h | 24 ++++++++++++++++ src/audio/AudioSinkThread.cpp | 37 +++++------------------- src/audio/AudioSinkThread.h | 5 ++-- src/demod/DemodulatorInstance.cpp | 23 +++++++++++++-- src/demod/DemodulatorInstance.h | 5 ++++ 11 files changed, 226 insertions(+), 34 deletions(-) create mode 100644 src/audio/AudioFile.cpp create mode 100644 src/audio/AudioFile.h create mode 100644 src/audio/AudioFileWAV.cpp create mode 100644 src/audio/AudioFileWAV.h create mode 100644 src/audio/AudioSinkFileThread.cpp create mode 100644 src/audio/AudioSinkFileThread.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ce75fe7f..24cd20e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -348,6 +348,9 @@ SET (cubicsdr_sources src/modules/modem/analog/ModemUSB.cpp src/audio/AudioThread.cpp src/audio/AudioSinkThread.cpp + src/audio/AudioSinkFileThread.cpp + src/audio/AudioFile.cpp + src/audio/AudioFileWAV.cpp src/util/Gradient.cpp src/util/Timer.cpp src/util/MouseTracker.cpp @@ -453,6 +456,9 @@ SET (cubicsdr_headers src/modules/modem/analog/ModemUSB.h src/audio/AudioThread.h src/audio/AudioSinkThread.h + src/audio/AudioSinkFileThread.h + src/audio/AudioFile.h + src/audio/AudioFileWAV.h src/util/Gradient.h src/util/Timer.h src/util/ThreadBlockingQueue.h diff --git a/src/audio/AudioFile.cpp b/src/audio/AudioFile.cpp new file mode 100644 index 00000000..063d6dd7 --- /dev/null +++ b/src/audio/AudioFile.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioFile.h" +#include "CubicSDR.h" + +AudioFile::AudioFile() { + +} + +AudioFile::~AudioFile() { + +} + +void AudioFile::setOutputFileName(std::string filename) { + filenameBase = filename; +} + +std::string AudioFile::getOutputFileName() { + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + + // TODO: Handle invalid chars, etc.. + std::string filenameBaseSafe = filenameBase; + + return recPath + filePathSeparator + filenameBaseSafe + getSuffix() + "." + getExtension(); +} + diff --git a/src/audio/AudioFile.h b/src/audio/AudioFile.h new file mode 100644 index 00000000..a982e4f1 --- /dev/null +++ b/src/audio/AudioFile.h @@ -0,0 +1,26 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioThread.h" + +class AudioFile +{ + +public: + AudioFile(); + virtual ~AudioFile(); + + virtual void setOutputFileName(std::string filename); + virtual std::string getExtension() = 0; + virtual std::string getSuffix() = 0; + virtual std::string getOutputFileName(); + + virtual bool writeToFile(AudioThreadInputPtr input) = 0; + virtual bool closeFile() = 0; + +protected: + std::string filenameBase; + +}; \ No newline at end of file diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp new file mode 100644 index 00000000..afe52242 --- /dev/null +++ b/src/audio/AudioFileWAV.cpp @@ -0,0 +1,47 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioFileWAV.h" +// #include "WavFileFormatHandlerStuff.h" + +AudioFileWAV::AudioFileWAV() : AudioFile() { +} + +AudioFileWAV::~AudioFileWAV() { +} + + +std::string AudioFileWAV::getExtension() +{ + return "wav"; +} + +std::string AudioFileWAV::getSuffix() +{ + return suffix; +} + +bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) +{ + if (!outputFileStream.is_open()) { + suffix = ""; + std::string ofName = getOutputFileName(); + + // Check if file exists, sequence the suffix? + + outputFileStream.open(ofName.c_str(), std::ios::out | std::ios::binary); + } + + // write input data to wav file + + return true; +} + +bool AudioFileWAV::closeFile() +{ + if (outputFileStream.is_open()) { + outputFileStream.close(); + } + + return true; +} diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h new file mode 100644 index 00000000..2787ac34 --- /dev/null +++ b/src/audio/AudioFileWAV.h @@ -0,0 +1,26 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioFile.h" + +#include + +class AudioFileWAV : public AudioFile { + +public: + AudioFileWAV(); + ~AudioFileWAV(); + + std::string getExtension(); + std::string getSuffix(); + + bool writeToFile(AudioThreadInputPtr input); + bool closeFile(); + +protected: + std::ofstream outputFileStream; + std::string suffix; + +}; \ No newline at end of file diff --git a/src/audio/AudioSinkFileThread.cpp b/src/audio/AudioSinkFileThread.cpp new file mode 100644 index 00000000..93fc1823 --- /dev/null +++ b/src/audio/AudioSinkFileThread.cpp @@ -0,0 +1,34 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioSinkFileThread.h" + +AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() { + +} + +AudioSinkFileThread::~AudioSinkFileThread() { + if (outputFileHandler != nullptr) { + outputFileHandler->closeFile(); + } +} + +void AudioSinkFileThread::sink(AudioThreadInputPtr input) { + if (!outputFileHandler) { + return; + } + // forward to output file handler + outputFileHandler->writeToFile(input); +} + +void AudioSinkFileThread::inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) { + // close, set new parameters, adjust file name sequence and re-open? + if (!outputFileHandler) { + return; + } +} + +void AudioSinkFileThread::setOutput(AudioFile * output) { + outputFileHandler = output; + outputFileHandler->setOutputFileName(sinkName); +} diff --git a/src/audio/AudioSinkFileThread.h b/src/audio/AudioSinkFileThread.h new file mode 100644 index 00000000..4e3930eb --- /dev/null +++ b/src/audio/AudioSinkFileThread.h @@ -0,0 +1,24 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioSinkThread.h" +#include "AudioFile.h" + +class AudioSinkFileThread : public AudioSinkThread { + +public: + AudioSinkFileThread(); + ~AudioSinkFileThread(); + + void sink(AudioThreadInputPtr input); + void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); + + void setOutput(AudioFile *output); + +protected: + AudioFile *outputFileHandler = nullptr; + +}; + diff --git a/src/audio/AudioSinkThread.cpp b/src/audio/AudioSinkThread.cpp index 85c4e5a0..14c58ede 100644 --- a/src/audio/AudioSinkThread.cpp +++ b/src/audio/AudioSinkThread.cpp @@ -5,19 +5,16 @@ #define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000) -AudioSinkThread::AudioSinkThread() -{ +AudioSinkThread::AudioSinkThread() { inputQueuePtr = std::make_shared(); setInputQueue("input", inputQueuePtr); } -AudioSinkThread::~AudioSinkThread() -{ +AudioSinkThread::~AudioSinkThread() { } -void AudioSinkThread::run() -{ +void AudioSinkThread::run() { #ifdef __APPLE__ pthread_t tID = pthread_self(); // ID of this thread int priority = sched_get_priority_max(SCHED_RR) - 1; @@ -46,38 +43,18 @@ void AudioSinkThread::run() 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() -{ +void AudioSinkThread::terminate() { IOThread::terminate(); + inputQueuePtr->flush(); } -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) -{ +void AudioSinkThread::setSinkName(std::string sinkName_in) { sinkName = sinkName_in; } -std::string AudioSinkThread::getSinkName() -{ +std::string AudioSinkThread::getSinkName() { return sinkName; } - - diff --git a/src/audio/AudioSinkThread.h b/src/audio/AudioSinkThread.h index a8876d8f..d01fa643 100644 --- a/src/audio/AudioSinkThread.h +++ b/src/audio/AudioSinkThread.h @@ -15,8 +15,8 @@ class AudioSinkThread : public IOThread { virtual void run(); virtual void terminate(); - virtual void sink(AudioThreadInputPtr *input); - virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); + virtual void sink(AudioThreadInputPtr input) = 0; + virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) = 0; virtual void setSinkName(std::string sinkName_in); virtual std::string getSinkName(); @@ -25,4 +25,5 @@ class AudioSinkThread : public IOThread { std::recursive_mutex m_mutex; AudioThreadInputQueuePtr inputQueuePtr; std::string sinkName; + }; diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 13f481b1..6886b9fe 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -106,6 +106,7 @@ DemodulatorInstance::~DemodulatorInstance() { delete demodulatorPreThread; delete demodulatorThread; delete audioThread; + delete audioSinkThread; break; } @@ -181,6 +182,10 @@ void DemodulatorInstance::terminate() { // std::cout << "Terminating demodulator preprocessor thread.." << std::endl; demodulatorPreThread->terminate(); + if (audioSinkThread != nullptr) { + audioSinkThread->terminate(); + } + //that will actually unblock the currently blocked push(). pipeIQInputData->flush(); pipeAudioData->flush(); @@ -204,6 +209,7 @@ bool DemodulatorInstance::isTerminated() { bool audioTerminated = audioThread->isTerminated(); bool demodTerminated = demodulatorThread->isTerminated(); bool preDemodTerminated = demodulatorPreThread->isTerminated(); + bool audioSinkTerminated = (audioSinkThread == nullptr) || audioSinkThread->isTerminated(); //Cleanup the worker threads, if the threads are indeed terminated. // threads are linked as t_PreDemod ==> t_Demod ==> t_Audio @@ -240,14 +246,27 @@ bool DemodulatorInstance::isTerminated() { if (audioTerminated) { if (t_Audio) { +#ifdef __APPLE__ + pthread_join(t_PreDemod, NULL); +#else t_Audio->join(); - delete t_Audio; +#endif t_Audio = nullptr; } } - bool terminated = audioTerminated && demodTerminated && preDemodTerminated; + if (audioSinkTerminated) { + + if (t_AudioSink != nullptr) { + t_AudioSink->join(); + + delete t_AudioSink; + t_AudioSink = nullptr; + } + } + + bool terminated = audioTerminated && demodTerminated && preDemodTerminated && audioSinkTerminated; return terminated; } diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h index 846aa865..f36d6351 100644 --- a/src/demod/DemodulatorInstance.h +++ b/src/demod/DemodulatorInstance.h @@ -11,6 +11,7 @@ #include "ModemDigital.h" #include "ModemAnalog.h" #include "AudioThread.h" +#include "AudioSinkThread.h" #if ENABLE_DIGITAL_LAB #include "DigitalConsole.h" @@ -139,6 +140,10 @@ class DemodulatorInstance { DemodulatorThread *demodulatorThread; DemodulatorThreadControlCommandQueuePtr threadQueueControl; + AudioSinkThread *audioSinkThread = nullptr; + std::thread *t_AudioSink = nullptr; + AudioThreadInputQueuePtr audioSinkInputQueue; + //protects child thread creation and termination std::recursive_mutex m_thread_control_mutex; From f8e51df8cd1ec6e64a7d7db999c168b89aeab023 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 9 Oct 2017 20:07:40 -0400 Subject: [PATCH 03/22] Demod instance start/stop recording setup --- src/audio/AudioSinkFileThread.cpp | 17 ++++---- src/audio/AudioSinkFileThread.h | 4 +- src/audio/AudioSinkThread.cpp | 11 +----- src/audio/AudioSinkThread.h | 4 -- src/demod/DemodulatorInstance.cpp | 65 ++++++++++++++++++++++++++++++- src/demod/DemodulatorInstance.h | 9 +++++ src/demod/DemodulatorThread.cpp | 20 ++++++++++ src/demod/DemodulatorThread.h | 2 + 8 files changed, 108 insertions(+), 24 deletions(-) diff --git a/src/audio/AudioSinkFileThread.cpp b/src/audio/AudioSinkFileThread.cpp index 93fc1823..2b00cdb8 100644 --- a/src/audio/AudioSinkFileThread.cpp +++ b/src/audio/AudioSinkFileThread.cpp @@ -8,27 +8,28 @@ AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() { } AudioSinkFileThread::~AudioSinkFileThread() { - if (outputFileHandler != nullptr) { - outputFileHandler->closeFile(); + if (audioFileHandler != nullptr) { + audioFileHandler->closeFile(); } } void AudioSinkFileThread::sink(AudioThreadInputPtr input) { - if (!outputFileHandler) { + if (!audioFileHandler) { return; } // forward to output file handler - outputFileHandler->writeToFile(input); + audioFileHandler->writeToFile(input); } void AudioSinkFileThread::inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) { // close, set new parameters, adjust file name sequence and re-open? - if (!outputFileHandler) { + if (!audioFileHandler) { return; } + + audioFileHandler->closeFile(); } -void AudioSinkFileThread::setOutput(AudioFile * output) { - outputFileHandler = output; - outputFileHandler->setOutputFileName(sinkName); +void AudioSinkFileThread::setAudioFileHandler(AudioFile * output) { + audioFileHandler = output; } diff --git a/src/audio/AudioSinkFileThread.h b/src/audio/AudioSinkFileThread.h index 4e3930eb..1ba4c4c7 100644 --- a/src/audio/AudioSinkFileThread.h +++ b/src/audio/AudioSinkFileThread.h @@ -15,10 +15,10 @@ class AudioSinkFileThread : public AudioSinkThread { void sink(AudioThreadInputPtr input); void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); - void setOutput(AudioFile *output); + void setAudioFileHandler(AudioFile *output); protected: - AudioFile *outputFileHandler = nullptr; + AudioFile *audioFileHandler = nullptr; }; diff --git a/src/audio/AudioSinkThread.cpp b/src/audio/AudioSinkThread.cpp index 14c58ede..11e9009d 100644 --- a/src/audio/AudioSinkThread.cpp +++ b/src/audio/AudioSinkThread.cpp @@ -42,6 +42,8 @@ void AudioSinkThread::run() { inputRef.inputRate = inp->inputRate; inputRef.sampleRate = inp->sampleRate; } + + sink(inp); } } @@ -49,12 +51,3 @@ void AudioSinkThread::terminate() { IOThread::terminate(); inputQueuePtr->flush(); } - -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 index d01fa643..b862b0df 100644 --- a/src/audio/AudioSinkThread.h +++ b/src/audio/AudioSinkThread.h @@ -18,12 +18,8 @@ class AudioSinkThread : public IOThread { virtual void sink(AudioThreadInputPtr input) = 0; virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) = 0; - 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/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 6886b9fe..f63d1969 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -2,12 +2,15 @@ // SPDX-License-Identifier: GPL-2.0+ #include +#include + #include "DemodulatorInstance.h" #include "CubicSDR.h" #include "DemodulatorThread.h" #include "DemodulatorPreThread.h" - +#include "AudioSinkFileThread.h" +#include "AudioFileWAV.h" #if USE_HAMLIB #include "RigThread.h" @@ -47,6 +50,7 @@ DemodulatorInstance::DemodulatorInstance() { active.store(false); squelch.store(false); muted.store(false); + recording.store(false); deltaLock.store(false); deltaLockOfs.store(0); currentOutputDevice.store(-1); @@ -542,6 +546,21 @@ void DemodulatorInstance::setMuted(bool muted) { wxGetApp().getDemodMgr().setLastMuted(muted); } +bool DemodulatorInstance::isRecording() +{ + return recording.load(); +} + +void DemodulatorInstance::setRecording(bool recording_in) +{ + if (!recording.load() && recording_in) { + startRecording(); + } + else if (recording.load() && !recording_in) { + stopRecording(); + } +} + DemodVisualCue *DemodulatorInstance::getVisualCue() { return &visualCue; } @@ -599,6 +618,50 @@ ModemSettings DemodulatorInstance::getLastModemSettings(std::string demodType) { } } + +void DemodulatorInstance::startRecording() { + if (recording.load()) { + return; + } + + AudioSinkFileThread *newSinkThread = new AudioSinkFileThread(); + AudioFileWAV *afHandler = new AudioFileWAV(); + + std::stringstream fileName; + fileName << getLabel() << "_" << std::time(nullptr); + + afHandler->setOutputFileName(fileName.str()); + newSinkThread->setAudioFileHandler(afHandler); + + audioSinkThread = newSinkThread; + t_AudioSink = new std::thread(&AudioSinkThread::threadMain, audioSinkThread); + + demodulatorThread->setOutputQueue("AudioSink", audioSinkThread->getInputQueue("input")); + + recording.store(true); +} + + +void DemodulatorInstance::stopRecording() { + if (!recording.load()) { + return; + } + + demodulatorThread->setOutputQueue("AudioSink", nullptr); + audioSinkThread->terminate(); + + t_AudioSink->join(); + + delete t_AudioSink; + delete audioSinkThread; + + t_AudioSink = nullptr; + audioSinkThread = nullptr; + + recording.store(false); +} + + #if ENABLE_DIGITAL_LAB ModemDigitalOutput *DemodulatorInstance::getOutput() { if (activeOutput == nullptr) { diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h index f36d6351..297df69a 100644 --- a/src/demod/DemodulatorInstance.h +++ b/src/demod/DemodulatorInstance.h @@ -111,6 +111,9 @@ class DemodulatorInstance { bool isMuted(); void setMuted(bool muted); + bool isRecording(); + void setRecording(bool recording); + DemodVisualCue *getVisualCue(); DemodulatorThreadInputQueuePtr getIQInputDataPipe(); @@ -132,6 +135,10 @@ class DemodulatorInstance { void closeOutput(); #endif +protected: + void startRecording(); + void stopRecording(); + private: DemodulatorThreadInputQueuePtr pipeIQInputData; DemodulatorThreadPostInputQueuePtr pipeIQDemodData; @@ -155,6 +162,8 @@ class DemodulatorInstance { std::atomic_bool squelch; std::atomic_bool muted; std::atomic_bool deltaLock; + std::atomic_bool recording; + std::atomic_int deltaLockOfs; std::atomic_int currentOutputDevice; diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index d1bfb56f..9b9d5c98 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -43,6 +43,12 @@ void DemodulatorThread::onBindOutput(std::string name, ThreadQueueBasePtr thread audioVisOutputQueue = std::static_pointer_cast(threadQueue); } + + if (name == "AudioSinkOutput") { + std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); + + audioSinkOutputQueue = std::static_pointer_cast(threadQueue); + } } double DemodulatorThread::abMagnitude(float inphase, float quadrature) { @@ -310,6 +316,13 @@ void DemodulatorThread::run() { } } + // Capture audioSinkOutputQueue state in a local + DemodulatorThreadOutputQueuePtr localAudioSinkOutputQueue = nullptr; + { + std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); + localAudioSinkOutputQueue = audioSinkOutputQueue; + } + if (ati != nullptr) { if (!muted.load() && (!wxGetApp().getSoloMode() || (demodInstance == wxGetApp().getDemodMgr().getLastActiveDemodulator().get()))) { //non-blocking push needed for audio out @@ -318,6 +331,13 @@ void DemodulatorThread::run() { std::cout << "DemodulatorThread::run() cannot push ati into audioOutputQueue, is full !" << std::endl; std::this_thread::yield(); } + + if (localAudioSinkOutputQueue != nullptr) { + if (!audioSinkOutputQueue->try_push(ati)) { + std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; + std::this_thread::yield(); + } + } } } diff --git a/src/demod/DemodulatorThread.h b/src/demod/DemodulatorThread.h index bc4e2b05..fbd265a3 100644 --- a/src/demod/DemodulatorThread.h +++ b/src/demod/DemodulatorThread.h @@ -67,6 +67,8 @@ class DemodulatorThread : public IOThread { DemodulatorThreadOutputQueuePtr audioVisOutputQueue = nullptr; DemodulatorThreadControlCommandQueuePtr threadQueueControl = nullptr; + DemodulatorThreadOutputQueuePtr audioSinkOutputQueue = nullptr; + //protects the audioVisOutputQueue dynamic binding change at runtime (in DemodulatorMgr) std::mutex m_mutexAudioVisOutputQueue; }; From 38fab6ac5197c99a9ede9877552c54d7fb055cf4 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Thu, 12 Oct 2017 00:07:05 -0400 Subject: [PATCH 04/22] Add 'R' toggle key + fixes from initial testing --- src/AppConfig.cpp | 2 +- src/AppFrame.cpp | 6 ++++++ src/BookmarkMgr.cpp | 6 +++++- src/CubicSDR.cpp | 8 ++++++++ src/CubicSDR.h | 3 +++ src/demod/DemodulatorMgr.cpp | 1 - src/demod/DemodulatorThread.cpp | 2 +- src/visual/PrimaryGLContext.cpp | 24 ++++++++++++++++++------ 8 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp index fc754401..4fbff527 100644 --- a/src/AppConfig.cpp +++ b/src/AppConfig.cpp @@ -756,7 +756,7 @@ bool AppConfig::load() { DataNode *rec_node = cfg.rootNode()->getNext("recording"); if (rec_node->hasAnother("path")) { - DataNode *rec_path = cfg.rootNode()->getNext("path"); + DataNode *rec_path = rec_node->getNext("path"); recordingPath = rec_path->element()->toString(); } } diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 4617a91e..b7b71d4e 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -2502,6 +2502,7 @@ int AppFrame::OnGlobalKeyDown(wxKeyEvent &event) { case 'S': case 'P': case 'M': + case 'R': return 1; case '0': case '1': @@ -2642,6 +2643,11 @@ int AppFrame::OnGlobalKeyUp(wxKeyEvent &event) { wxGetApp().setSoloMode(!wxGetApp().getSoloMode()); return 1; break; + case 'R': + if (activeDemod) { + activeDemod->setRecording(!activeDemod->isRecording()); + } + break; case 'P': wxGetApp().getSpectrumProcessor()->setPeakHold(!wxGetApp().getSpectrumProcessor()->getPeakHold()); if (wxGetApp().getDemodSpectrumProcessor()) { diff --git a/src/BookmarkMgr.cpp b/src/BookmarkMgr.cpp index fb9e58e1..69c234e8 100644 --- a/src/BookmarkMgr.cpp +++ b/src/BookmarkMgr.cpp @@ -360,7 +360,11 @@ bool BookmarkMgr::getExpandState(std::string groupName) { void BookmarkMgr::updateActiveList() { - std::lock_guard < std::recursive_mutex > lockData(busy_lock); + std::lock_guard < std::recursive_mutex > lockData(busy_lock); + + if (wxGetApp().isShuttingDown()) { + return; + } BookmarkView *bmv = wxGetApp().getAppFrame()->getBookmarkView(); diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp index f4cb6aab..898f53f2 100644 --- a/src/CubicSDR.cpp +++ b/src/CubicSDR.cpp @@ -203,6 +203,7 @@ CubicSDR::CubicSDR() : frequency(0), offset(0), ppm(0), snap(1), sampleRate(DEFA sampleRateInitialized.store(false); agcMode.store(true); soloMode.store(false); + shuttingDown.store(false); fdlgTarget = FrequencyDialog::FDIALOG_TARGET_DEFAULT; stoppedDev = nullptr; } @@ -384,6 +385,8 @@ bool CubicSDR::OnInit() { } int CubicSDR::OnExit() { + shuttingDown.store(true); + #if USE_HAMLIB if (rigIsActive()) { std::cout << "Terminating Rig thread.." << std::endl << std::flush; @@ -1025,6 +1028,11 @@ bool CubicSDR::getSoloMode() { return soloMode.load(); } +bool CubicSDR::isShuttingDown() +{ + return shuttingDown.load(); +} + int CubicSDR::FilterEvent(wxEvent& event) { if (!appframe) { return -1; diff --git a/src/CubicSDR.h b/src/CubicSDR.h index c1675092..71dd1a24 100644 --- a/src/CubicSDR.h +++ b/src/CubicSDR.h @@ -170,6 +170,8 @@ class CubicSDR: public wxApp { void setSoloMode(bool solo); bool getSoloMode(); + + bool isShuttingDown(); #ifdef USE_HAMLIB RigThread *getRigThread(); @@ -195,6 +197,7 @@ class CubicSDR: public wxApp { std::atomic_llong sampleRate; std::string antennaName; std::atomic_bool agcMode; + std::atomic_bool shuttingDown; SDRThread *sdrThread = nullptr; SDREnumerator *sdrEnum = nullptr; diff --git a/src/demod/DemodulatorMgr.cpp b/src/demod/DemodulatorMgr.cpp index 9b449c64..442b6a63 100644 --- a/src/demod/DemodulatorMgr.cpp +++ b/src/demod/DemodulatorMgr.cpp @@ -58,7 +58,6 @@ void DemodulatorMgr::terminateAll() { DemodulatorInstancePtr d = demods.back(); demods.pop_back(); - wxGetApp().removeDemodulator(d); deleteThread(d); } } diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 9b9d5c98..8e243c06 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -44,7 +44,7 @@ void DemodulatorThread::onBindOutput(std::string name, ThreadQueueBasePtr thread audioVisOutputQueue = std::static_pointer_cast(threadQueue); } - if (name == "AudioSinkOutput") { + if (name == "AudioSink") { std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); audioSinkOutputQueue = std::static_pointer_cast(threadQueue); diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index 5211d5e3..15030e06 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -104,17 +104,25 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstancePtr demod, RGBA4f color, glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); bool soloMode = wxGetApp().getSoloMode(); + bool isRecording = demod->isRecording(); bool isSolo = soloMode && demod == wxGetApp().getDemodMgr().getLastActiveDemodulator(); + RGBA4f labelBg(0, 0, 0, 0.35f); + if (isSolo) { - glColor4f(0.8f, 0.8f, 0, 0.35f); + labelBg.r = labelBg.g = 0.8f; } else if (demod->isMuted()) { - glColor4f(0.8f, 0, 0, 0.35f); + labelBg.r = 0.8f; } else if (soloMode) { - glColor4f(0.2f, 0, 0, 0.35f); - } else { - glColor4f(0, 0, 0, 0.35f); + labelBg.r = 0.2f; } + + // TODO: Better recording indicator... pulsating red circle? + if (isRecording) { + labelBg.g = 1.0f; + } + + glColor4f(labelBg.r, labelBg.g, labelBg.b, labelBg.a); glBegin(GL_QUADS); glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0); @@ -167,7 +175,11 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstancePtr demod, RGBA4f color, if (demod->isDeltaLock()) { demodLabel.append(" [V]"); } - + + if (isRecording) { + demodLabel.append(" [R]"); + } + if (demod->getDemodulatorType() == "USB") { GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true); } else if (demod->getDemodulatorType() == "LSB") { From 8f31fd9f5b526940b91045d1c64b19f895ddcd8e Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Thu, 2 Nov 2017 00:01:10 -0400 Subject: [PATCH 05/22] Recording is now writing playable .WAV files --- src/audio/AudioFileWAV.cpp | 91 ++++++++++++++++++++++++++++++++++++-- src/audio/AudioFileWAV.h | 2 +- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp index afe52242..9595baab 100644 --- a/src/audio/AudioFileWAV.cpp +++ b/src/audio/AudioFileWAV.cpp @@ -2,7 +2,49 @@ // SPDX-License-Identifier: GPL-2.0+ #include "AudioFileWAV.h" -// #include "WavFileFormatHandlerStuff.h" + + +// Simple endian io read/write handling from +// http://www.cplusplus.com/forum/beginner/31584/#msg171056 +namespace little_endian_io +{ + template + std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) { + for (; size; --size, value >>= 8) { + outs.put(static_cast (value & 0xFF)); + } + return outs; + } + + template + std::istream& read_word(std::istream& ins, Word& value, unsigned size = sizeof(Word)) { + for (unsigned n = 0, value = 0; n < size; ++n) { + value |= ins.get() << (8 * n); + } + return ins; + } +} + +namespace big_endian_io +{ + template + std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) { + while (size) { + outs.put(static_cast ((value >> (8 * --size)) & 0xFF)); + } + return outs; + } + + template + std::istream& read_word(std::istream& ins, Word& value, unsigned size = sizeof(Word)) { + for (value = 0; size; --size) { + value = (value << 8) | ins.get(); + } + return ins; + } +} + +using namespace little_endian_io; AudioFileWAV::AudioFileWAV() : AudioFile() { } @@ -27,12 +69,43 @@ bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) suffix = ""; std::string ofName = getOutputFileName(); - // Check if file exists, sequence the suffix? + // TODO: Check if file exists, sequence the suffix + outputFileStream.open(ofName.c_str(), std::ios::binary); + + // Based on simple wav file output code from + // http://www.cplusplus.com/forum/beginner/166954/ - outputFileStream.open(ofName.c_str(), std::ios::out | std::ios::binary); + // Write the wav file headers + outputFileStream << "RIFF----WAVEfmt "; // (chunk size to be filled in later) + write_word(outputFileStream, 16, 4); // no extension data + write_word(outputFileStream, 1, 2); // PCM - integer samples + write_word(outputFileStream, input->channels, 2); // channels + write_word(outputFileStream, input->sampleRate, 4); // samples per second (Hz) + write_word(outputFileStream, (input->sampleRate * 16 * input->channels) / 8, 4); // (Sample Rate * BitsPerSample * Channels) / 8 + write_word(outputFileStream, input->channels * 2, 2); // data block size (size of integer samples, one for each channel, in bytes) + write_word(outputFileStream, 16, 2); // number of bits per sample (use a multiple of 8) + + // Write the data chunk header + dataChunkPos = outputFileStream.tellp(); + outputFileStream << "data----"; // (chunk size to be filled in later) } - // write input data to wav file + // Prevent clipping + float intScale = (input->peak < 1.0) ? 32767.0f : (32767.0f / input->peak); + + if (input->channels == 1) { + for (int i = 0, iMax = input->data.size(); i < iMax; i++) { + write_word(outputFileStream, int(input->data[i] * intScale), 2); + } + } + else if (input->channels == 2) { + for (int i = 0, iMax = input->data.size() / 2; i < iMax; i++) { + write_word(outputFileStream, int(input->data[i * 2] * intScale), 2); + write_word(outputFileStream, int(input->data[i * 2 + 1] * intScale), 2); + } + } + + // TODO: Periodically update the RIFF/data chunk size in case of crash? return true; } @@ -40,6 +113,16 @@ bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) bool AudioFileWAV::closeFile() { if (outputFileStream.is_open()) { + size_t file_length = outputFileStream.tellp(); + + // Fix the data chunk header to contain the data size + outputFileStream.seekp(dataChunkPos + 4); + write_word(outputFileStream, file_length - dataChunkPos + 8); + + // Fix the file header to contain the proper RIFF chunk size, which is (file size - 8) bytes + outputFileStream.seekp(0 + 4); + write_word(outputFileStream, file_length - 8, 4); + outputFileStream.close(); } diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h index 2787ac34..b162b251 100644 --- a/src/audio/AudioFileWAV.h +++ b/src/audio/AudioFileWAV.h @@ -22,5 +22,5 @@ class AudioFileWAV : public AudioFile { protected: std::ofstream outputFileStream; std::string suffix; - + size_t dataChunkPos; }; \ No newline at end of file From 326a993a29fce45c20034f43bf502321bd05fa8e Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Thu, 16 Nov 2017 23:53:30 -0500 Subject: [PATCH 06/22] Sequence recording on format change; user label as filename when available --- src/audio/AudioFile.cpp | 22 ++++++++++++++++++++-- src/audio/AudioFile.h | 1 - src/audio/AudioFileWAV.cpp | 11 ++--------- src/audio/AudioFileWAV.h | 2 -- src/demod/DemodulatorInstance.cpp | 18 +++++++++++++++++- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/audio/AudioFile.cpp b/src/audio/AudioFile.cpp index 063d6dd7..f3e1a34c 100644 --- a/src/audio/AudioFile.cpp +++ b/src/audio/AudioFile.cpp @@ -19,9 +19,27 @@ void AudioFile::setOutputFileName(std::string filename) { std::string AudioFile::getOutputFileName() { std::string recPath = wxGetApp().getConfig()->getRecordingPath(); - // TODO: Handle invalid chars, etc.. + // Strip any invalid characters from the name + std::string stripChars("<>:\"/\\|?*"); std::string filenameBaseSafe = filenameBase; - return recPath + filePathSeparator + filenameBaseSafe + getSuffix() + "." + getExtension(); + for (size_t i = 0, iMax = filenameBaseSafe.length(); i < iMax; i++) { + if (stripChars.find(filenameBaseSafe[i]) != std::string::npos) { + filenameBaseSafe.replace(i,1,"_"); + } + } + + // Create output file name + std::string outputFileName = recPath + filePathSeparator + filenameBaseSafe + "." + getExtension(); + + int idx = 0; + + // If the file exists; then find the next non-existing file in sequence. + while (FILE *file = fopen(outputFileName.c_str(), "r")) { + fclose(file); + outputFileName = recPath + filePathSeparator + filenameBaseSafe + "-" + std::to_string(++idx) + "." + getExtension(); + } + + return outputFileName; } diff --git a/src/audio/AudioFile.h b/src/audio/AudioFile.h index a982e4f1..c8636b84 100644 --- a/src/audio/AudioFile.h +++ b/src/audio/AudioFile.h @@ -14,7 +14,6 @@ class AudioFile virtual void setOutputFileName(std::string filename); virtual std::string getExtension() = 0; - virtual std::string getSuffix() = 0; virtual std::string getOutputFileName(); virtual bool writeToFile(AudioThreadInputPtr input) = 0; diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp index 9595baab..a8d5fd2a 100644 --- a/src/audio/AudioFileWAV.cpp +++ b/src/audio/AudioFileWAV.cpp @@ -58,18 +58,11 @@ std::string AudioFileWAV::getExtension() return "wav"; } -std::string AudioFileWAV::getSuffix() -{ - return suffix; -} - bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) { if (!outputFileStream.is_open()) { - suffix = ""; std::string ofName = getOutputFileName(); - // TODO: Check if file exists, sequence the suffix outputFileStream.open(ofName.c_str(), std::ios::binary); // Based on simple wav file output code from @@ -94,12 +87,12 @@ bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) float intScale = (input->peak < 1.0) ? 32767.0f : (32767.0f / input->peak); if (input->channels == 1) { - for (int i = 0, iMax = input->data.size(); i < iMax; i++) { + for (size_t i = 0, iMax = input->data.size(); i < iMax; i++) { write_word(outputFileStream, int(input->data[i] * intScale), 2); } } else if (input->channels == 2) { - for (int i = 0, iMax = input->data.size() / 2; i < iMax; i++) { + for (size_t i = 0, iMax = input->data.size() / 2; i < iMax; i++) { write_word(outputFileStream, int(input->data[i * 2] * intScale), 2); write_word(outputFileStream, int(input->data[i * 2 + 1] * intScale), 2); } diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h index b162b251..d64f7640 100644 --- a/src/audio/AudioFileWAV.h +++ b/src/audio/AudioFileWAV.h @@ -14,13 +14,11 @@ class AudioFileWAV : public AudioFile { ~AudioFileWAV(); std::string getExtension(); - std::string getSuffix(); bool writeToFile(AudioThreadInputPtr input); bool closeFile(); protected: std::ofstream outputFileStream; - std::string suffix; size_t dataChunkPos; }; \ No newline at end of file diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index f63d1969..05d99e5a 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "DemodulatorInstance.h" #include "CubicSDR.h" @@ -627,8 +628,23 @@ void DemodulatorInstance::startRecording() { AudioSinkFileThread *newSinkThread = new AudioSinkFileThread(); AudioFileWAV *afHandler = new AudioFileWAV(); + time_t t = std::time(nullptr); + tm ltm = *std::localtime(&t); + std::stringstream fileName; - fileName << getLabel() << "_" << std::time(nullptr); + + std::wstring userLabel = getDemodulatorUserLabel(); + + // TODO: Can we support wstring filenames for user labels? + std::string userLabelStr(userLabel.begin(), userLabel.end()); + + if (!userLabelStr.empty()) { + fileName << userLabelStr; + } else { + fileName << getLabel(); + } + + fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); afHandler->setOutputFileName(fileName.str()); newSinkThread->setAudioFileHandler(afHandler); From b9e4f6aeba867fbeb60c3ac59fd2d5f4b20a26b3 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 4 Dec 2017 22:44:47 -0500 Subject: [PATCH 07/22] Recording path notify, bookmark panel record buttons, tweaks and adjustments --- src/AppConfig.cpp | 21 ++++++++++++++++++ src/AppConfig.h | 2 ++ src/AppFrame.cpp | 17 ++++++++++++--- src/AppFrame.h | 2 ++ src/audio/AudioSinkThread.cpp | 1 + src/audio/AudioThread.h | 2 +- src/demod/DemodulatorThread.cpp | 13 +++++------ src/forms/Bookmark/BookmarkView.cpp | 34 ++++++++++++++++++++++++++++- src/forms/Bookmark/BookmarkView.h | 2 ++ src/visual/WaterfallCanvas.cpp | 4 ++-- 10 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp index 4fbff527..dc68da53 100644 --- a/src/AppConfig.cpp +++ b/src/AppConfig.cpp @@ -4,6 +4,8 @@ #include "AppConfig.h" #include "CubicSDR.h" +#include + DeviceConfig::DeviceConfig() : deviceId("") { ppm.store(0); offset.store(0); @@ -513,6 +515,25 @@ std::string AppConfig::getRecordingPath() { return recordingPath; } +bool AppConfig::verifyRecordingPath() { + string recPathStr = wxGetApp().getConfig()->getRecordingPath(); + + if (recPathStr.empty()) { + wxMessageBox( wxT("Recording path is not set. Please use 'Set Recording Path' from the 'File' Menu."), wxT("Recording Path Error"), wxICON_INFORMATION); + + return false; + } + + wxFileName recPath(recPathStr); + + if (!recPath.Exists() || !recPath.IsDirWritable()) { + wxMessageBox( wxT("Recording path does not exist or is not writable. Please use 'Set Recording Path' from the 'File' Menu."), wxT("Recording Path Error"), wxICON_INFORMATION); + + return false; + } + + return true; +} void AppConfig::setConfigName(std::string configName) { this->configName = configName; diff --git a/src/AppConfig.h b/src/AppConfig.h index d620ee90..51212726 100644 --- a/src/AppConfig.h +++ b/src/AppConfig.h @@ -141,6 +141,8 @@ class AppConfig { void setRecordingPath(std::string recPath); std::string getRecordingPath(); + bool verifyRecordingPath(); + #if USE_HAMLIB int getRigModel(); void setRigModel(int rigModel); diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 5e24b27c..c0c0177c 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -2643,9 +2643,7 @@ int AppFrame::OnGlobalKeyUp(wxKeyEvent &event) { return 1; break; case 'R': - if (activeDemod) { - activeDemod->setRecording(!activeDemod->isRecording()); - } + toggleActiveDemodRecording(); break; case 'P': wxGetApp().getSpectrumProcessor()->setPeakHold(!wxGetApp().getSpectrumProcessor()->getPeakHold()); @@ -2691,6 +2689,19 @@ int AppFrame::OnGlobalKeyUp(wxKeyEvent &event) { return 1; } +void AppFrame::toggleActiveDemodRecording() { + if (!wxGetApp().getConfig()->verifyRecordingPath()) { + return; + } + + DemodulatorInstancePtr activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator(); + + if (activeDemod) { + activeDemod->setRecording(!activeDemod->isRecording()); + wxGetApp().getBookmarkMgr().updateActiveList(); + } +} + void AppFrame::setWaterfallLinesPerSecond(int lps) { waterfallSpeedMeter->setUserInputValue(sqrt(lps)); diff --git a/src/AppFrame.h b/src/AppFrame.h index fcee6e5b..8d64fcdb 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -115,6 +115,8 @@ class AppFrame: public wxFrame { int OnGlobalKeyDown(wxKeyEvent &event); int OnGlobalKeyUp(wxKeyEvent &event); + void toggleActiveDemodRecording(); + void setWaterfallLinesPerSecond(int lps); void setSpectrumAvgSpeed(double avg); diff --git a/src/audio/AudioSinkThread.cpp b/src/audio/AudioSinkThread.cpp index 11e9009d..edf31529 100644 --- a/src/audio/AudioSinkThread.cpp +++ b/src/audio/AudioSinkThread.cpp @@ -7,6 +7,7 @@ AudioSinkThread::AudioSinkThread() { inputQueuePtr = std::make_shared(); + inputQueuePtr->set_max_num_items(1000); setInputQueue("input", inputQueuePtr); } diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index d648895a..d37593b3 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -24,7 +24,7 @@ class AudioThreadInput { std::vector data; AudioThreadInput() : - frequency(0), sampleRate(0), inputRate(0), channels(0), peak(0), type(0) { + frequency(0), inputRate(0), sampleRate(0), channels(0), peak(0), type(0) { } diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 8e243c06..0afb8637 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -331,12 +331,11 @@ void DemodulatorThread::run() { std::cout << "DemodulatorThread::run() cannot push ati into audioOutputQueue, is full !" << std::endl; std::this_thread::yield(); } - - if (localAudioSinkOutputQueue != nullptr) { - if (!audioSinkOutputQueue->try_push(ati)) { - std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; - std::this_thread::yield(); - } + } + + if (localAudioSinkOutputQueue != nullptr) { + if (!audioSinkOutputQueue->try_push(ati)) { + std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; } } } @@ -418,4 +417,4 @@ void DemodulatorThread::releaseSquelchLock(DemodulatorInstance* inst) { if (inst == nullptr || squelchLock == inst) { squelchLock = nullptr; } -} \ No newline at end of file +} diff --git a/src/forms/Bookmark/BookmarkView.cpp b/src/forms/Bookmark/BookmarkView.cpp index 4cb9888b..7fabbb49 100644 --- a/src/forms/Bookmark/BookmarkView.cpp +++ b/src/forms/Bookmark/BookmarkView.cpp @@ -816,8 +816,16 @@ void BookmarkView::activeSelection(DemodulatorInstancePtr dsel) { clearButtons(); addBookmarkChoice(m_buttonPanel); - addButton(m_buttonPanel, "Remove Active", wxCommandEventHandler( BookmarkView::onRemoveActive )); + + if (dsel->isActive() && !(dsel->isRecording())) { + addButton(m_buttonPanel, "Start Recording", wxCommandEventHandler( BookmarkView::onStartRecording )); + } else { + addButton(m_buttonPanel, "Stop Recording", wxCommandEventHandler( BookmarkView::onStopRecording )); + } + addButton(m_buttonPanel, "Remove Active", wxCommandEventHandler( BookmarkView::onRemoveActive )); + + showProps(); showButtons(); refreshLayout(); @@ -1149,6 +1157,30 @@ void BookmarkView::onRemoveActive( wxCommandEvent& /* event */ ) { } } +void BookmarkView::onStartRecording( wxCommandEvent& /* event */ ) { + TreeViewItem *curSel = itemToTVI(m_treeView->GetSelection()); + + if (curSel && curSel->type == TreeViewItem::TREEVIEW_ITEM_TYPE_ACTIVE) { + if (!curSel->demod->isRecording() && wxGetApp().getConfig()->verifyRecordingPath()) { + curSel->demod->setRecording(true); + wxGetApp().getBookmarkMgr().updateActiveList(); + } + } +} + + +void BookmarkView::onStopRecording( wxCommandEvent& /* event */ ) { + TreeViewItem *curSel = itemToTVI(m_treeView->GetSelection()); + + if (curSel && curSel->type == TreeViewItem::TREEVIEW_ITEM_TYPE_ACTIVE) { + if (curSel->demod->isRecording()) { + curSel->demod->setRecording(false); + wxGetApp().getBookmarkMgr().updateActiveList(); + } + } +} + + void BookmarkView::onRemoveBookmark( wxCommandEvent& /* event */ ) { if (editingLabel) { diff --git a/src/forms/Bookmark/BookmarkView.h b/src/forms/Bookmark/BookmarkView.h index 8ed1bffa..820ece19 100644 --- a/src/forms/Bookmark/BookmarkView.h +++ b/src/forms/Bookmark/BookmarkView.h @@ -145,6 +145,8 @@ class BookmarkView : public BookmarkPanel { void onBookmarkChoice( wxCommandEvent &event ); void onRemoveActive( wxCommandEvent& event ); + void onStartRecording( wxCommandEvent& event ); + void onStopRecording( wxCommandEvent& event ); void onRemoveBookmark( wxCommandEvent& event ); void onActivateBookmark( wxCommandEvent& event ); diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index a951a1f8..03760abc 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -565,14 +565,14 @@ void WaterfallCanvas::updateHoverState() { mouseTracker.setVertDragLock(true); mouseTracker.setHorizDragLock(false); - setStatusText("Click and drag to change demodulator bandwidth. SPACE or numeric key for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label."); + setStatusText("Click and drag to change demodulator bandwidth. SPACE or numeric key for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record."); } else { SetCursor(wxCURSOR_SIZING); nextDragState = WF_DRAG_FREQUENCY; mouseTracker.setVertDragLock(true); mouseTracker.setHorizDragLock(false); - setStatusText("Click and drag to change demodulator frequency; SPACE or numeric key for direct input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label."); + setStatusText("Click and drag to change demodulator frequency; SPACE or numeric key for direct input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record."); } } else { From 5c45c1cf6b00d7468f5f8a43ea9c6e10de7335dd Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Fri, 29 Dec 2017 22:46:39 -0500 Subject: [PATCH 08/22] Record muted, toggle-all recording /w shift-R, status texts --- src/AppFrame.cpp | 32 ++++++++++++++++++++++++++++++-- src/AppFrame.h | 1 + src/demod/DemodulatorThread.cpp | 2 +- src/visual/PrimaryGLContext.cpp | 3 ++- src/visual/WaterfallCanvas.cpp | 8 ++++---- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index c0c0177c..75989d28 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -2643,7 +2643,11 @@ int AppFrame::OnGlobalKeyUp(wxKeyEvent &event) { return 1; break; case 'R': - toggleActiveDemodRecording(); + if (event.ShiftDown()) { + toggleAllActiveDemodRecording(); + } else { + toggleActiveDemodRecording(); + } break; case 'P': wxGetApp().getSpectrumProcessor()->setPeakHold(!wxGetApp().getSpectrumProcessor()->getPeakHold()); @@ -2701,7 +2705,31 @@ void AppFrame::toggleActiveDemodRecording() { wxGetApp().getBookmarkMgr().updateActiveList(); } } - + +void AppFrame::toggleAllActiveDemodRecording() { + if (!wxGetApp().getConfig()->verifyRecordingPath()) { + return; + } + + auto activeDemods = wxGetApp().getDemodMgr().getDemodulators(); + + bool stateToSet = true; + + for (auto i : activeDemods) { + if (i->isActive() && i->isRecording()) { + stateToSet = false; + break; + } + } + + for (auto i : activeDemods) { + if (i->isActive() && i->isRecording() != stateToSet) { + i->setRecording(stateToSet); + } + } +} + + void AppFrame::setWaterfallLinesPerSecond(int lps) { waterfallSpeedMeter->setUserInputValue(sqrt(lps)); diff --git a/src/AppFrame.h b/src/AppFrame.h index 8d64fcdb..80336850 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -116,6 +116,7 @@ class AppFrame: public wxFrame { int OnGlobalKeyUp(wxKeyEvent &event); void toggleActiveDemodRecording(); + void toggleAllActiveDemodRecording(); void setWaterfallLinesPerSecond(int lps); void setSpectrumAvgSpeed(double avg); diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 0afb8637..f3f36cf6 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -201,7 +201,7 @@ void DemodulatorThread::run() { signalLevel = signalLevel + (currentSignalLevel - signalLevel) * 0.05 * sampleTime * 30.0; } - bool squelched = (muted.load() || (squelchEnabled && (signalLevel < squelchLevel))); + bool squelched = squelchEnabled && (signalLevel < squelchLevel); if (squelchEnabled) { if (!squelched && !squelchBreak) { diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index 15030e06..a21dc781 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -119,7 +119,8 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstancePtr demod, RGBA4f color, // TODO: Better recording indicator... pulsating red circle? if (isRecording) { - labelBg.g = 1.0f; + auto t = std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + labelBg.g = sinf(2.0f * M_PI * (float(t) / 1000.0f)) * 0.25f + 0.75f; } glColor4f(labelBg.r, labelBg.g, labelBg.b, labelBg.a); diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index 03760abc..8efd1e58 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -565,25 +565,25 @@ void WaterfallCanvas::updateHoverState() { mouseTracker.setVertDragLock(true); mouseTracker.setHorizDragLock(false); - setStatusText("Click and drag to change demodulator bandwidth. SPACE or numeric key for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record."); + setStatusText("Drag to change bandwidth. SPACE or 0-9 for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record."); } else { SetCursor(wxCURSOR_SIZING); nextDragState = WF_DRAG_FREQUENCY; mouseTracker.setVertDragLock(true); mouseTracker.setHorizDragLock(false); - setStatusText("Click and drag to change demodulator frequency; SPACE or numeric key for direct input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record."); + setStatusText("Drag to change frequency; SPACE or 0-9 for direct input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record."); } } else { SetCursor(wxCURSOR_CROSS); nextDragState = WF_DRAG_NONE; if (shiftDown) { - setStatusText("Click to create a new demodulator or hold ALT to drag range, SPACE or numeric key for direct center frequency input."); + setStatusText("Click to create a new demodulator or hold ALT to drag new range."); } else { setStatusText( - "Click to set active demodulator frequency or hold ALT to drag range; hold SHIFT to create new. Right drag or wheel to Zoom. Arrow keys to navigate/zoom, C to center."); + "Click to set demodulator frequency or hold ALT to drag range; hold SHIFT to create new. Right drag or wheel to Zoom. Arrow keys to navigate/zoom, C to center. Shift-R record/stop all."); } } } From e6cd2ae77449c0038888c7b0a16004591ca23e41 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Sun, 31 Dec 2017 20:59:06 -0500 Subject: [PATCH 09/22] AppImage fix - put_time req. GCC 5+ --- src/demod/DemodulatorInstance.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 05d99e5a..92a4f4c1 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -644,8 +644,14 @@ void DemodulatorInstance::startRecording() { fileName << getLabel(); } - fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); - + // GCC 5+ + // fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); + + char timeStr[512]; + strftime(timeStr, sizeof(timeStr), "%d-%m-%Y_%H-%M-%S", <m); + fileName << "_" << timeStr; + + afHandler->setOutputFileName(fileName.str()); newSinkThread->setAudioFileHandler(afHandler); From c3257536cf98e5e711029076ed0d0fcf4337f890 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Sun, 31 Dec 2017 21:14:47 -0500 Subject: [PATCH 10/22] Version bump --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aedd5f2f..3337b7d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") SET(CUBICSDR_VERSION_MAJOR "0") SET(CUBICSDR_VERSION_MINOR "2") -SET(CUBICSDR_VERSION_PATCH "2") +SET(CUBICSDR_VERSION_PATCH "3") SET(CUBICSDR_VERSION_SUFFIX "") SET(CUBICSDR_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}${CUBICSDR_VERSION_SUFFIX}") From f625cb6eea1bf290f7829a6f64b1467557cd2ba0 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 1 Jan 2018 22:12:04 -0500 Subject: [PATCH 11/22] Donation list update --- src/forms/Dialog/AboutDialog.fbp | 332 +++++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) diff --git a/src/forms/Dialog/AboutDialog.fbp b/src/forms/Dialog/AboutDialog.fbp index 9f9cb8ad..c3534f10 100644 --- a/src/forms/Dialog/AboutDialog.fbp +++ b/src/forms/Dialog/AboutDialog.fbp @@ -6534,6 +6534,338 @@ + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Chad Myslinsky + + 0 + + + 0 + + 1 + m_dChadMyslinsky + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Charlie Bruckner + + 0 + + + 0 + + 1 + m_dCharlieBruckner + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Jordan Parker + + 0 + + + 0 + + 1 + m_dJordanParker + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Robert Chave + + 0 + + + 0 + + 1 + m_dRobertChave + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + From 8609667f3383eb74676ff49d833a09cb5c443796 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 2 Jan 2018 00:13:09 -0500 Subject: [PATCH 12/22] About Dialog code update, passive vcredist install --- CMakeLists.txt | 2 +- src/forms/Dialog/AboutDialogBase.cpp | 926 ++++++++++++++------------- src/forms/Dialog/AboutDialogBase.h | 220 +++---- 3 files changed, 584 insertions(+), 564 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3337b7d9..d82e51a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1003,7 +1003,7 @@ IF (WIN32 AND BUILD_INSTALLER) IF (MSVC) install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/external/msvc/${EX_PLATFORM_NAME}/vc_redist.${EX_PLATFORM_NAME}.exe DESTINATION vc_redist) - set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\vc_redist\\\\vc_redist.${EX_PLATFORM_NAME}.exe\\\" /q:a'") + set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\vc_redist\\\\vc_redist.${EX_PLATFORM_NAME}.exe\\\" /passive /norestart'") ENDIF (MSVC) diff --git a/src/forms/Dialog/AboutDialogBase.cpp b/src/forms/Dialog/AboutDialogBase.cpp index 84bb0035..8ab39df1 100644 --- a/src/forms/Dialog/AboutDialogBase.cpp +++ b/src/forms/Dialog/AboutDialogBase.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 27 2017) +// C++ code generated with wxFormBuilder (version Nov 6 2017) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -9,475 +9,491 @@ /////////////////////////////////////////////////////////////////////////// -AboutDialogBase::AboutDialogBase(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxDialog(parent, id, title, pos, size, style) +AboutDialogBase::AboutDialogBase( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { - this->SetSizeHints(wxDefaultSize, wxDefaultSize); - + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + wxBoxSizer* dlgSizer; - dlgSizer = new wxBoxSizer(wxVERTICAL); - - m_hPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + dlgSizer = new wxBoxSizer( wxVERTICAL ); + + m_hPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxBoxSizer* m_hSizer; - m_hSizer = new wxBoxSizer(wxHORIZONTAL); - - m_appName = new wxStaticText(m_hPanel, wxID_ANY, wxT("CubicSDR"), wxDefaultPosition, wxDefaultSize, 0); - m_appName->Wrap(-1); - m_appName->SetFont(wxFont(20, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_hSizer->Add(m_appName, 0, wxALL, 6); - - - m_hPanel->SetSizer(m_hSizer); + m_hSizer = new wxBoxSizer( wxHORIZONTAL ); + + m_appName = new wxStaticText( m_hPanel, wxID_ANY, wxT("CubicSDR"), wxDefaultPosition, wxDefaultSize, 0 ); + m_appName->Wrap( -1 ); + m_appName->SetFont( wxFont( 20, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_hSizer->Add( m_appName, 0, wxALL, 6 ); + + + m_hPanel->SetSizer( m_hSizer ); m_hPanel->Layout(); - m_hSizer->Fit(m_hPanel); - dlgSizer->Add(m_hPanel, 0, wxALL | wxEXPAND, 5); - - m_aboutNotebook = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); - m_dbScroll = new wxScrolledWindow(m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL); - m_dbScroll->SetScrollRate(5, 5); + m_hSizer->Fit( m_hPanel ); + dlgSizer->Add( m_hPanel, 0, wxALL|wxEXPAND, 5 ); + + m_aboutNotebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_dbScroll = new wxScrolledWindow( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_dbScroll->SetScrollRate( 5, 5 ); wxBoxSizer* m_dbPane; - m_dbPane = new wxBoxSizer(wxVERTICAL); - + m_dbPane = new wxBoxSizer( wxVERTICAL ); + wxFlexGridSizer* m_dbSizer; - m_dbSizer = new wxFlexGridSizer(0, 3, 2, 20); - m_dbSizer->SetFlexibleDirection(wxBOTH); - m_dbSizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); - - m_dbHeader = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Developed By"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_dbHeader->Wrap(-1); - m_dbHeader->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_dbSizer->Add(m_dbHeader, 0, wxALL, 5); - - m_dbGHHeader = new wxStaticText(m_dbScroll, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0); - m_dbGHHeader->Wrap(-1); - m_dbGHHeader->SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_dbSizer->Add(m_dbGHHeader, 0, wxALL, 5); - - m_dbTwitter = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Twitter"), wxDefaultPosition, wxDefaultSize, 0); - m_dbTwitter->Wrap(-1); - m_dbTwitter->SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_dbSizer->Add(m_dbTwitter, 0, wxALL, 5); - - m_dbCharlesCliffe = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Charles J. Cliffe"), wxDefaultPosition, wxDefaultSize, 0); - m_dbCharlesCliffe->Wrap(-1); - m_dbSizer->Add(m_dbCharlesCliffe, 0, wxALL, 5); - - m_dbghCC = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@cjcliffe"), wxDefaultPosition, wxDefaultSize, 0); - m_dbghCC->Wrap(-1); - m_dbSizer->Add(m_dbghCC, 0, wxALL, 5); - - m_dbtCC = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@ccliffe"), wxDefaultPosition, wxDefaultSize, 0); - m_dbtCC->Wrap(-1); - m_dbSizer->Add(m_dbtCC, 0, wxALL, 5); - - m_dbVincentSonnier = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0); - m_dbVincentSonnier->Wrap(-1); - m_dbSizer->Add(m_dbVincentSonnier, 0, wxALL, 5); - - m_dbghVS = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@vsonnier"), wxDefaultPosition, wxDefaultSize, 0); - m_dbghVS->Wrap(-1); - m_dbSizer->Add(m_dbghVS, 0, wxALL, 5); - - m_dbtVS = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@VincentSonnier"), wxDefaultPosition, wxDefaultSize, 0); - m_dbtVS->Wrap(-1); - m_dbSizer->Add(m_dbtVS, 0, wxALL, 5); - - - m_dbPane->Add(m_dbSizer, 0, wxALL | wxEXPAND, 5); - - m_dbDivider1 = new wxStaticLine(m_dbScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_dbPane->Add(m_dbDivider1, 0, wxALL | wxEXPAND, 10); - + m_dbSizer = new wxFlexGridSizer( 0, 3, 2, 20 ); + m_dbSizer->SetFlexibleDirection( wxBOTH ); + m_dbSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL ); + + m_dbHeader = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Developed By"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_dbHeader->Wrap( -1 ); + m_dbHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_dbSizer->Add( m_dbHeader, 0, wxALL, 5 ); + + m_dbGHHeader = new wxStaticText( m_dbScroll, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbGHHeader->Wrap( -1 ); + m_dbGHHeader->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_dbSizer->Add( m_dbGHHeader, 0, wxALL, 5 ); + + m_dbTwitter = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Twitter"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbTwitter->Wrap( -1 ); + m_dbTwitter->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_dbSizer->Add( m_dbTwitter, 0, wxALL, 5 ); + + m_dbCharlesCliffe = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Charles J. Cliffe"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbCharlesCliffe->Wrap( -1 ); + m_dbSizer->Add( m_dbCharlesCliffe, 0, wxALL, 5 ); + + m_dbghCC = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@cjcliffe"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbghCC->Wrap( -1 ); + m_dbSizer->Add( m_dbghCC, 0, wxALL, 5 ); + + m_dbtCC = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@ccliffe"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbtCC->Wrap( -1 ); + m_dbSizer->Add( m_dbtCC, 0, wxALL, 5 ); + + m_dbVincentSonnier = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbVincentSonnier->Wrap( -1 ); + m_dbSizer->Add( m_dbVincentSonnier, 0, wxALL, 5 ); + + m_dbghVS = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@vsonnier"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbghVS->Wrap( -1 ); + m_dbSizer->Add( m_dbghVS, 0, wxALL, 5 ); + + m_dbtVS = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@VincentSonnier"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbtVS->Wrap( -1 ); + m_dbSizer->Add( m_dbtVS, 0, wxALL, 5 ); + + + m_dbPane->Add( m_dbSizer, 0, wxALL|wxEXPAND, 5 ); + + m_dbDivider1 = new wxStaticLine( m_dbScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_dbPane->Add( m_dbDivider1, 0, wxALL|wxEXPAND, 10 ); + wxFlexGridSizer* m_cSizer; - m_cSizer = new wxFlexGridSizer(0, 2, 2, 20); - m_cSizer->SetFlexibleDirection(wxBOTH); - m_cSizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); - - m_cContributorsHeader = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Contributors"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_cContributorsHeader->Wrap(-1); - m_cContributorsHeader->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_cSizer->Add(m_cContributorsHeader, 0, wxALL, 5); - - m_cGitHub = new wxStaticText(m_dbScroll, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0); - m_cGitHub->Wrap(-1); - m_cGitHub->SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_cSizer->Add(m_cGitHub, 0, wxALL, 5); - - m_cCorneLukken = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Corne Lukken"), wxDefaultPosition, wxDefaultSize, 0); - m_cCorneLukken->Wrap(-1); - m_cSizer->Add(m_cCorneLukken, 0, wxALL, 5); - - m_cghCL = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@Dantali0n"), wxDefaultPosition, wxDefaultSize, 0); - m_cghCL->Wrap(-1); - m_cSizer->Add(m_cghCL, 0, wxALL, 5); - - m_cStainislawPitucha = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Stanisław Pitucha"), wxDefaultPosition, wxDefaultSize, 0); - m_cStainislawPitucha->Wrap(-1); - m_cSizer->Add(m_cStainislawPitucha, 0, wxALL, 5); - - m_cghSP = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@viraptor"), wxDefaultPosition, wxDefaultSize, 0); - m_cghSP->Wrap(-1); - m_cSizer->Add(m_cghSP, 0, wxALL, 5); - - m_cghStefanTalpalaru = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Ștefan Talpalaru"), wxDefaultPosition, wxDefaultSize, 0); - m_cghStefanTalpalaru->Wrap(-1); - m_cSizer->Add(m_cghStefanTalpalaru, 0, wxALL, 5); - - m_cghST = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@stefantalpalaru"), wxDefaultPosition, wxDefaultSize, 0); - m_cghST->Wrap(-1); - m_cSizer->Add(m_cghST, 0, wxALL, 5); - - m_cCrisMotch = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Chris Motch"), wxDefaultPosition, wxDefaultSize, 0); - m_cCrisMotch->Wrap(-1); - m_cSizer->Add(m_cCrisMotch, 0, wxALL, 5); - - m_cghCM = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@bodrick"), wxDefaultPosition, wxDefaultSize, 0); - m_cghCM->Wrap(-1); - m_cSizer->Add(m_cghCM, 0, wxALL, 5); - - m_cMariuszRyndzionek = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Mariusz Ryndzionek"), wxDefaultPosition, wxDefaultSize, 0); - m_cMariuszRyndzionek->Wrap(-1); - m_cSizer->Add(m_cMariuszRyndzionek, 0, wxALL, 5); - - m_cghMR = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@mryndzionek"), wxDefaultPosition, wxDefaultSize, 0); - m_cghMR->Wrap(-1); - m_cSizer->Add(m_cghMR, 0, wxALL, 5); - - m_cJiangWei = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Jiang Wei"), wxDefaultPosition, wxDefaultSize, 0); - m_cJiangWei->Wrap(-1); - m_cSizer->Add(m_cJiangWei, 0, wxALL, 5); - - m_cghJW = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@jocover"), wxDefaultPosition, wxDefaultSize, 0); - m_cghJW->Wrap(-1); - m_cSizer->Add(m_cghJW, 0, wxALL, 5); - - m_cTomSwartz = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Tom Swartz"), wxDefaultPosition, wxDefaultSize, 0); - m_cTomSwartz->Wrap(-1); - m_cSizer->Add(m_cTomSwartz, 0, wxALL, 5); - - m_cghTS = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@tomswartz07"), wxDefaultPosition, wxDefaultSize, 0); - m_cghTS->Wrap(-1); - m_cSizer->Add(m_cghTS, 0, wxALL, 5); - - m_cInfinityCyberworks = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Infinity Cyberworks"), wxDefaultPosition, wxDefaultSize, 0); - m_cInfinityCyberworks->Wrap(-1); - m_cSizer->Add(m_cInfinityCyberworks, 0, wxALL, 5); - - m_cghIC = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@infinitycyberworks"), wxDefaultPosition, wxDefaultSize, 0); - m_cghIC->Wrap(-1); - m_cSizer->Add(m_cghIC, 0, wxALL, 5); - - - m_dbPane->Add(m_cSizer, 0, wxALL | wxEXPAND, 5); - - - m_dbScroll->SetSizer(m_dbPane); + m_cSizer = new wxFlexGridSizer( 0, 2, 2, 20 ); + m_cSizer->SetFlexibleDirection( wxBOTH ); + m_cSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL ); + + m_cContributorsHeader = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Contributors"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_cContributorsHeader->Wrap( -1 ); + m_cContributorsHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_cSizer->Add( m_cContributorsHeader, 0, wxALL, 5 ); + + m_cGitHub = new wxStaticText( m_dbScroll, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cGitHub->Wrap( -1 ); + m_cGitHub->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_cSizer->Add( m_cGitHub, 0, wxALL, 5 ); + + m_cCorneLukken = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Corne Lukken"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cCorneLukken->Wrap( -1 ); + m_cSizer->Add( m_cCorneLukken, 0, wxALL, 5 ); + + m_cghCL = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@Dantali0n"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghCL->Wrap( -1 ); + m_cSizer->Add( m_cghCL, 0, wxALL, 5 ); + + m_cStainislawPitucha = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Stanisław Pitucha"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cStainislawPitucha->Wrap( -1 ); + m_cSizer->Add( m_cStainislawPitucha, 0, wxALL, 5 ); + + m_cghSP = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@viraptor"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghSP->Wrap( -1 ); + m_cSizer->Add( m_cghSP, 0, wxALL, 5 ); + + m_cghStefanTalpalaru = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Ștefan Talpalaru"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghStefanTalpalaru->Wrap( -1 ); + m_cSizer->Add( m_cghStefanTalpalaru, 0, wxALL, 5 ); + + m_cghST = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@stefantalpalaru"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghST->Wrap( -1 ); + m_cSizer->Add( m_cghST, 0, wxALL, 5 ); + + m_cCrisMotch = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Chris Motch"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cCrisMotch->Wrap( -1 ); + m_cSizer->Add( m_cCrisMotch, 0, wxALL, 5 ); + + m_cghCM = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@bodrick"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghCM->Wrap( -1 ); + m_cSizer->Add( m_cghCM, 0, wxALL, 5 ); + + m_cMariuszRyndzionek = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Mariusz Ryndzionek"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cMariuszRyndzionek->Wrap( -1 ); + m_cSizer->Add( m_cMariuszRyndzionek, 0, wxALL, 5 ); + + m_cghMR = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@mryndzionek"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghMR->Wrap( -1 ); + m_cSizer->Add( m_cghMR, 0, wxALL, 5 ); + + m_cJiangWei = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Jiang Wei"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cJiangWei->Wrap( -1 ); + m_cSizer->Add( m_cJiangWei, 0, wxALL, 5 ); + + m_cghJW = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@jocover"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghJW->Wrap( -1 ); + m_cSizer->Add( m_cghJW, 0, wxALL, 5 ); + + m_cTomSwartz = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Tom Swartz"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cTomSwartz->Wrap( -1 ); + m_cSizer->Add( m_cTomSwartz, 0, wxALL, 5 ); + + m_cghTS = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@tomswartz07"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghTS->Wrap( -1 ); + m_cSizer->Add( m_cghTS, 0, wxALL, 5 ); + + m_cInfinityCyberworks = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Infinity Cyberworks"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cInfinityCyberworks->Wrap( -1 ); + m_cSizer->Add( m_cInfinityCyberworks, 0, wxALL, 5 ); + + m_cghIC = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@infinitycyberworks"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghIC->Wrap( -1 ); + m_cSizer->Add( m_cghIC, 0, wxALL, 5 ); + + + m_dbPane->Add( m_cSizer, 0, wxALL|wxEXPAND, 5 ); + + + m_dbScroll->SetSizer( m_dbPane ); m_dbScroll->Layout(); - m_dbPane->Fit(m_dbScroll); - m_aboutNotebook->AddPage(m_dbScroll, wxT("Developers"), false); - m_dScroll = new wxScrolledWindow(m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL); - m_dScroll->SetScrollRate(5, 5); + m_dbPane->Fit( m_dbScroll ); + m_aboutNotebook->AddPage( m_dbScroll, wxT("Developers"), true ); + m_dScroll = new wxScrolledWindow( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_dScroll->SetScrollRate( 5, 5 ); wxBoxSizer* m_dBSizer; - m_dBSizer = new wxBoxSizer(wxVERTICAL); - + m_dBSizer = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* m_dSizer; - m_dSizer = new wxBoxSizer(wxVERTICAL); - - m_dHeader = new wxStaticText(m_dScroll, wxID_ANY, wxT("Thanks to everyone who donated at cubicsdr.com!"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_dHeader->Wrap(-1); - m_dHeader->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_dSizer->Add(m_dHeader, 0, wxALL, 5); - - m_dDivider1 = new wxStaticLine(m_dScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_dSizer->Add(m_dDivider1, 0, wxEXPAND | wxALL, 5); - - m_dSDRplay = new wxStaticText(m_dScroll, wxID_ANY, wxT("SDRplay / sdrplay.com"), wxDefaultPosition, wxDefaultSize, 0); - m_dSDRplay->Wrap(-1); - m_dSizer->Add(m_dSDRplay, 0, wxALL, 5); - - m_dMichaelLadd = new wxStaticText(m_dScroll, wxID_ANY, wxT("Michael Ladd"), wxDefaultPosition, wxDefaultSize, 0); - m_dMichaelLadd->Wrap(-1); - m_dSizer->Add(m_dMichaelLadd, 0, wxALL, 5); - - m_dAutoMotiveTemplates = new wxStaticText(m_dScroll, wxID_ANY, wxT("Automotive Templates"), wxDefaultPosition, wxDefaultSize, 0); - m_dAutoMotiveTemplates->Wrap(-1); - m_dSizer->Add(m_dAutoMotiveTemplates, 0, wxALL, 5); - - m_dJorgeMorales = new wxStaticText(m_dScroll, wxID_ANY, wxT("Jorge Morales"), wxDefaultPosition, wxDefaultSize, 0); - m_dJorgeMorales->Wrap(-1); - m_dSizer->Add(m_dJorgeMorales, 0, wxALL, 5); - - m_dMichaelRooke = new wxStaticText(m_dScroll, wxID_ANY, wxT("Michael Rooke"), wxDefaultPosition, wxDefaultSize, 0); - m_dMichaelRooke->Wrap(-1); - m_dSizer->Add(m_dMichaelRooke, 0, wxALL, 5); - - m_dTNCOM = new wxStaticText(m_dScroll, wxID_ANY, wxT("TNCOM"), wxDefaultPosition, wxDefaultSize, 0); - m_dTNCOM->Wrap(-1); - m_dSizer->Add(m_dTNCOM, 0, wxALL, 5); - - m_dErikWied = new wxStaticText(m_dScroll, wxID_ANY, wxT("Erik Mikkel Wied"), wxDefaultPosition, wxDefaultSize, 0); - m_dErikWied->Wrap(-1); - m_dSizer->Add(m_dErikWied, 0, wxALL, 5); - - m_dRobertDuering = new wxStaticText(m_dScroll, wxID_ANY, wxT("Robert Duering"), wxDefaultPosition, wxDefaultSize, 0); - m_dRobertDuering->Wrap(-1); - m_dSizer->Add(m_dRobertDuering, 0, wxALL, 5); - - m_dJimDeitch = new wxStaticText(m_dScroll, wxID_ANY, wxT("Jim Deitch"), wxDefaultPosition, wxDefaultSize, 0); - m_dJimDeitch->Wrap(-1); - m_dSizer->Add(m_dJimDeitch, 0, wxALL, 5); - - m_dNooElec = new wxStaticText(m_dScroll, wxID_ANY, wxT("NooElec Inc. / nooelec.com"), wxDefaultPosition, wxDefaultSize, 0); - m_dNooElec->Wrap(-1); - m_dSizer->Add(m_dNooElec, 0, wxALL, 5); - - m_dDavidAhlgren = new wxStaticText(m_dScroll, wxID_ANY, wxT("David Ahlgren"), wxDefaultPosition, wxDefaultSize, 0); - m_dDavidAhlgren->Wrap(-1); - m_dSizer->Add(m_dDavidAhlgren, 0, wxALL, 5); - - m_dRonaldCook = new wxStaticText(m_dScroll, wxID_ANY, wxT("Ronald Cook"), wxDefaultPosition, wxDefaultSize, 0); - m_dRonaldCook->Wrap(-1); - m_dSizer->Add(m_dRonaldCook, 0, wxALL, 5); - - m_dEricPeterson = new wxStaticText(m_dScroll, wxID_ANY, wxT("Eric Peterson"), wxDefaultPosition, wxDefaultSize, 0); - m_dEricPeterson->Wrap(-1); - m_dSizer->Add(m_dEricPeterson, 0, wxALL, 5); - - m_dGeoDistributing = new wxStaticText(m_dScroll, wxID_ANY, wxT("Geo Distributing"), wxDefaultPosition, wxDefaultSize, 0); - m_dGeoDistributing->Wrap(-1); - m_dSizer->Add(m_dGeoDistributing, 0, wxALL, 5); - - m_dJamesCarson = new wxStaticText(m_dScroll, wxID_ANY, wxT("James Carson"), wxDefaultPosition, wxDefaultSize, 0); - m_dJamesCarson->Wrap(-1); - m_dSizer->Add(m_dJamesCarson, 0, wxALL, 5); - - m_dCraigWilliams = new wxStaticText(m_dScroll, wxID_ANY, wxT("Craig Williams"), wxDefaultPosition, wxDefaultSize, 0); - m_dCraigWilliams->Wrap(-1); - m_dSizer->Add(m_dCraigWilliams, 0, wxALL, 5); - - m_dRudolfShaffer = new wxStaticText(m_dScroll, wxID_ANY, wxT("Rudolf Schaffer"), wxDefaultPosition, wxDefaultSize, 0); - m_dRudolfShaffer->Wrap(-1); - m_dSizer->Add(m_dRudolfShaffer, 0, wxALL, 5); - - m_dJohnKaton = new wxStaticText(m_dScroll, wxID_ANY, wxT("John Katon"), wxDefaultPosition, wxDefaultSize, 0); - m_dJohnKaton->Wrap(-1); - m_dSizer->Add(m_dJohnKaton, 0, wxALL, 5); - - m_dVincentSonnier = new wxStaticText(m_dScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0); - m_dVincentSonnier->Wrap(-1); - m_dSizer->Add(m_dVincentSonnier, 0, wxALL, 5); - - m_dCorq = new wxStaticText(m_dScroll, wxID_ANY, wxT("corq's auctions/L. Easterly LTD (x 4)"), wxDefaultPosition, wxDefaultSize, 0); - m_dCorq->Wrap(-1); - m_dSizer->Add(m_dCorq, 0, wxALL, 5); - - m_dIvanAlekseev = new wxStaticText(m_dScroll, wxID_ANY, wxT("Ivan Alekseev"), wxDefaultPosition, wxDefaultSize, 0); - m_dIvanAlekseev->Wrap(-1); - m_dSizer->Add(m_dIvanAlekseev, 0, wxALL, 5); - - m_dOleJorgenKolsrud = new wxStaticText(m_dScroll, wxID_ANY, wxT("Ole-Jørgen Næss Kolsrud"), wxDefaultPosition, wxDefaultSize, 0); - m_dOleJorgenKolsrud->Wrap(-1); - m_dSizer->Add(m_dOleJorgenKolsrud, 0, wxALL, 5); - - m_dHenrikJagemyr = new wxStaticText(m_dScroll, wxID_ANY, wxT("Henrik Jagemyr"), wxDefaultPosition, wxDefaultSize, 0); - m_dHenrikJagemyr->Wrap(-1); - m_dSizer->Add(m_dHenrikJagemyr, 0, wxALL, 5); - - m_dPeterHaines = new wxStaticText(m_dScroll, wxID_ANY, wxT("Peter Haines"), wxDefaultPosition, wxDefaultSize, 0); - m_dPeterHaines->Wrap(-1); - m_dSizer->Add(m_dPeterHaines, 0, wxALL, 5); - - m_dLeonAbrassart = new wxStaticText(m_dScroll, wxID_ANY, wxT("Leon Abrassart"), wxDefaultPosition, wxDefaultSize, 0); - m_dLeonAbrassart->Wrap(-1); - m_dSizer->Add(m_dLeonAbrassart, 0, wxALL, 5); - - m_dGeorgeTalbot = new wxStaticText(m_dScroll, wxID_ANY, wxT("George Alan Talbot"), wxDefaultPosition, wxDefaultSize, 0); - m_dGeorgeTalbot->Wrap(-1); - m_dSizer->Add(m_dGeorgeTalbot, 0, wxALL, 5); - - m_dFranciscoPuerta = new wxStaticText(m_dScroll, wxID_ANY, wxT("Francisco Borja Marcos de la Puerta"), wxDefaultPosition, wxDefaultSize, 0); - m_dFranciscoPuerta->Wrap(-1); - m_dSizer->Add(m_dFranciscoPuerta, 0, wxALL, 5); - - m_dRonaldLundeen = new wxStaticText(m_dScroll, wxID_ANY, wxT("Ronald A. Lundeen"), wxDefaultPosition, wxDefaultSize, 0); - m_dRonaldLundeen->Wrap(-1); - m_dSizer->Add(m_dRonaldLundeen, 0, wxALL, 5); - - m_dWalterHorbert = new wxStaticText(m_dScroll, wxID_ANY, wxT("Walter Horbert"), wxDefaultPosition, wxDefaultSize, 0); - m_dWalterHorbert->Wrap(-1); - m_dSizer->Add(m_dWalterHorbert, 0, wxALL, 5); - - m_dWilliamLD = new wxStaticText(m_dScroll, wxID_ANY, wxT("William Lloyd-Davies"), wxDefaultPosition, wxDefaultSize, 0); - m_dWilliamLD->Wrap(-1); - m_dSizer->Add(m_dWilliamLD, 0, wxALL, 5); - - m_dBratislavArandjelovic = new wxStaticText(m_dScroll, wxID_ANY, wxT("Bratislav Arandjelovic"), wxDefaultPosition, wxDefaultSize, 0); - m_dBratislavArandjelovic->Wrap(-1); - m_dSizer->Add(m_dBratislavArandjelovic, 0, wxALL, 5); - - m_dGaryMartin = new wxStaticText(m_dScroll, wxID_ANY, wxT("Gary Martin"), wxDefaultPosition, wxDefaultSize, 0); - m_dGaryMartin->Wrap(-1); - m_dSizer->Add(m_dGaryMartin, 0, wxALL, 5); - - m_dEinarsRepse = new wxStaticText(m_dScroll, wxID_ANY, wxT("Einars Repse"), wxDefaultPosition, wxDefaultSize, 0); - m_dEinarsRepse->Wrap(-1); - m_dSizer->Add(m_dEinarsRepse, 0, wxALL, 5); - - m_dTimothyGatton = new wxStaticText(m_dScroll, wxID_ANY, wxT("Timothy Gatton"), wxDefaultPosition, wxDefaultSize, 0); - m_dTimothyGatton->Wrap(-1); - m_dSizer->Add(m_dTimothyGatton, 0, wxALL, 5); - - m_dStephenCuccio = new wxStaticText(m_dScroll, wxID_ANY, wxT("Stephen Cuccio"), wxDefaultPosition, wxDefaultSize, 0); - m_dStephenCuccio->Wrap(-1); - m_dSizer->Add(m_dStephenCuccio, 0, wxALL, 5); - - m_dKeshavlalPatel = new wxStaticText(m_dScroll, wxID_ANY, wxT("Keshavlal Patel"), wxDefaultPosition, wxDefaultSize, 0); - m_dKeshavlalPatel->Wrap(-1); - m_dSizer->Add(m_dKeshavlalPatel, 0, wxALL, 5); - - m_dBobSchatzman = new wxStaticText(m_dScroll, wxID_ANY, wxT("Bob Schatzman"), wxDefaultPosition, wxDefaultSize, 0); - m_dBobSchatzman->Wrap(-1); - m_dSizer->Add(m_dBobSchatzman, 0, wxALL, 5); - - m_dRobertRoss = new wxStaticText(m_dScroll, wxID_ANY, wxT("Robert Ross"), wxDefaultPosition, wxDefaultSize, 0); - m_dRobertRoss->Wrap(-1); - m_dSizer->Add(m_dRobertRoss, 0, wxALL, 5); - - m_dRobertoBellotti = new wxStaticText(m_dScroll, wxID_ANY, wxT("Roberto Bellotti"), wxDefaultPosition, wxDefaultSize, 0); - m_dRobertoBellotti->Wrap(-1); - m_dSizer->Add(m_dRobertoBellotti, 0, wxALL, 5); - - m_dSergeVanderTorre = new wxStaticText(m_dScroll, wxID_ANY, wxT("Serge Van der Torre"), wxDefaultPosition, wxDefaultSize, 0); - m_dSergeVanderTorre->Wrap(-1); - m_dSizer->Add(m_dSergeVanderTorre, 0, wxALL, 5); - - m_dDieterSchneider = new wxStaticText(m_dScroll, wxID_ANY, wxT("Dieter Schneider"), wxDefaultPosition, wxDefaultSize, 0); - m_dDieterSchneider->Wrap(-1); - m_dSizer->Add(m_dDieterSchneider, 0, wxALL, 5); - - m_dPetrikaJaneku = new wxStaticText(m_dScroll, wxID_ANY, wxT("Petrika Janeku"), wxDefaultPosition, wxDefaultSize, 0); - m_dPetrikaJaneku->Wrap(-1); - m_dSizer->Add(m_dPetrikaJaneku, 0, wxALL, 5); - - - m_dBSizer->Add(m_dSizer, 1, wxALL | wxEXPAND, 5); - - - m_dScroll->SetSizer(m_dBSizer); + m_dSizer = new wxBoxSizer( wxVERTICAL ); + + m_dHeader = new wxStaticText( m_dScroll, wxID_ANY, wxT("Thanks to everyone who donated at cubicsdr.com!"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_dHeader->Wrap( -1 ); + m_dHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_dSizer->Add( m_dHeader, 0, wxALL, 5 ); + + m_dDivider1 = new wxStaticLine( m_dScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_dSizer->Add( m_dDivider1, 0, wxEXPAND | wxALL, 5 ); + + m_dSDRplay = new wxStaticText( m_dScroll, wxID_ANY, wxT("SDRplay / sdrplay.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dSDRplay->Wrap( -1 ); + m_dSizer->Add( m_dSDRplay, 0, wxALL, 5 ); + + m_dMichaelLadd = new wxStaticText( m_dScroll, wxID_ANY, wxT("Michael Ladd"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dMichaelLadd->Wrap( -1 ); + m_dSizer->Add( m_dMichaelLadd, 0, wxALL, 5 ); + + m_dAutoMotiveTemplates = new wxStaticText( m_dScroll, wxID_ANY, wxT("Automotive Templates"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dAutoMotiveTemplates->Wrap( -1 ); + m_dSizer->Add( m_dAutoMotiveTemplates, 0, wxALL, 5 ); + + m_dJorgeMorales = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jorge Morales"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJorgeMorales->Wrap( -1 ); + m_dSizer->Add( m_dJorgeMorales, 0, wxALL, 5 ); + + m_dMichaelRooke = new wxStaticText( m_dScroll, wxID_ANY, wxT("Michael Rooke"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dMichaelRooke->Wrap( -1 ); + m_dSizer->Add( m_dMichaelRooke, 0, wxALL, 5 ); + + m_dTNCOM = new wxStaticText( m_dScroll, wxID_ANY, wxT("TNCOM"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dTNCOM->Wrap( -1 ); + m_dSizer->Add( m_dTNCOM, 0, wxALL, 5 ); + + m_dErikWied = new wxStaticText( m_dScroll, wxID_ANY, wxT("Erik Mikkel Wied"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dErikWied->Wrap( -1 ); + m_dSizer->Add( m_dErikWied, 0, wxALL, 5 ); + + m_dRobertDuering = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Duering"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRobertDuering->Wrap( -1 ); + m_dSizer->Add( m_dRobertDuering, 0, wxALL, 5 ); + + m_dJimDeitch = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jim Deitch"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJimDeitch->Wrap( -1 ); + m_dSizer->Add( m_dJimDeitch, 0, wxALL, 5 ); + + m_dNooElec = new wxStaticText( m_dScroll, wxID_ANY, wxT("NooElec Inc. / nooelec.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dNooElec->Wrap( -1 ); + m_dSizer->Add( m_dNooElec, 0, wxALL, 5 ); + + m_dDavidAhlgren = new wxStaticText( m_dScroll, wxID_ANY, wxT("David Ahlgren"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dDavidAhlgren->Wrap( -1 ); + m_dSizer->Add( m_dDavidAhlgren, 0, wxALL, 5 ); + + m_dRonaldCook = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ronald Cook"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRonaldCook->Wrap( -1 ); + m_dSizer->Add( m_dRonaldCook, 0, wxALL, 5 ); + + m_dEricPeterson = new wxStaticText( m_dScroll, wxID_ANY, wxT("Eric Peterson"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dEricPeterson->Wrap( -1 ); + m_dSizer->Add( m_dEricPeterson, 0, wxALL, 5 ); + + m_dGeoDistributing = new wxStaticText( m_dScroll, wxID_ANY, wxT("Geo Distributing"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dGeoDistributing->Wrap( -1 ); + m_dSizer->Add( m_dGeoDistributing, 0, wxALL, 5 ); + + m_dJamesCarson = new wxStaticText( m_dScroll, wxID_ANY, wxT("James Carson"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJamesCarson->Wrap( -1 ); + m_dSizer->Add( m_dJamesCarson, 0, wxALL, 5 ); + + m_dCraigWilliams = new wxStaticText( m_dScroll, wxID_ANY, wxT("Craig Williams"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dCraigWilliams->Wrap( -1 ); + m_dSizer->Add( m_dCraigWilliams, 0, wxALL, 5 ); + + m_dRudolfShaffer = new wxStaticText( m_dScroll, wxID_ANY, wxT("Rudolf Schaffer"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRudolfShaffer->Wrap( -1 ); + m_dSizer->Add( m_dRudolfShaffer, 0, wxALL, 5 ); + + m_dJohnKaton = new wxStaticText( m_dScroll, wxID_ANY, wxT("John Katon"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJohnKaton->Wrap( -1 ); + m_dSizer->Add( m_dJohnKaton, 0, wxALL, 5 ); + + m_dVincentSonnier = new wxStaticText( m_dScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dVincentSonnier->Wrap( -1 ); + m_dSizer->Add( m_dVincentSonnier, 0, wxALL, 5 ); + + m_dCorq = new wxStaticText( m_dScroll, wxID_ANY, wxT("corq's auctions/L. Easterly LTD (x 4)"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dCorq->Wrap( -1 ); + m_dSizer->Add( m_dCorq, 0, wxALL, 5 ); + + m_dIvanAlekseev = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ivan Alekseev"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dIvanAlekseev->Wrap( -1 ); + m_dSizer->Add( m_dIvanAlekseev, 0, wxALL, 5 ); + + m_dOleJorgenKolsrud = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ole-Jørgen Næss Kolsrud"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dOleJorgenKolsrud->Wrap( -1 ); + m_dSizer->Add( m_dOleJorgenKolsrud, 0, wxALL, 5 ); + + m_dHenrikJagemyr = new wxStaticText( m_dScroll, wxID_ANY, wxT("Henrik Jagemyr"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dHenrikJagemyr->Wrap( -1 ); + m_dSizer->Add( m_dHenrikJagemyr, 0, wxALL, 5 ); + + m_dPeterHaines = new wxStaticText( m_dScroll, wxID_ANY, wxT("Peter Haines"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dPeterHaines->Wrap( -1 ); + m_dSizer->Add( m_dPeterHaines, 0, wxALL, 5 ); + + m_dLeonAbrassart = new wxStaticText( m_dScroll, wxID_ANY, wxT("Leon Abrassart"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dLeonAbrassart->Wrap( -1 ); + m_dSizer->Add( m_dLeonAbrassart, 0, wxALL, 5 ); + + m_dGeorgeTalbot = new wxStaticText( m_dScroll, wxID_ANY, wxT("George Alan Talbot"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dGeorgeTalbot->Wrap( -1 ); + m_dSizer->Add( m_dGeorgeTalbot, 0, wxALL, 5 ); + + m_dFranciscoPuerta = new wxStaticText( m_dScroll, wxID_ANY, wxT("Francisco Borja Marcos de la Puerta"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dFranciscoPuerta->Wrap( -1 ); + m_dSizer->Add( m_dFranciscoPuerta, 0, wxALL, 5 ); + + m_dRonaldLundeen = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ronald A. Lundeen"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRonaldLundeen->Wrap( -1 ); + m_dSizer->Add( m_dRonaldLundeen, 0, wxALL, 5 ); + + m_dWalterHorbert = new wxStaticText( m_dScroll, wxID_ANY, wxT("Walter Horbert"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dWalterHorbert->Wrap( -1 ); + m_dSizer->Add( m_dWalterHorbert, 0, wxALL, 5 ); + + m_dWilliamLD = new wxStaticText( m_dScroll, wxID_ANY, wxT("William Lloyd-Davies"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dWilliamLD->Wrap( -1 ); + m_dSizer->Add( m_dWilliamLD, 0, wxALL, 5 ); + + m_dBratislavArandjelovic = new wxStaticText( m_dScroll, wxID_ANY, wxT("Bratislav Arandjelovic"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dBratislavArandjelovic->Wrap( -1 ); + m_dSizer->Add( m_dBratislavArandjelovic, 0, wxALL, 5 ); + + m_dGaryMartin = new wxStaticText( m_dScroll, wxID_ANY, wxT("Gary Martin"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dGaryMartin->Wrap( -1 ); + m_dSizer->Add( m_dGaryMartin, 0, wxALL, 5 ); + + m_dEinarsRepse = new wxStaticText( m_dScroll, wxID_ANY, wxT("Einars Repse"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dEinarsRepse->Wrap( -1 ); + m_dSizer->Add( m_dEinarsRepse, 0, wxALL, 5 ); + + m_dTimothyGatton = new wxStaticText( m_dScroll, wxID_ANY, wxT("Timothy Gatton"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dTimothyGatton->Wrap( -1 ); + m_dSizer->Add( m_dTimothyGatton, 0, wxALL, 5 ); + + m_dStephenCuccio = new wxStaticText( m_dScroll, wxID_ANY, wxT("Stephen Cuccio"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dStephenCuccio->Wrap( -1 ); + m_dSizer->Add( m_dStephenCuccio, 0, wxALL, 5 ); + + m_dKeshavlalPatel = new wxStaticText( m_dScroll, wxID_ANY, wxT("Keshavlal Patel"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dKeshavlalPatel->Wrap( -1 ); + m_dSizer->Add( m_dKeshavlalPatel, 0, wxALL, 5 ); + + m_dBobSchatzman = new wxStaticText( m_dScroll, wxID_ANY, wxT("Bob Schatzman"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dBobSchatzman->Wrap( -1 ); + m_dSizer->Add( m_dBobSchatzman, 0, wxALL, 5 ); + + m_dRobertRoss = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Ross"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRobertRoss->Wrap( -1 ); + m_dSizer->Add( m_dRobertRoss, 0, wxALL, 5 ); + + m_dRobertoBellotti = new wxStaticText( m_dScroll, wxID_ANY, wxT("Roberto Bellotti"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRobertoBellotti->Wrap( -1 ); + m_dSizer->Add( m_dRobertoBellotti, 0, wxALL, 5 ); + + m_dSergeVanderTorre = new wxStaticText( m_dScroll, wxID_ANY, wxT("Serge Van der Torre"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dSergeVanderTorre->Wrap( -1 ); + m_dSizer->Add( m_dSergeVanderTorre, 0, wxALL, 5 ); + + m_dDieterSchneider = new wxStaticText( m_dScroll, wxID_ANY, wxT("Dieter Schneider"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dDieterSchneider->Wrap( -1 ); + m_dSizer->Add( m_dDieterSchneider, 0, wxALL, 5 ); + + m_dPetrikaJaneku = new wxStaticText( m_dScroll, wxID_ANY, wxT("Petrika Janeku"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dPetrikaJaneku->Wrap( -1 ); + m_dSizer->Add( m_dPetrikaJaneku, 0, wxALL, 5 ); + + m_dChadMyslinsky = new wxStaticText( m_dScroll, wxID_ANY, wxT("Chad Myslinsky"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dChadMyslinsky->Wrap( -1 ); + m_dSizer->Add( m_dChadMyslinsky, 0, wxALL, 5 ); + + m_dCharlieBruckner = new wxStaticText( m_dScroll, wxID_ANY, wxT("Charlie Bruckner"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dCharlieBruckner->Wrap( -1 ); + m_dSizer->Add( m_dCharlieBruckner, 0, wxALL, 5 ); + + m_dJordanParker = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jordan Parker"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJordanParker->Wrap( -1 ); + m_dSizer->Add( m_dJordanParker, 0, wxALL, 5 ); + + m_dRobertChave = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Chave"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRobertChave->Wrap( -1 ); + m_dSizer->Add( m_dRobertChave, 0, wxALL, 5 ); + + + m_dBSizer->Add( m_dSizer, 1, wxALL|wxEXPAND, 5 ); + + + m_dScroll->SetSizer( m_dBSizer ); m_dScroll->Layout(); - m_dBSizer->Fit(m_dScroll); - m_aboutNotebook->AddPage(m_dScroll, wxT("Donations"), false); - m_stScroll = new wxScrolledWindow(m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL); - m_stScroll->SetScrollRate(5, 5); + m_dBSizer->Fit( m_dScroll ); + m_aboutNotebook->AddPage( m_dScroll, wxT("Donations"), false ); + m_stScroll = new wxScrolledWindow( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_stScroll->SetScrollRate( 5, 5 ); wxBoxSizer* m_stBSizer; - m_stBSizer = new wxBoxSizer(wxVERTICAL); - + m_stBSizer = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* m_stSizer; - m_stSizer = new wxBoxSizer(wxVERTICAL); - - m_stHeader = new wxStaticText(m_stScroll, wxID_ANY, wxT("Special Thanks To"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_stHeader->Wrap(-1); - m_stHeader->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_stSizer->Add(m_stHeader, 0, wxALL, 5); - - m_stDivider1 = new wxStaticLine(m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_stSizer->Add(m_stDivider1, 0, wxEXPAND | wxALL, 5); - - m_stSoapyDevAssistHeader = new wxStaticText(m_stScroll, wxID_ANY, wxT("SoapySDR Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_stSoapyDevAssistHeader->Wrap(-1); - m_stSoapyDevAssistHeader->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString)); - - m_stSizer->Add(m_stSoapyDevAssistHeader, 0, wxALL, 5); - - m_stJoshBlum = new wxStaticText(m_stScroll, wxID_ANY, wxT("Josh Blum / @guruofquality / pothosware.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stJoshBlum->Wrap(-1); - m_stSizer->Add(m_stJoshBlum, 0, wxALL, 5); - - m_stDivider2 = new wxStaticLine(m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_stSizer->Add(m_stDivider2, 0, wxEXPAND | wxALL, 5); - - m_stLiquidDSPHeader = new wxStaticText(m_stScroll, wxID_ANY, wxT("Liquid-DSP Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_stLiquidDSPHeader->Wrap(-1); - m_stLiquidDSPHeader->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString)); - - m_stSizer->Add(m_stLiquidDSPHeader, 0, wxALL, 5); - - m_stJosephGaeddert = new wxStaticText(m_stScroll, wxID_ANY, wxT("Joseph D. Gaeddert / @jgaeddert / liquidsdr.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stJosephGaeddert->Wrap(-1); - m_stSizer->Add(m_stJosephGaeddert, 0, wxALL, 5); - - m_stDivider3 = new wxStaticLine(m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_stSizer->Add(m_stDivider3, 0, wxEXPAND | wxALL, 5); - - m_stIdeasDirectionsHeader = new wxStaticText(m_stScroll, wxID_ANY, wxT("Ideas, Direction && Encouragement:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_stIdeasDirectionsHeader->Wrap(-1); - m_stIdeasDirectionsHeader->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString)); - - m_stSizer->Add(m_stIdeasDirectionsHeader, 0, wxALL, 5); - - m_stTonMachielsen = new wxStaticText(m_stScroll, wxID_ANY, wxT("Ton Machielsen / @Toontje / @EA3HOE "), wxDefaultPosition, wxDefaultSize, 0); - m_stTonMachielsen->Wrap(-1); - m_stSizer->Add(m_stTonMachielsen, 0, wxALL, 5); - - m_stMikeLadd = new wxStaticText(m_stScroll, wxID_ANY, wxT("Mike Ladd / KD2KOG.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stMikeLadd->Wrap(-1); - m_stSizer->Add(m_stMikeLadd, 0, wxALL, 5); - - m_stSDRplay = new wxStaticText(m_stScroll, wxID_ANY, wxT("SDRplay team / @SDRplay / SDRplay.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stSDRplay->Wrap(-1); - m_stSizer->Add(m_stSDRplay, 0, wxALL, 5); - - m_stSDRplayFB = new wxStaticText(m_stScroll, wxID_ANY, wxT("SDRplay Facebook group"), wxDefaultPosition, wxDefaultSize, 0); - m_stSDRplayFB->Wrap(-1); - m_stSizer->Add(m_stSDRplayFB, 0, wxALL, 5); - - m_stPaulWarren = new wxStaticText(m_stScroll, wxID_ANY, wxT("Paul Warren / @pwarren"), wxDefaultPosition, wxDefaultSize, 0); - m_stPaulWarren->Wrap(-1); - m_stSizer->Add(m_stPaulWarren, 0, wxALL, 5); - - m_stSegesdiKaroly = new wxStaticText(m_stScroll, wxID_ANY, wxT("Segesdi Károly / @jazzkutya"), wxDefaultPosition, wxDefaultSize, 0); - m_stSegesdiKaroly->Wrap(-1); - m_stSizer->Add(m_stSegesdiKaroly, 0, wxALL, 5); - - m_stRedditRTLSDR = new wxStaticText(m_stScroll, wxID_ANY, wxT("Reddit RTL-SDR group /r/rtlsdr"), wxDefaultPosition, wxDefaultSize, 0); - m_stRedditRTLSDR->Wrap(-1); - m_stSizer->Add(m_stRedditRTLSDR, 0, wxALL, 5); - - m_stNooElec = new wxStaticText(m_stScroll, wxID_ANY, wxT("NooElec team / NooElec.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stNooElec->Wrap(-1); - m_stSizer->Add(m_stNooElec, 0, wxALL, 5); - - m_stGHIssues = new wxStaticText(m_stScroll, wxID_ANY, wxT("Everyone who's contributed to the GitHub issues; thanks!"), wxDefaultPosition, wxDefaultSize, 0); - m_stGHIssues->Wrap(-1); - m_stSizer->Add(m_stGHIssues, 0, wxALL, 5); - - m_stNominate = new wxStaticText(m_stScroll, wxID_ANY, wxT("Please feel free to nominate anyone we might have missed."), wxDefaultPosition, wxDefaultSize, 0); - m_stNominate->Wrap(-1); - m_stSizer->Add(m_stNominate, 0, wxALL, 5); - - - m_stBSizer->Add(m_stSizer, 1, wxALL | wxEXPAND, 5); - - - m_stScroll->SetSizer(m_stBSizer); + m_stSizer = new wxBoxSizer( wxVERTICAL ); + + m_stHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Special Thanks To"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_stHeader->Wrap( -1 ); + m_stHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_stSizer->Add( m_stHeader, 0, wxALL, 5 ); + + m_stDivider1 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_stSizer->Add( m_stDivider1, 0, wxEXPAND | wxALL, 5 ); + + m_stSoapyDevAssistHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("SoapySDR Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_stSoapyDevAssistHeader->Wrap( -1 ); + m_stSoapyDevAssistHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); + + m_stSizer->Add( m_stSoapyDevAssistHeader, 0, wxALL, 5 ); + + m_stJoshBlum = new wxStaticText( m_stScroll, wxID_ANY, wxT("Josh Blum / @guruofquality / pothosware.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stJoshBlum->Wrap( -1 ); + m_stSizer->Add( m_stJoshBlum, 0, wxALL, 5 ); + + m_stDivider2 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_stSizer->Add( m_stDivider2, 0, wxEXPAND | wxALL, 5 ); + + m_stLiquidDSPHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Liquid-DSP Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_stLiquidDSPHeader->Wrap( -1 ); + m_stLiquidDSPHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); + + m_stSizer->Add( m_stLiquidDSPHeader, 0, wxALL, 5 ); + + m_stJosephGaeddert = new wxStaticText( m_stScroll, wxID_ANY, wxT("Joseph D. Gaeddert / @jgaeddert / liquidsdr.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stJosephGaeddert->Wrap( -1 ); + m_stSizer->Add( m_stJosephGaeddert, 0, wxALL, 5 ); + + m_stDivider3 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_stSizer->Add( m_stDivider3, 0, wxEXPAND | wxALL, 5 ); + + m_stIdeasDirectionsHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Ideas, Direction && Encouragement:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_stIdeasDirectionsHeader->Wrap( -1 ); + m_stIdeasDirectionsHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); + + m_stSizer->Add( m_stIdeasDirectionsHeader, 0, wxALL, 5 ); + + m_stTonMachielsen = new wxStaticText( m_stScroll, wxID_ANY, wxT("Ton Machielsen / @Toontje / @EA3HOE "), wxDefaultPosition, wxDefaultSize, 0 ); + m_stTonMachielsen->Wrap( -1 ); + m_stSizer->Add( m_stTonMachielsen, 0, wxALL, 5 ); + + m_stMikeLadd = new wxStaticText( m_stScroll, wxID_ANY, wxT("Mike Ladd / KD2KOG.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stMikeLadd->Wrap( -1 ); + m_stSizer->Add( m_stMikeLadd, 0, wxALL, 5 ); + + m_stSDRplay = new wxStaticText( m_stScroll, wxID_ANY, wxT("SDRplay team / @SDRplay / SDRplay.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stSDRplay->Wrap( -1 ); + m_stSizer->Add( m_stSDRplay, 0, wxALL, 5 ); + + m_stSDRplayFB = new wxStaticText( m_stScroll, wxID_ANY, wxT("SDRplay Facebook group"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stSDRplayFB->Wrap( -1 ); + m_stSizer->Add( m_stSDRplayFB, 0, wxALL, 5 ); + + m_stPaulWarren = new wxStaticText( m_stScroll, wxID_ANY, wxT("Paul Warren / @pwarren"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stPaulWarren->Wrap( -1 ); + m_stSizer->Add( m_stPaulWarren, 0, wxALL, 5 ); + + m_stSegesdiKaroly = new wxStaticText( m_stScroll, wxID_ANY, wxT("Segesdi Károly / @jazzkutya"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stSegesdiKaroly->Wrap( -1 ); + m_stSizer->Add( m_stSegesdiKaroly, 0, wxALL, 5 ); + + m_stRedditRTLSDR = new wxStaticText( m_stScroll, wxID_ANY, wxT("Reddit RTL-SDR group /r/rtlsdr"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stRedditRTLSDR->Wrap( -1 ); + m_stSizer->Add( m_stRedditRTLSDR, 0, wxALL, 5 ); + + m_stNooElec = new wxStaticText( m_stScroll, wxID_ANY, wxT("NooElec team / NooElec.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stNooElec->Wrap( -1 ); + m_stSizer->Add( m_stNooElec, 0, wxALL, 5 ); + + m_stGHIssues = new wxStaticText( m_stScroll, wxID_ANY, wxT("Everyone who's contributed to the GitHub issues; thanks!"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stGHIssues->Wrap( -1 ); + m_stSizer->Add( m_stGHIssues, 0, wxALL, 5 ); + + m_stNominate = new wxStaticText( m_stScroll, wxID_ANY, wxT("Please feel free to nominate anyone we might have missed."), wxDefaultPosition, wxDefaultSize, 0 ); + m_stNominate->Wrap( -1 ); + m_stSizer->Add( m_stNominate, 0, wxALL, 5 ); + + + m_stBSizer->Add( m_stSizer, 1, wxALL|wxEXPAND, 5 ); + + + m_stScroll->SetSizer( m_stBSizer ); m_stScroll->Layout(); - m_stBSizer->Fit(m_stScroll); - m_aboutNotebook->AddPage(m_stScroll, wxT("Special Thanks"), false); - - dlgSizer->Add(m_aboutNotebook, 1, wxEXPAND | wxALL, 5); - - - this->SetSizer(dlgSizer); + m_stBSizer->Fit( m_stScroll ); + m_aboutNotebook->AddPage( m_stScroll, wxT("Special Thanks"), false ); + + dlgSizer->Add( m_aboutNotebook, 1, wxEXPAND | wxALL, 5 ); + + + this->SetSizer( dlgSizer ); this->Layout(); - - this->Centre(wxBOTH); + + this->Centre( wxBOTH ); } AboutDialogBase::~AboutDialogBase() diff --git a/src/forms/Dialog/AboutDialogBase.h b/src/forms/Dialog/AboutDialogBase.h index 099f910d..13b1eaa9 100644 --- a/src/forms/Dialog/AboutDialogBase.h +++ b/src/forms/Dialog/AboutDialogBase.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 27 2017) +// C++ code generated with wxFormBuilder (version Nov 6 2017) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -32,114 +32,118 @@ /////////////////////////////////////////////////////////////////////////////// /// Class AboutDialogBase /////////////////////////////////////////////////////////////////////////////// -class AboutDialogBase : public wxDialog +class AboutDialogBase : public wxDialog { -private: - -protected: - wxPanel* m_hPanel; - wxStaticText* m_appName; - wxNotebook* m_aboutNotebook; - wxScrolledWindow* m_dbScroll; - wxStaticText* m_dbHeader; - wxStaticText* m_dbGHHeader; - wxStaticText* m_dbTwitter; - wxStaticText* m_dbCharlesCliffe; - wxStaticText* m_dbghCC; - wxStaticText* m_dbtCC; - wxStaticText* m_dbVincentSonnier; - wxStaticText* m_dbghVS; - wxStaticText* m_dbtVS; - wxStaticLine* m_dbDivider1; - wxStaticText* m_cContributorsHeader; - wxStaticText* m_cGitHub; - wxStaticText* m_cCorneLukken; - wxStaticText* m_cghCL; - wxStaticText* m_cStainislawPitucha; - wxStaticText* m_cghSP; - wxStaticText* m_cghStefanTalpalaru; - wxStaticText* m_cghST; - wxStaticText* m_cCrisMotch; - wxStaticText* m_cghCM; - wxStaticText* m_cMariuszRyndzionek; - wxStaticText* m_cghMR; - wxStaticText* m_cJiangWei; - wxStaticText* m_cghJW; - wxStaticText* m_cTomSwartz; - wxStaticText* m_cghTS; - wxStaticText* m_cInfinityCyberworks; - wxStaticText* m_cghIC; - wxScrolledWindow* m_dScroll; - wxStaticText* m_dHeader; - wxStaticLine* m_dDivider1; - wxStaticText* m_dSDRplay; - wxStaticText* m_dMichaelLadd; - wxStaticText* m_dAutoMotiveTemplates; - wxStaticText* m_dJorgeMorales; - wxStaticText* m_dMichaelRooke; - wxStaticText* m_dTNCOM; - wxStaticText* m_dErikWied; - wxStaticText* m_dRobertDuering; - wxStaticText* m_dJimDeitch; - wxStaticText* m_dNooElec; - wxStaticText* m_dDavidAhlgren; - wxStaticText* m_dRonaldCook; - wxStaticText* m_dEricPeterson; - wxStaticText* m_dGeoDistributing; - wxStaticText* m_dJamesCarson; - wxStaticText* m_dCraigWilliams; - wxStaticText* m_dRudolfShaffer; - wxStaticText* m_dJohnKaton; - wxStaticText* m_dVincentSonnier; - wxStaticText* m_dCorq; - wxStaticText* m_dIvanAlekseev; - wxStaticText* m_dOleJorgenKolsrud; - wxStaticText* m_dHenrikJagemyr; - wxStaticText* m_dPeterHaines; - wxStaticText* m_dLeonAbrassart; - wxStaticText* m_dGeorgeTalbot; - wxStaticText* m_dFranciscoPuerta; - wxStaticText* m_dRonaldLundeen; - wxStaticText* m_dWalterHorbert; - wxStaticText* m_dWilliamLD; - wxStaticText* m_dBratislavArandjelovic; - wxStaticText* m_dGaryMartin; - wxStaticText* m_dEinarsRepse; - wxStaticText* m_dTimothyGatton; - wxStaticText* m_dStephenCuccio; - wxStaticText* m_dKeshavlalPatel; - wxStaticText* m_dBobSchatzman; - wxStaticText* m_dRobertRoss; - wxStaticText* m_dRobertoBellotti; - wxStaticText* m_dSergeVanderTorre; - wxStaticText* m_dDieterSchneider; - wxStaticText* m_dPetrikaJaneku; - wxScrolledWindow* m_stScroll; - wxStaticText* m_stHeader; - wxStaticLine* m_stDivider1; - wxStaticText* m_stSoapyDevAssistHeader; - wxStaticText* m_stJoshBlum; - wxStaticLine* m_stDivider2; - wxStaticText* m_stLiquidDSPHeader; - wxStaticText* m_stJosephGaeddert; - wxStaticLine* m_stDivider3; - wxStaticText* m_stIdeasDirectionsHeader; - wxStaticText* m_stTonMachielsen; - wxStaticText* m_stMikeLadd; - wxStaticText* m_stSDRplay; - wxStaticText* m_stSDRplayFB; - wxStaticText* m_stPaulWarren; - wxStaticText* m_stSegesdiKaroly; - wxStaticText* m_stRedditRTLSDR; - wxStaticText* m_stNooElec; - wxStaticText* m_stGHIssues; - wxStaticText* m_stNominate; - -public: - - AboutDialogBase(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("About"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(530, 420), long style = wxDEFAULT_DIALOG_STYLE); - ~AboutDialogBase(); - + private: + + protected: + wxPanel* m_hPanel; + wxStaticText* m_appName; + wxNotebook* m_aboutNotebook; + wxScrolledWindow* m_dbScroll; + wxStaticText* m_dbHeader; + wxStaticText* m_dbGHHeader; + wxStaticText* m_dbTwitter; + wxStaticText* m_dbCharlesCliffe; + wxStaticText* m_dbghCC; + wxStaticText* m_dbtCC; + wxStaticText* m_dbVincentSonnier; + wxStaticText* m_dbghVS; + wxStaticText* m_dbtVS; + wxStaticLine* m_dbDivider1; + wxStaticText* m_cContributorsHeader; + wxStaticText* m_cGitHub; + wxStaticText* m_cCorneLukken; + wxStaticText* m_cghCL; + wxStaticText* m_cStainislawPitucha; + wxStaticText* m_cghSP; + wxStaticText* m_cghStefanTalpalaru; + wxStaticText* m_cghST; + wxStaticText* m_cCrisMotch; + wxStaticText* m_cghCM; + wxStaticText* m_cMariuszRyndzionek; + wxStaticText* m_cghMR; + wxStaticText* m_cJiangWei; + wxStaticText* m_cghJW; + wxStaticText* m_cTomSwartz; + wxStaticText* m_cghTS; + wxStaticText* m_cInfinityCyberworks; + wxStaticText* m_cghIC; + wxScrolledWindow* m_dScroll; + wxStaticText* m_dHeader; + wxStaticLine* m_dDivider1; + wxStaticText* m_dSDRplay; + wxStaticText* m_dMichaelLadd; + wxStaticText* m_dAutoMotiveTemplates; + wxStaticText* m_dJorgeMorales; + wxStaticText* m_dMichaelRooke; + wxStaticText* m_dTNCOM; + wxStaticText* m_dErikWied; + wxStaticText* m_dRobertDuering; + wxStaticText* m_dJimDeitch; + wxStaticText* m_dNooElec; + wxStaticText* m_dDavidAhlgren; + wxStaticText* m_dRonaldCook; + wxStaticText* m_dEricPeterson; + wxStaticText* m_dGeoDistributing; + wxStaticText* m_dJamesCarson; + wxStaticText* m_dCraigWilliams; + wxStaticText* m_dRudolfShaffer; + wxStaticText* m_dJohnKaton; + wxStaticText* m_dVincentSonnier; + wxStaticText* m_dCorq; + wxStaticText* m_dIvanAlekseev; + wxStaticText* m_dOleJorgenKolsrud; + wxStaticText* m_dHenrikJagemyr; + wxStaticText* m_dPeterHaines; + wxStaticText* m_dLeonAbrassart; + wxStaticText* m_dGeorgeTalbot; + wxStaticText* m_dFranciscoPuerta; + wxStaticText* m_dRonaldLundeen; + wxStaticText* m_dWalterHorbert; + wxStaticText* m_dWilliamLD; + wxStaticText* m_dBratislavArandjelovic; + wxStaticText* m_dGaryMartin; + wxStaticText* m_dEinarsRepse; + wxStaticText* m_dTimothyGatton; + wxStaticText* m_dStephenCuccio; + wxStaticText* m_dKeshavlalPatel; + wxStaticText* m_dBobSchatzman; + wxStaticText* m_dRobertRoss; + wxStaticText* m_dRobertoBellotti; + wxStaticText* m_dSergeVanderTorre; + wxStaticText* m_dDieterSchneider; + wxStaticText* m_dPetrikaJaneku; + wxStaticText* m_dChadMyslinsky; + wxStaticText* m_dCharlieBruckner; + wxStaticText* m_dJordanParker; + wxStaticText* m_dRobertChave; + wxScrolledWindow* m_stScroll; + wxStaticText* m_stHeader; + wxStaticLine* m_stDivider1; + wxStaticText* m_stSoapyDevAssistHeader; + wxStaticText* m_stJoshBlum; + wxStaticLine* m_stDivider2; + wxStaticText* m_stLiquidDSPHeader; + wxStaticText* m_stJosephGaeddert; + wxStaticLine* m_stDivider3; + wxStaticText* m_stIdeasDirectionsHeader; + wxStaticText* m_stTonMachielsen; + wxStaticText* m_stMikeLadd; + wxStaticText* m_stSDRplay; + wxStaticText* m_stSDRplayFB; + wxStaticText* m_stPaulWarren; + wxStaticText* m_stSegesdiKaroly; + wxStaticText* m_stRedditRTLSDR; + wxStaticText* m_stNooElec; + wxStaticText* m_stGHIssues; + wxStaticText* m_stNominate; + + public: + + AboutDialogBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("About"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 530,420 ), long style = wxDEFAULT_DIALOG_STYLE ); + ~AboutDialogBase(); + }; #endif //__ABOUTDIALOGBASE_H__ From be75c627780ce1944470fb1b0bc48d6f0759aeeb Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 2 Jan 2018 23:51:32 -0500 Subject: [PATCH 13/22] Menu and demod display tweaks --- src/AppFrame.cpp | 95 +++++++++++++++++++------------ src/AppFrame.h | 4 ++ src/demod/DemodulatorInstance.cpp | 4 +- src/visual/PrimaryGLContext.cpp | 27 +++++---- 4 files changed, 81 insertions(+), 49 deletions(-) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index fc901fc6..7f2998d6 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -402,45 +402,15 @@ AppFrame::AppFrame() : // Make a menubar menuBar = new wxMenuBar; - wxMenu *menu = new wxMenu; -#ifndef __APPLE__ -#ifdef CUBICSDR_ENABLE_ABOUT_DIALOG - menu->Append(wxID_ABOUT_CUBICSDR, "About " CUBICSDR_INSTALL_NAME); -#endif -#endif - menu->Append(wxID_SDR_DEVICES, "SDR Devices"); - 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.."); - menu->AppendSeparator(); - menu->Append(wxID_RESET, "&Reset Session"); - -#ifndef __APPLE__ - menu->AppendSeparator(); - menu->Append(wxID_CLOSE); -#else -#ifdef CUBICSDR_ENABLE_ABOUT_DIALOG - if ( wxApp::s_macAboutMenuItemId != wxID_NONE ) { - wxString aboutLabel; - aboutLabel.Printf(_("About %s"), CUBICSDR_INSTALL_NAME); - menu->Append( wxApp::s_macAboutMenuItemId, aboutLabel); - } -#endif -#endif + + fileMenu = makeFileMenu(); - menuBar->Append(menu, wxT("&File")); + menuBar->Append(fileMenu, wxT("&File")); settingsMenu = new wxMenu; menuBar->Append(settingsMenu, wxT("&Settings")); - menu = new wxMenu; - std::vector::iterator devices_i; std::map::iterator mdevices_i; AudioThread::enumerateDevices(devices); @@ -474,7 +444,7 @@ AppFrame::AppFrame() : menuBar->Append(sampleRateMenu, wxT("Sample &Rate")); // Audio Sample Rates - menu = new wxMenu; + wxMenu *audioSampleRateMenu = new wxMenu; #define NUM_RATES_DEFAULT 4 unsigned int desired_rates[NUM_RATES_DEFAULT] = { 48000, 44100, 96000, 192000 }; @@ -504,7 +474,7 @@ AppFrame::AppFrame() : for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) { int menu_id = wxID_AUDIO_BANDWIDTH_BASE + wxID_AUDIO_DEVICE_MULTIPLIER * mdevices_i->first; wxMenu *subMenu = new wxMenu; - menu->AppendSubMenu(subMenu, mdevices_i->second.name, wxT("Description?")); + audioSampleRateMenu->AppendSubMenu(subMenu, mdevices_i->second.name, wxT("Description?")); int j = 0; for (std::vector::iterator srate = mdevices_i->second.sampleRates.begin(); srate != mdevices_i->second.sampleRates.end(); @@ -522,7 +492,7 @@ AppFrame::AppFrame() : } } - menuBar->Append(menu, wxT("Audio &Sample Rate")); + menuBar->Append(audioSampleRateMenu, wxT("Audio &Sample Rate")); //Add Display menu displayMenu = new wxMenu; @@ -531,7 +501,7 @@ AppFrame::AppFrame() : int fontScale = wxGetApp().getConfig()->getFontScale(); - fontMenu->AppendRadioItem(wxID_DISPLAY_BASE, "Normal")->Check(GLFont::GLFONT_SCALE_NORMAL == fontScale); + fontMenu->AppendRadioItem(wxID_DISPLAY_BASE, "Default")->Check(GLFont::GLFONT_SCALE_NORMAL == fontScale); fontMenu->AppendRadioItem(wxID_DISPLAY_BASE + 1, "1.5x")->Check(GLFont::GLFONT_SCALE_MEDIUM == fontScale); fontMenu->AppendRadioItem(wxID_DISPLAY_BASE + 2, "2.0x")->Check(GLFont::GLFONT_SCALE_LARGE == fontScale); @@ -725,6 +695,56 @@ AppFrame::~AppFrame() { t_FFTData->join(); } +wxMenu *AppFrame::makeFileMenu() { + + wxMenu *menu = new wxMenu; +#ifndef __APPLE__ +#ifdef CUBICSDR_ENABLE_ABOUT_DIALOG + menu->Append(wxID_ABOUT_CUBICSDR, "About " CUBICSDR_INSTALL_NAME); +#endif +#endif + menu->Append(wxID_SDR_DEVICES, "SDR Devices"); + menu->AppendSeparator(); + menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); + menu->AppendSeparator(); + + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + if (recPath.length() > 32) { + recPath = "..." + recPath.substr(recPath.length() - 32, 32); + } + + menu->Append(wxID_RECORDING_PATH, getSettingsLabel("Set Recording Path", recPath.empty() ? "" : recPath)); + + menu->AppendSeparator(); + menu->Append(wxID_OPEN, "&Open Session"); + menu->Append(wxID_SAVE, "&Save Session"); + menu->Append(wxID_SAVEAS, "Save Session &As.."); + menu->AppendSeparator(); + menu->Append(wxID_RESET, "&Reset Session"); + +#ifndef __APPLE__ + menu->AppendSeparator(); + menu->Append(wxID_CLOSE); +#else +#ifdef CUBICSDR_ENABLE_ABOUT_DIALOG + if (wxApp::s_macAboutMenuItemId != wxID_NONE) { + wxString aboutLabel; + aboutLabel.Printf(_("About %s"), CUBICSDR_INSTALL_NAME); + menu->Append(wxApp::s_macAboutMenuItemId, aboutLabel); + } +#endif +#endif + + return menu; +} + +void AppFrame::updateFileMenu() { + wxMenu *newFileMenu = makeFileMenu(); + menuBar->Replace(0, newFileMenu, wxT("&File")); + fileMenu = newFileMenu; +} + + void AppFrame::initDeviceParams(SDRDeviceInfo *devInfo) { this->devInfo = devInfo; deviceChanged.store(true); @@ -1570,6 +1590,7 @@ void AppFrame::OnMenu(wxCommandEvent& event) { } wxGetApp().getConfig()->setRecordingPath(recPathDialog.GetPath().ToStdString()); + updateFileMenu(); } else if (event.GetId() == wxID_LOW_PERF) { lowPerfMode = lowPerfMenuItem->IsChecked(); diff --git a/src/AppFrame.h b/src/AppFrame.h index 80336850..57576a88 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -97,6 +97,9 @@ class AppFrame: public wxFrame { AppFrame(); ~AppFrame(); + wxMenu *makeFileMenu(); + void updateFileMenu(); + void initDeviceParams(SDRDeviceInfo *devInfo); void updateDeviceParams(); @@ -221,6 +224,7 @@ class AppFrame: public wxFrame { wxMenuItem *agcMenuItem = nullptr; wxMenuItem *iqSwapMenuItem = nullptr; wxMenuItem *lowPerfMenuItem = nullptr; + wxMenu *fileMenu = nullptr; wxMenu *settingsMenu = nullptr; SoapySDR::ArgInfoList settingArgs; diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 92a4f4c1..067745b9 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -635,8 +635,8 @@ void DemodulatorInstance::startRecording() { std::wstring userLabel = getDemodulatorUserLabel(); - // TODO: Can we support wstring filenames for user labels? - std::string userLabelStr(userLabel.begin(), userLabel.end()); + wxString userLabelForFileName(userLabel); + std::string userLabelStr = userLabelForFileName.ToStdString(); if (!userLabelStr.empty()) { fileName << userLabelStr; diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index a21dc781..cd564084 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -165,22 +165,29 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstancePtr demod, RGBA4f color, glColor4f(1.0, 1.0, 1.0, 0.8f); - std::string demodLabel = demod->getLabel(); - - if (demod->isMuted()) { - demodLabel = std::string("[M] ") + demodLabel; - } else if (isSolo) { - demodLabel = std::string("[S] ") + demodLabel; - } - + std::string demodLabel, demodPrefix; + if (demod->isDeltaLock()) { - demodLabel.append(" [V]"); + demodPrefix.append("V"); } if (isRecording) { - demodLabel.append(" [R]"); + demodPrefix.append("R"); } + if (demod->isMuted()) { + demodPrefix.append("M"); + } else if (isSolo) { + demodPrefix.append("S"); + } + + // Set the prefix + if (!demodPrefix.empty()) { + demodLabel = "[" + demodPrefix + "] "; + } + // Append the default label + demodLabel.append(demod->getLabel()); + if (demod->getDemodulatorType() == "USB") { GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true); } else if (demod->getDemodulatorType() == "LSB") { From 1dbfcedcd2e5908b6c06199f0b2ef81ee9887658 Mon Sep 17 00:00:00 2001 From: vsonnier Date: Sat, 6 Jan 2018 14:19:32 +0100 Subject: [PATCH 14/22] Fix to record below-squelch levels as audio silence --- src/demod/DemodulatorThread.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index f3f36cf6..539672f5 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -224,10 +224,18 @@ void DemodulatorThread::run() { squelchBreak = false; } } - + + // Capture audioSinkOutputQueue state in a local variable + DemodulatorThreadOutputQueuePtr localAudioSinkOutputQueue = nullptr; + { + std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); + localAudioSinkOutputQueue = audioSinkOutputQueue; + } + if (audioOutputQueue != nullptr && ati && ati->data.size() && !squelched) { - std::vector::iterator data_i; + ati->peak = 0; + for (auto data_i : ati->data) { float p = fabs(data_i); if (p > ati->peak) { @@ -235,6 +243,16 @@ void DemodulatorThread::run() { } } } else if (ati) { + //squelch situation, but recording is on-going, so record "silence" to AudioSink: + if (localAudioSinkOutputQueue != nullptr) { + + //Zero the ati samples + ati->peak = 0; + ati->data.assign(ati->data.size(), 0.0f); + + localAudioSinkOutputQueue->try_push(ati); + } + ati = nullptr; } @@ -316,13 +334,6 @@ void DemodulatorThread::run() { } } - // Capture audioSinkOutputQueue state in a local - DemodulatorThreadOutputQueuePtr localAudioSinkOutputQueue = nullptr; - { - std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); - localAudioSinkOutputQueue = audioSinkOutputQueue; - } - if (ati != nullptr) { if (!muted.load() && (!wxGetApp().getSoloMode() || (demodInstance == wxGetApp().getDemodMgr().getLastActiveDemodulator().get()))) { //non-blocking push needed for audio out @@ -334,7 +345,7 @@ void DemodulatorThread::run() { } if (localAudioSinkOutputQueue != nullptr) { - if (!audioSinkOutputQueue->try_push(ati)) { + if (!localAudioSinkOutputQueue->try_push(ati)) { std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; } } From 8daadc3603288061b1dc899b7772319b39d004bc Mon Sep 17 00:00:00 2001 From: vsonnier Date: Sat, 6 Jan 2018 15:09:33 +0100 Subject: [PATCH 15/22] Missing trace in AudioSink queue saturation --- src/demod/DemodulatorThread.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 539672f5..b71ea982 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -250,7 +250,9 @@ void DemodulatorThread::run() { ati->peak = 0; ati->data.assign(ati->data.size(), 0.0f); - localAudioSinkOutputQueue->try_push(ati); + if (!localAudioSinkOutputQueue->try_push(ati)) { + std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; + } } ati = nullptr; From fc0f20f07daac6d3b6d7c9c382770588aa091377 Mon Sep 17 00:00:00 2001 From: vsonnier Date: Sun, 7 Jan 2018 08:55:38 +0100 Subject: [PATCH 16/22] Removed recording duration limitation: - Limit WAV size to 2GB for maximum compatibility, - Continue recording on another file when size gets too big (XXX_001.wav, then XXX_002.wav and so on) - The sequence assure up to 2000GB worth of recording which should be enough - Changed file pattern to international Year.Month.Day so its recognizable whether you are English of French or whatever :) --- src/audio/AudioFile.cpp | 22 ++++-- src/audio/AudioFile.h | 2 +- src/audio/AudioFileWAV.cpp | 107 +++++++++++++++++++++--------- src/audio/AudioFileWAV.h | 11 +++ src/demod/DemodulatorInstance.cpp | 3 +- 5 files changed, 106 insertions(+), 39 deletions(-) diff --git a/src/audio/AudioFile.cpp b/src/audio/AudioFile.cpp index f3e1a34c..1fefed3b 100644 --- a/src/audio/AudioFile.cpp +++ b/src/audio/AudioFile.cpp @@ -3,6 +3,8 @@ #include "AudioFile.h" #include "CubicSDR.h" +#include +#include AudioFile::AudioFile() { @@ -16,7 +18,8 @@ void AudioFile::setOutputFileName(std::string filename) { filenameBase = filename; } -std::string AudioFile::getOutputFileName() { +std::string AudioFile::getOutputFileName(int sequenceNumber) { + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); // Strip any invalid characters from the name @@ -30,16 +33,23 @@ std::string AudioFile::getOutputFileName() { } // Create output file name - std::string outputFileName = recPath + filePathSeparator + filenameBaseSafe + "." + getExtension(); + std::stringstream outputFileName; + outputFileName << recPath << filePathSeparator << filenameBaseSafe; + + if (sequenceNumber > 0) { + outputFileName << "_" << std::setfill('0') << std::setw(3) << sequenceNumber; + } int idx = 0; // If the file exists; then find the next non-existing file in sequence. - while (FILE *file = fopen(outputFileName.c_str(), "r")) { + std::string fileNameCandidate = outputFileName.str(); + + while (FILE *file = fopen((fileNameCandidate + "." + getExtension()).c_str(), "r")) { fclose(file); - outputFileName = recPath + filePathSeparator + filenameBaseSafe + "-" + std::to_string(++idx) + "." + getExtension(); + fileNameCandidate = outputFileName.str() + "-" + std::to_string(++idx); } - - return outputFileName; + + return fileNameCandidate + "." + getExtension(); } diff --git a/src/audio/AudioFile.h b/src/audio/AudioFile.h index c8636b84..46fc1bdc 100644 --- a/src/audio/AudioFile.h +++ b/src/audio/AudioFile.h @@ -14,7 +14,7 @@ class AudioFile virtual void setOutputFileName(std::string filename); virtual std::string getExtension() = 0; - virtual std::string getOutputFileName(); + virtual std::string getOutputFileName(int sequenceNumber = 0); virtual bool writeToFile(AudioThreadInputPtr input) = 0; virtual bool closeFile() = 0; diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp index a8d5fd2a..d60cfee3 100644 --- a/src/audio/AudioFileWAV.cpp +++ b/src/audio/AudioFileWAV.cpp @@ -3,6 +3,8 @@ #include "AudioFileWAV.h" +//limit file size to 2GB (- margin) for maximum compatibility. +#define MAX_WAV_FILE_SIZE (0x7FFFFFFF - 1024) // Simple endian io read/write handling from // http://www.cplusplus.com/forum/beginner/31584/#msg171056 @@ -61,44 +63,34 @@ std::string AudioFileWAV::getExtension() bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) { if (!outputFileStream.is_open()) { - std::string ofName = getOutputFileName(); + std::string ofName = getOutputFileName(currentSequenceNumber); outputFileStream.open(ofName.c_str(), std::ios::binary); - // Based on simple wav file output code from - // http://www.cplusplus.com/forum/beginner/166954/ - - // Write the wav file headers - outputFileStream << "RIFF----WAVEfmt "; // (chunk size to be filled in later) - write_word(outputFileStream, 16, 4); // no extension data - write_word(outputFileStream, 1, 2); // PCM - integer samples - write_word(outputFileStream, input->channels, 2); // channels - write_word(outputFileStream, input->sampleRate, 4); // samples per second (Hz) - write_word(outputFileStream, (input->sampleRate * 16 * input->channels) / 8, 4); // (Sample Rate * BitsPerSample * Channels) / 8 - write_word(outputFileStream, input->channels * 2, 2); // data block size (size of integer samples, one for each channel, in bytes) - write_word(outputFileStream, 16, 2); // number of bits per sample (use a multiple of 8) - - // Write the data chunk header - dataChunkPos = outputFileStream.tellp(); - outputFileStream << "data----"; // (chunk size to be filled in later) + writeHeaderToFileStream(input); } - // Prevent clipping - float intScale = (input->peak < 1.0) ? 32767.0f : (32767.0f / input->peak); + size_t maxRoomInCurrentFileInSamples = getMaxWritableNumberOfSamples(input); - if (input->channels == 1) { - for (size_t i = 0, iMax = input->data.size(); i < iMax; i++) { - write_word(outputFileStream, int(input->data[i] * intScale), 2); - } - } - else if (input->channels == 2) { - for (size_t i = 0, iMax = input->data.size() / 2; i < iMax; i++) { - write_word(outputFileStream, int(input->data[i * 2] * intScale), 2); - write_word(outputFileStream, int(input->data[i * 2 + 1] * intScale), 2); - } - } + if (maxRoomInCurrentFileInSamples >= input->data.size()) { + writePayloadToFileStream(input, 0, input->data.size()); + } + else { + //we complete the current file and open another: + writePayloadToFileStream(input, 0, maxRoomInCurrentFileInSamples); + + closeFile(); - // TODO: Periodically update the RIFF/data chunk size in case of crash? + // Open a new file with the next sequence number, and dump the rest of samples in it. + currentSequenceNumber++; + currentFileSize = 0; + + std::string ofName = getOutputFileName(currentSequenceNumber); + outputFileStream.open(ofName.c_str(), std::ios::binary); + + writeHeaderToFileStream(input); + writePayloadToFileStream(input, maxRoomInCurrentFileInSamples, input->data.size()); + } return true; } @@ -121,3 +113,56 @@ bool AudioFileWAV::closeFile() return true; } + +void AudioFileWAV::writeHeaderToFileStream(AudioThreadInputPtr input) { + + // Based on simple wav file output code from + // http://www.cplusplus.com/forum/beginner/166954/ + + // Write the wav file headers + outputFileStream << "RIFF----WAVEfmt "; // (chunk size to be filled in later) + write_word(outputFileStream, 16, 4); // no extension data + write_word(outputFileStream, 1, 2); // PCM - integer samples + write_word(outputFileStream, input->channels, 2); // channels + write_word(outputFileStream, input->sampleRate, 4); // samples per second (Hz) + write_word(outputFileStream, (input->sampleRate * 16 * input->channels) / 8, 4); // (Sample Rate * BitsPerSample * Channels) / 8 + write_word(outputFileStream, input->channels * 2, 2); // data block size (size of integer samples, one for each channel, in bytes) + write_word(outputFileStream, 16, 2); // number of bits per sample (use a multiple of 8) + + // Write the data chunk header + dataChunkPos = outputFileStream.tellp(); + currentFileSize = dataChunkPos; + outputFileStream << "data----"; // (chunk size to be filled in later) +} + +void AudioFileWAV::writePayloadToFileStream(AudioThreadInputPtr input, size_t startInputPosition, size_t endInputPosition) { + + // Prevent clipping + float intScale = (input->peak < 1.0) ? 32767.0f : (32767.0f / input->peak); + + if (input->channels == 1) { + for (size_t i = startInputPosition, iMax = endInputPosition; i < iMax; i++) { + + write_word(outputFileStream, int(input->data[i] * intScale), 2); + + currentFileSize += 2; + } + } + else if (input->channels == 2) { + for (size_t i = startInputPosition, iMax = endInputPosition / 2; i < iMax; i++) { + + write_word(outputFileStream, int(input->data[i * 2] * intScale), 2); + write_word(outputFileStream, int(input->data[i * 2 + 1] * intScale), 2); + + currentFileSize += 4; + } + } +} + +size_t AudioFileWAV::getMaxWritableNumberOfSamples(AudioThreadInputPtr input) { + + long long remainingBytesInFile = (long long)(MAX_WAV_FILE_SIZE) - currentFileSize; + + return (size_t)(remainingBytesInFile / (input->channels * 2)); + +} diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h index d64f7640..883b893e 100644 --- a/src/audio/AudioFileWAV.h +++ b/src/audio/AudioFileWAV.h @@ -21,4 +21,15 @@ class AudioFileWAV : public AudioFile { protected: std::ofstream outputFileStream; size_t dataChunkPos; + long long currentFileSize = 0; + int currentSequenceNumber = 0; + +private: + + size_t getMaxWritableNumberOfSamples(AudioThreadInputPtr input); + + void writeHeaderToFileStream(AudioThreadInputPtr input); + + //write [startInputPosition; endInputPosition[ samples from input into the file. + void writePayloadToFileStream(AudioThreadInputPtr input, size_t startInputPosition, size_t endInputPosition); }; \ No newline at end of file diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 067745b9..743c12a1 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -648,7 +648,8 @@ void DemodulatorInstance::startRecording() { // fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); char timeStr[512]; - strftime(timeStr, sizeof(timeStr), "%d-%m-%Y_%H-%M-%S", <m); + //International format: Year.Month.Day, also lexicographically sortable + strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", <m); fileName << "_" << timeStr; From 26deefd60674a9ef2f0bbfe3b79dff0b849dfea9 Mon Sep 17 00:00:00 2001 From: vsonnier Date: Sat, 13 Jan 2018 11:50:08 +0100 Subject: [PATCH 17/22] Added #583: add periodic file generation, plus other options: - Added a Recording menu, git commit -m Added --- src/AppConfig.cpp | 32 ++++++ src/AppConfig.h | 13 ++- src/AppFrame.cpp | 185 +++++++++++++++++++++++++----- src/AppFrame.h | 18 ++- src/audio/AudioFile.cpp | 7 +- src/audio/AudioFile.h | 2 +- src/audio/AudioFileWAV.cpp | 42 ++++++- src/audio/AudioFileWAV.h | 10 +- src/audio/AudioSinkFileThread.cpp | 110 ++++++++++++++++++ src/audio/AudioSinkFileThread.h | 30 ++++- src/audio/AudioSinkThread.h | 4 +- src/audio/AudioThread.h | 2 + src/demod/DemodulatorInstance.cpp | 22 ++-- src/demod/DemodulatorThread.cpp | 55 ++++----- 14 files changed, 441 insertions(+), 91 deletions(-) diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp index dc68da53..f85ab7c0 100644 --- a/src/AppConfig.cpp +++ b/src/AppConfig.cpp @@ -535,6 +535,24 @@ bool AppConfig::verifyRecordingPath() { return true; } + +void AppConfig::setRecordingSquelchOption(int enumChoice) { + recordingSquelchOption = enumChoice; +} + +int AppConfig::getRecordingSquelchOption() { + return recordingSquelchOption; +} + +void AppConfig::setRecordingFileTimeLimit(int nbSeconds) { + recordingFileTimeLimitSeconds = nbSeconds; +} + +int AppConfig::getRecordingFileTimeLimit() { + return recordingFileTimeLimitSeconds; +} + + void AppConfig::setConfigName(std::string configName) { this->configName = configName; } @@ -588,8 +606,11 @@ bool AppConfig::save() { *window_node->newChild("bookmark_visible") = bookmarksVisible.load(); } + //Recording settings: DataNode *rec_node = cfg.rootNode()->newChild("recording"); *rec_node->newChild("path") = recordingPath; + *rec_node->newChild("squelch") = recordingSquelchOption; + *rec_node->newChild("file_time_limit") = recordingFileTimeLimitSeconds; DataNode *devices_node = cfg.rootNode()->newChild("devices"); @@ -773,6 +794,7 @@ bool AppConfig::load() { } } + //Recording settings: if (cfg.rootNode()->hasAnother("recording")) { DataNode *rec_node = cfg.rootNode()->getNext("recording"); @@ -780,6 +802,16 @@ bool AppConfig::load() { DataNode *rec_path = rec_node->getNext("path"); recordingPath = rec_path->element()->toString(); } + + if (rec_node->hasAnother("squelch")) { + DataNode *rec_squelch = rec_node->getNext("squelch"); + rec_squelch->element()->get(recordingSquelchOption); + } + + if (rec_node->hasAnother("file_time_limit")) { + DataNode *rec_file_time_limit = rec_node->getNext("file_time_limit"); + rec_file_time_limit->element()->get(recordingFileTimeLimitSeconds); + } } if (cfg.rootNode()->hasAnother("devices")) { diff --git a/src/AppConfig.h b/src/AppConfig.h index 51212726..c1fe3924 100644 --- a/src/AppConfig.h +++ b/src/AppConfig.h @@ -138,10 +138,16 @@ class AppConfig { void setBookmarksVisible(bool state); bool getBookmarksVisible(); + //Recording settings: void setRecordingPath(std::string recPath); std::string getRecordingPath(); + bool verifyRecordingPath(); + + void setRecordingSquelchOption(int enumChoice); + int getRecordingSquelchOption(); - bool verifyRecordingPath(); + void setRecordingFileTimeLimit(int nbSeconds); + int getRecordingFileTimeLimit(); #if USE_HAMLIB int getRigModel(); @@ -189,7 +195,10 @@ class AppConfig { std::atomic_int dbOffset; std::vector manualDevices; std::atomic_bool bookmarksVisible; - std::string recordingPath; + + std::string recordingPath = ""; + int recordingSquelchOption = 0; + int recordingFileTimeLimitSeconds = 0; #if USE_HAMLIB std::atomic_int rigModel, rigRate; std::string rigPort; diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 56566045..77d8fce7 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -18,7 +18,7 @@ #include #include -#include "AudioThread.h" +#include "AudioSinkFileThread.h" #include "CubicSDR.h" #include "DataTree.h" #include "ColorTheme.h" @@ -402,10 +402,8 @@ AppFrame::AppFrame() : // Make a menubar menuBar = new wxMenuBar; - - fileMenu = makeFileMenu(); - - menuBar->Append(fileMenu, wxT("&File")); + + menuBar->Append(makeFileMenu(), wxT("&File")); settingsMenu = new wxMenu; @@ -494,6 +492,11 @@ AppFrame::AppFrame() : menuBar->Append(audioSampleRateMenu, wxT("Audio &Sample Rate")); + //Add a Recording menu + menuBar->Append(makeRecordingMenu(), wxT("Recordin&g")); + // + updateRecordingMenu(); + //Add Display menu displayMenu = new wxMenu; @@ -708,14 +711,6 @@ wxMenu *AppFrame::makeFileMenu() { menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); menu->AppendSeparator(); - std::string recPath = wxGetApp().getConfig()->getRecordingPath(); - if (recPath.length() > 32) { - recPath = "..." + recPath.substr(recPath.length() - 32, 32); - } - - menu->Append(wxID_RECORDING_PATH, getSettingsLabel("Set Recording Path", recPath.empty() ? "" : recPath)); - - menu->AppendSeparator(); menu->Append(wxID_OPEN, "&Open Session"); menu->Append(wxID_SAVE, "&Save Session"); menu->Append(wxID_SAVEAS, "Save Session &As.."); @@ -741,15 +736,88 @@ wxMenu *AppFrame::makeFileMenu() { #endif #endif + fileMenu = menu; + return menu; } -void AppFrame::updateFileMenu() { - wxMenu *newFileMenu = makeFileMenu(); - menuBar->Replace(0, newFileMenu, wxT("&File")); - fileMenu = newFileMenu; +wxMenu *AppFrame::makeRecordingMenu() { + + recordingMenuItems.clear(); + + wxMenu *menu = new wxMenu; + + recordingMenuItems[wxID_RECORDING_PATH] = menu->Append(wxID_RECORDING_PATH, getSettingsLabel("Set Recording Path", "")); + + menu->AppendSeparator(); + + //Squelch options as sub-menu: + wxMenu *subMenu = new wxMenu; + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE] = menu->AppendSubMenu(subMenu, "Squelch"); + + recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_SILENCE, "Record Silence", + "Record below squelch-break audio as silence, i.e records as the user may hear."); + recordingMenuItems[wxID_RECORDING_SQUELCH_SKIP] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_SKIP, "Skip Silence", + "Do not record below squelch-break audio, i.e squelch-break audio parts are packed together."); + recordingMenuItems[wxID_RECORDING_SQUELCH_ALWAYS] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_ALWAYS, "Record Always", + "Record everything irrespective of the squelch level."); + + recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT] = menu->Append(wxID_RECORDING_FILE_TIME_LIMIT, getSettingsLabel("File time limit", ""), + "Creates a new file automatically, each time the recording lasts longer than the limit, named according to the current time."); + + recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true); + + recordingMenu = menu; + + return menu; } +void AppFrame::updateRecordingMenu() { + + // Recording path: + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + if (recPath.length() > 32) { + recPath = "..." + recPath.substr(recPath.length() - 32, 32); + } + + recordingMenuItems[wxID_RECORDING_PATH]->SetItemLabel(getSettingsLabel("Set Recording Path", recPath.empty() ? "" : recPath)); + + //Squelch options: + int squelchEnumValue = wxGetApp().getConfig()->getRecordingSquelchOption(); + + if (squelchEnumValue == AudioSinkFileThread::SQUELCH_RECORD_SILENCE) { + + recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true); + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Silence")); + + } else if (squelchEnumValue == AudioSinkFileThread::SQUELCH_SKIP_SILENCE) { + + recordingMenuItems[wxID_RECORDING_SQUELCH_SKIP]->Check(true); + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Skip Silence")); + + } else if (squelchEnumValue == AudioSinkFileThread::SQUELCH_RECORD_ALWAYS) { + + recordingMenuItems[wxID_RECORDING_SQUELCH_ALWAYS]->Check(true); + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Always")); + } + else { + recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true); + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Silence")); + + } + + //File time limit: + int fileTimeLimitSeconds = wxGetApp().getConfig()->getRecordingFileTimeLimit(); + + if (fileTimeLimitSeconds <= 0) { + + recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT]->SetItemLabel(getSettingsLabel("File time limit","")); + } + else { + recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT]->SetItemLabel(getSettingsLabel("File time limit", + std::to_string(fileTimeLimitSeconds), "s")); + } +} void AppFrame::initDeviceParams(SDRDeviceInfo *devInfo) { this->devInfo = devInfo; @@ -1512,6 +1580,73 @@ bool AppFrame::actionOnMenuLoadSave(wxCommandEvent& event) { return false; } +bool AppFrame::actionOnMenuRecording(wxCommandEvent& event) { + + 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 true; + } + + wxGetApp().getConfig()->setRecordingPath(recPathDialog.GetPath().ToStdString()); + + updateRecordingMenu(); + return true; + + } + else if (event.GetId() == wxID_RECORDING_SQUELCH_SILENCE) { + + wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_RECORD_SILENCE); + + updateRecordingMenu(); + return true; + } + else if (event.GetId() == wxID_RECORDING_SQUELCH_SKIP) { + + wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_SKIP_SILENCE); + + updateRecordingMenu(); + return true; + } + else if (event.GetId() == wxID_RECORDING_SQUELCH_ALWAYS) { + + wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_RECORD_ALWAYS); + + updateRecordingMenu(); + return true; + } + else if (event.GetId() == wxID_RECORDING_FILE_TIME_LIMIT) { + + int currentFileLimitSeconds = wxGetApp().getConfig()->getRecordingFileTimeLimit(); + + long newFileLimit = wxGetNumberFromUser(wxString("\nFile time limit:\n") + + "\nCreates a new file automatically, each time the recording lasts longer than the limit, named according to the current time.\n\n " + + + "min: 0 s (no limit)" + + ", max: 36000 s (10 hours)\n", + "Time in seconds", + "File Time Limit", + //If a manual sample rate has already been input, recall this one. + currentFileLimitSeconds > 0 ? currentFileLimitSeconds : 0, + 0, + 36000, + this); + + if (newFileLimit != -1) { + + wxGetApp().getConfig()->setRecordingFileTimeLimit((int)newFileLimit); + + updateRecordingMenu(); + } + + return true; + } + + return false; +} + bool AppFrame::actionOnMenuRig(wxCommandEvent& event) { bool bManaged = false; @@ -1667,17 +1802,6 @@ 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()); - updateFileMenu(); - } else if (event.GetId() == wxID_LOW_PERF) { lowPerfMode = lowPerfMenuItem->IsChecked(); wxGetApp().getConfig()->setLowPerfMode(lowPerfMode); @@ -1742,9 +1866,12 @@ void AppFrame::OnMenu(wxCommandEvent& event) { else if (actionOnMenuAudioSampleRate(event)) { return; } - else if (actionOnMenuDisplay(event)) { + else if (actionOnMenuRecording(event)) { return; } + else if (actionOnMenuDisplay(event)) { + return; + } //Optional : Rig else if (actionOnMenuRig(event)) { return; diff --git a/src/AppFrame.h b/src/AppFrame.h index 6f31a04d..081db1c5 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -42,7 +42,6 @@ #define wxID_LOW_PERF 2011 #define wxID_SET_DB_OFFSET 2012 #define wxID_ABOUT_CUBICSDR 2013 -#define wxID_RECORDING_PATH 2014 #define wxID_OPEN_BOOKMARKS 2020 #define wxID_SAVE_BOOKMARKS 2021 @@ -77,6 +76,13 @@ #define wxID_DEVICE_ID 3500 +#define wxID_RECORDING_PATH 8500 +#define wxID_RECORDING_SQUELCH_BASE 8501 +#define wxID_RECORDING_SQUELCH_SILENCE 8502 +#define wxID_RECORDING_SQUELCH_SKIP 8503 +#define wxID_RECORDING_SQUELCH_ALWAYS 8504 +#define wxID_RECORDING_FILE_TIME_LIMIT 8505 + #define wxID_AUDIO_BANDWIDTH_BASE 9000 #define wxID_AUDIO_DEVICE_MULTIPLIER 50 @@ -103,7 +109,9 @@ class AppFrame: public wxFrame { ~AppFrame(); wxMenu *makeFileMenu(); - void updateFileMenu(); + + wxMenu *makeRecordingMenu(); + void updateRecordingMenu(); void initDeviceParams(SDRDeviceInfo *devInfo); void updateDeviceParams(); @@ -178,6 +186,7 @@ class AppFrame: public wxFrame { bool actionOnMenuAudioSampleRate(wxCommandEvent& event); bool actionOnMenuDisplay(wxCommandEvent& event); bool actionOnMenuLoadSave(wxCommandEvent& event); + bool actionOnMenuRecording(wxCommandEvent& event); bool actionOnMenuRig(wxCommandEvent& event); wxString getSettingsLabel(const std::string& settingsName, @@ -221,6 +230,10 @@ class AppFrame: public wxFrame { std::map settingsMenuItems; std::map audioSampleRateMenuItems; + + // + std::map recordingMenuItems; + std::map directSamplingMenuItems; wxMenuBar *menuBar; @@ -231,6 +244,7 @@ class AppFrame: public wxFrame { wxMenuItem *lowPerfMenuItem = nullptr; wxMenu *fileMenu = nullptr; wxMenu *settingsMenu = nullptr; + wxMenu *recordingMenu = nullptr; SoapySDR::ArgInfoList settingArgs; int settingsIdMax; diff --git a/src/audio/AudioFile.cpp b/src/audio/AudioFile.cpp index 1fefed3b..6b15db7e 100644 --- a/src/audio/AudioFile.cpp +++ b/src/audio/AudioFile.cpp @@ -3,7 +3,6 @@ #include "AudioFile.h" #include "CubicSDR.h" -#include #include AudioFile::AudioFile() { @@ -18,7 +17,7 @@ void AudioFile::setOutputFileName(std::string filename) { filenameBase = filename; } -std::string AudioFile::getOutputFileName(int sequenceNumber) { +std::string AudioFile::getOutputFileName() { std::string recPath = wxGetApp().getConfig()->getRecordingPath(); @@ -36,10 +35,6 @@ std::string AudioFile::getOutputFileName(int sequenceNumber) { std::stringstream outputFileName; outputFileName << recPath << filePathSeparator << filenameBaseSafe; - if (sequenceNumber > 0) { - outputFileName << "_" << std::setfill('0') << std::setw(3) << sequenceNumber; - } - int idx = 0; // If the file exists; then find the next non-existing file in sequence. diff --git a/src/audio/AudioFile.h b/src/audio/AudioFile.h index 46fc1bdc..c8636b84 100644 --- a/src/audio/AudioFile.h +++ b/src/audio/AudioFile.h @@ -14,7 +14,7 @@ class AudioFile virtual void setOutputFileName(std::string filename); virtual std::string getExtension() = 0; - virtual std::string getOutputFileName(int sequenceNumber = 0); + virtual std::string getOutputFileName(); virtual bool writeToFile(AudioThreadInputPtr input) = 0; virtual bool closeFile() = 0; diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp index d60cfee3..ad4078f1 100644 --- a/src/audio/AudioFileWAV.cpp +++ b/src/audio/AudioFileWAV.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-2.0+ #include "AudioFileWAV.h" +#include "CubicSDR.h" +#include //limit file size to 2GB (- margin) for maximum compatibility. #define MAX_WAV_FILE_SIZE (0x7FFFFFFF - 1024) @@ -63,7 +65,7 @@ std::string AudioFileWAV::getExtension() bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) { if (!outputFileStream.is_open()) { - std::string ofName = getOutputFileName(currentSequenceNumber); + std::string ofName = getOutputFileName(); outputFileStream.open(ofName.c_str(), std::ios::binary); @@ -85,7 +87,7 @@ bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) currentSequenceNumber++; currentFileSize = 0; - std::string ofName = getOutputFileName(currentSequenceNumber); + std::string ofName = getOutputFileName(); outputFileStream.open(ofName.c_str(), std::ios::binary); writeHeaderToFileStream(input); @@ -166,3 +168,39 @@ size_t AudioFileWAV::getMaxWritableNumberOfSamples(AudioThreadInputPtr input) { return (size_t)(remainingBytesInFile / (input->channels * 2)); } + +std::string AudioFileWAV::getOutputFileName() { + + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + + // Strip any invalid characters from the name + std::string stripChars("<>:\"/\\|?*"); + std::string filenameBaseSafe = filenameBase; + + for (size_t i = 0, iMax = filenameBaseSafe.length(); i < iMax; i++) { + if (stripChars.find(filenameBaseSafe[i]) != std::string::npos) { + filenameBaseSafe.replace(i, 1, "_"); + } + } + + // Create output file name + std::stringstream outputFileName; + outputFileName << recPath << filePathSeparator << filenameBaseSafe; + + //customized part: append a sequence number. + if (currentSequenceNumber > 0) { + outputFileName << "_" << std::setfill('0') << std::setw(3) << currentSequenceNumber; + } + + int idx = 0; + + // If the file exists; then find the next non-existing file in sequence. + std::string fileNameCandidate = outputFileName.str(); + + while (FILE *file = fopen((fileNameCandidate + "." + getExtension()).c_str(), "r")) { + fclose(file); + fileNameCandidate = outputFileName.str() + "-" + std::to_string(++idx); + } + + return fileNameCandidate + "." + getExtension(); +} \ No newline at end of file diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h index 883b893e..dcd102de 100644 --- a/src/audio/AudioFileWAV.h +++ b/src/audio/AudioFileWAV.h @@ -13,10 +13,14 @@ class AudioFileWAV : public AudioFile { AudioFileWAV(); ~AudioFileWAV(); - std::string getExtension(); + //override of the base method to generate multi-part + //WAV to overcome the WAV format size limit. + virtual std::string getOutputFileName(); - bool writeToFile(AudioThreadInputPtr input); - bool closeFile(); + virtual std::string getExtension(); + + virtual bool writeToFile(AudioThreadInputPtr input); + virtual bool closeFile(); protected: std::ofstream outputFileStream; diff --git a/src/audio/AudioSinkFileThread.cpp b/src/audio/AudioSinkFileThread.cpp index 2b00cdb8..f17a5b66 100644 --- a/src/audio/AudioSinkFileThread.cpp +++ b/src/audio/AudioSinkFileThread.cpp @@ -2,6 +2,9 @@ // SPDX-License-Identifier: GPL-2.0+ #include "AudioSinkFileThread.h" +#include + +#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000) AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() { @@ -17,6 +20,57 @@ void AudioSinkFileThread::sink(AudioThreadInputPtr input) { if (!audioFileHandler) { return; } + + //by default, always write something + bool isSomethingToWrite = true; + + if (input->is_squelch_active) { + + if (squelchOption == SQUELCH_RECORD_SILENCE) { + + //patch with "silence" + input->data.assign(input->data.size(), 0.0f); + input->peak = 0.0f; + } + else if (squelchOption == SQUELCH_SKIP_SILENCE) { + isSomethingToWrite = false; + } + } + + //else, nothing to do record as if squelch was not enabled. + + if (!isSomethingToWrite) { + return; + } + + if (fileTimeLimit > 0) { + durationMeasurement.update(); + + //duration exeeded, close this file and create another + //with "now" as timestamp. + if (durationMeasurement.getSeconds() > fileTimeLimit) { + + audioFileHandler->closeFile(); + + //initialize the filename of the AudioFile with the current time + time_t t = std::time(nullptr); + tm ltm = *std::localtime(&t); + + // GCC 5+ + // fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); + + char timeStr[512]; + //International format: Year.Month.Day, also lexicographically sortable + strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", <m); + + audioFileHandler->setOutputFileName(fileNameBase + std::string("_") + timeStr); + + //reset duration counter + durationMeasurement.start(); + //the following writeToFile will take care of creating another file. + } + } + // forward to output file handler audioFileHandler->writeToFile(input); } @@ -28,8 +82,64 @@ void AudioSinkFileThread::inputChanged(AudioThreadInput oldProps, AudioThreadInp } audioFileHandler->closeFile(); + + //reset duration counter + durationMeasurement.start(); +} + +void AudioSinkFileThread::setAudioFileNameBase(const std::string& baseName) { + + fileNameBase = baseName; } void AudioSinkFileThread::setAudioFileHandler(AudioFile * output) { audioFileHandler = output; + + //initialize the filename of the AudioFile with the current time + time_t t = std::time(nullptr); + tm ltm = *std::localtime(&t); + + // GCC 5+ + // fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); + + char timeStr[512]; + //International format: Year.Month.Day, also lexicographically sortable + strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", <m); + + audioFileHandler->setOutputFileName(fileNameBase + std::string("_") + timeStr); + + // reset Timer + durationMeasurement.start(); +} + +void AudioSinkFileThread::setSquelchOption(int squelchOptEnumValue) { + + if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_SILENCE) { + + squelchOption = AudioSinkFileThread::SQUELCH_RECORD_SILENCE; + + } + else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_SKIP_SILENCE) { + + squelchOption = AudioSinkFileThread::SQUELCH_SKIP_SILENCE; + } + else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_ALWAYS) { + + squelchOption = AudioSinkFileThread::SQUELCH_RECORD_ALWAYS; + + } + else { + squelchOption = AudioSinkFileThread::SQUELCH_SKIP_SILENCE; + } +} + +// Time limit +void AudioSinkFileThread::setFileTimeLimit(int nbSeconds) { + + if (nbSeconds > 0) { + fileTimeLimit = nbSeconds; + } + else { + fileTimeLimit = 0; + } } diff --git a/src/audio/AudioSinkFileThread.h b/src/audio/AudioSinkFileThread.h index 1ba4c4c7..f0bdb739 100644 --- a/src/audio/AudioSinkFileThread.h +++ b/src/audio/AudioSinkFileThread.h @@ -5,6 +5,7 @@ #include "AudioSinkThread.h" #include "AudioFile.h" +#include "Timer.h" class AudioSinkFileThread : public AudioSinkThread { @@ -12,13 +13,38 @@ class AudioSinkFileThread : public AudioSinkThread { AudioSinkFileThread(); ~AudioSinkFileThread(); - void sink(AudioThreadInputPtr input); - void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); + enum SquelchOption { + SQUELCH_RECORD_SILENCE = 0, // default value, record as a user would hear it. + SQUELCH_SKIP_SILENCE = 1, // skip below-squelch level. + SQUELCH_RECORD_ALWAYS = 2, // record irrespective of the squelch level. + SQUELCH_RECORD_MAX + }; + + virtual void sink(AudioThreadInputPtr input); + virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); void setAudioFileHandler(AudioFile *output); + void setAudioFileNameBase(const std::string& baseName); + + //Squelch + void setSquelchOption(int squelchOptEnumValue); + + // Time limit + void setFileTimeLimit(int nbSeconds); + protected: + + std::string fileNameBase; + AudioFile *audioFileHandler = nullptr; + SquelchOption squelchOption = SQUELCH_RECORD_SILENCE; + int fileTimeLimit = 0; + + int fileTimeDurationSeconds = -1; + + Timer durationMeasurement; + }; diff --git a/src/audio/AudioSinkThread.h b/src/audio/AudioSinkThread.h index b862b0df..754c75d4 100644 --- a/src/audio/AudioSinkThread.h +++ b/src/audio/AudioSinkThread.h @@ -12,8 +12,8 @@ class AudioSinkThread : public IOThread { AudioSinkThread(); virtual ~AudioSinkThread(); - virtual void run(); - virtual void terminate(); + virtual void run(); + virtual void terminate(); virtual void sink(AudioThreadInputPtr input) = 0; virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) = 0; diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index d37593b3..540a8b20 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -21,6 +21,8 @@ class AudioThreadInput { int channels; float peak; int type; + boolean is_squelch_active = false; + std::vector data; AudioThreadInput() : diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 743c12a1..d7014d66 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-2.0+ #include -#include #include #include "DemodulatorInstance.h" @@ -628,9 +627,6 @@ void DemodulatorInstance::startRecording() { AudioSinkFileThread *newSinkThread = new AudioSinkFileThread(); AudioFileWAV *afHandler = new AudioFileWAV(); - time_t t = std::time(nullptr); - tm ltm = *std::localtime(&t); - std::stringstream fileName; std::wstring userLabel = getDemodulatorUserLabel(); @@ -643,17 +639,13 @@ void DemodulatorInstance::startRecording() { } else { fileName << getLabel(); } - - // GCC 5+ - // fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); - - char timeStr[512]; - //International format: Year.Month.Day, also lexicographically sortable - strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", <m); - fileName << "_" << timeStr; - - - afHandler->setOutputFileName(fileName.str()); + + newSinkThread->setAudioFileNameBase(fileName.str()); + + //attach options: + newSinkThread->setSquelchOption(wxGetApp().getConfig()->getRecordingSquelchOption()); + newSinkThread->setFileTimeLimit(wxGetApp().getConfig()->getRecordingFileTimeLimit()); + newSinkThread->setAudioFileHandler(afHandler); audioSinkThread = newSinkThread; diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index b71ea982..82cb1983 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -232,32 +232,39 @@ void DemodulatorThread::run() { localAudioSinkOutputQueue = audioSinkOutputQueue; } - if (audioOutputQueue != nullptr && ati && ati->data.size() && !squelched) { - - ati->peak = 0; + //compute audio peak: + if (audioOutputQueue != nullptr && ati) { - for (auto data_i : ati->data) { - float p = fabs(data_i); - if (p > ati->peak) { - ati->peak = p; - } - } - } else if (ati) { - //squelch situation, but recording is on-going, so record "silence" to AudioSink: - if (localAudioSinkOutputQueue != nullptr) { + ati->peak = 0; - //Zero the ati samples - ati->peak = 0; - ati->data.assign(ati->data.size(), 0.0f); - - if (!localAudioSinkOutputQueue->try_push(ati)) { - std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; + for (auto data_i : ati->data) { + float p = fabs(data_i); + if (p > ati->peak) { + ati->peak = p; } } + } - ati = nullptr; - } - + //attach squelch flag to samples, to be used by audio sink. + if (ati) { + ati->is_squelch_active = squelched; + } + + //Push to audio sink, if any: + if (ati && localAudioSinkOutputQueue != nullptr) { + + if (!localAudioSinkOutputQueue->try_push(ati)) { + std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; + std::this_thread::yield(); + } + } + + //now we can nullify ati if squelched, to skip the next processing entirely. + if (ati && squelched) { + + ati = nullptr; + } + //At that point, capture the current state of audioVisOutputQueue in a local //variable, and works with it with now on until the next while-turn. DemodulatorThreadOutputQueuePtr localAudioVisOutputQueue = nullptr; @@ -345,12 +352,6 @@ void DemodulatorThread::run() { std::this_thread::yield(); } } - - if (localAudioSinkOutputQueue != nullptr) { - if (!localAudioSinkOutputQueue->try_push(ati)) { - std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; - } - } } DemodulatorThreadControlCommand command; From 4e6197c57929ee9b0f5c3f014131bb7d8b2cc4e2 Mon Sep 17 00:00:00 2001 From: vsonnier Date: Sat, 13 Jan 2018 12:13:41 +0100 Subject: [PATCH 18/22] Fix minor cut-n-paste error in AudioSinkFileThread --- src/audio/AudioSinkFileThread.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/audio/AudioSinkFileThread.cpp b/src/audio/AudioSinkFileThread.cpp index f17a5b66..e49b57b4 100644 --- a/src/audio/AudioSinkFileThread.cpp +++ b/src/audio/AudioSinkFileThread.cpp @@ -115,21 +115,16 @@ void AudioSinkFileThread::setAudioFileHandler(AudioFile * output) { void AudioSinkFileThread::setSquelchOption(int squelchOptEnumValue) { if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_SILENCE) { - squelchOption = AudioSinkFileThread::SQUELCH_RECORD_SILENCE; - } else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_SKIP_SILENCE) { - squelchOption = AudioSinkFileThread::SQUELCH_SKIP_SILENCE; } else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_ALWAYS) { - squelchOption = AudioSinkFileThread::SQUELCH_RECORD_ALWAYS; - } else { - squelchOption = AudioSinkFileThread::SQUELCH_SKIP_SILENCE; + squelchOption = AudioSinkFileThread::SQUELCH_RECORD_SILENCE; } } From 3334538e649d4b5dd28dee4071b67053a56dd497 Mon Sep 17 00:00:00 2001 From: vsonnier Date: Sat, 13 Jan 2018 12:17:02 +0100 Subject: [PATCH 19/22] Fix boolean usage instead of bool (we are not Java, are we ?) --- src/audio/AudioThread.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index 540a8b20..dfb2e5e4 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -21,7 +21,7 @@ class AudioThreadInput { int channels; float peak; int type; - boolean is_squelch_active = false; + bool is_squelch_active = false; std::vector data; From 4c6d7ab4d79c3a509f5702278aaf7f14df3f2a4c Mon Sep 17 00:00:00 2001 From: vsonnier Date: Sun, 14 Jan 2018 15:30:40 +0100 Subject: [PATCH 20/22] Fix wrong multi-part WAV sequence management with time-limit files --- src/audio/AudioFileWAV.cpp | 13 +++++++++++++ src/audio/AudioFileWAV.h | 3 +++ 2 files changed, 16 insertions(+) diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp index ad4078f1..d0629abe 100644 --- a/src/audio/AudioFileWAV.cpp +++ b/src/audio/AudioFileWAV.cpp @@ -65,9 +65,11 @@ std::string AudioFileWAV::getExtension() bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) { if (!outputFileStream.is_open()) { + std::string ofName = getOutputFileName(); outputFileStream.open(ofName.c_str(), std::ios::binary); + currentFileSize = 0; writeHeaderToFileStream(input); } @@ -111,6 +113,7 @@ bool AudioFileWAV::closeFile() write_word(outputFileStream, file_length - 8, 4); outputFileStream.close(); + currentFileSize = 0; } return true; @@ -169,6 +172,16 @@ size_t AudioFileWAV::getMaxWritableNumberOfSamples(AudioThreadInputPtr input) { } +void AudioFileWAV::setOutputFileName(std::string filename) { + + if (filename != filenameBase) { + + currentSequenceNumber = 0; + } + + AudioFile::setOutputFileName(filename); +} + std::string AudioFileWAV::getOutputFileName() { std::string recPath = wxGetApp().getConfig()->getRecordingPath(); diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h index dcd102de..1dc9672a 100644 --- a/src/audio/AudioFileWAV.h +++ b/src/audio/AudioFileWAV.h @@ -13,6 +13,9 @@ class AudioFileWAV : public AudioFile { AudioFileWAV(); ~AudioFileWAV(); + //override to manage name change with multi-part WAV. + virtual void setOutputFileName(std::string filename); + //override of the base method to generate multi-part //WAV to overcome the WAV format size limit. virtual std::string getOutputFileName(); From 7baaca1216828739ba71cd8ad83cc2b8bc0e816b Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Sun, 14 Jan 2018 18:56:30 -0500 Subject: [PATCH 21/22] OSX crash-on-exit fix, File menu tidying, Confirm bookmark reset --- src/AppFrame.cpp | 53 +++++++++++++++++++++++-------- src/AppFrame.h | 1 + src/audio/AudioThread.h | 21 ++++++++++-- src/demod/DemodulatorInstance.cpp | 2 +- src/demod/DemodulatorThread.cpp | 43 +++++++++++-------------- 5 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 77d8fce7..d17a02e8 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -24,6 +24,7 @@ #include "ColorTheme.h" #include "DemodulatorMgr.h" #include "ImagePanel.h" +#include "ActionDialog.h" #include #include @@ -53,6 +54,22 @@ wxEND_EVENT_TABLE() #endif + +class ActionDialogBookmarkReset : public ActionDialog { +public: + ActionDialogBookmarkReset() : ActionDialog(wxGetApp().getAppFrame(), wxID_ANY, wxT("Reset Bookmarks?")) { + m_questionText->SetLabelText(wxT("Resetting bookmarks will erase all current bookmarks; are you sure?")); + } + + void doClickOK() { + wxGetApp().getBookmarkMgr().resetBookmarks(); + wxGetApp().getBookmarkMgr().updateBookmarks(); + wxGetApp().getBookmarkMgr().updateActiveList(); + } +}; + + + /* split a string by 'seperator' into a vector of string */ std::vector str_explode(const std::string &seperator, const std::string &in_str); @@ -711,18 +728,28 @@ wxMenu *AppFrame::makeFileMenu() { menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); menu->AppendSeparator(); - menu->Append(wxID_OPEN, "&Open Session"); - menu->Append(wxID_SAVE, "&Save Session"); - menu->Append(wxID_SAVEAS, "Save Session &As.."); - menu->AppendSeparator(); - menu->Append(wxID_RESET, "&Reset Session"); + wxMenu *sessionMenu = new wxMenu; + + sessionMenu->Append(wxID_OPEN, "&Open Session"); + sessionMenu->Append(wxID_SAVE, "&Save Session"); + sessionMenu->Append(wxID_SAVEAS, "Save Session &As.."); + sessionMenu->AppendSeparator(); + sessionMenu->Append(wxID_RESET, "&Reset Session"); + + menu->AppendSubMenu(sessionMenu, "Session"); + menu->AppendSeparator(); - menu->Append(wxID_OPEN_BOOKMARKS, "Open Bookmarks"); - menu->Append(wxID_SAVE_BOOKMARKS, "Save Bookmarks"); - menu->Append(wxID_SAVEAS_BOOKMARKS, "Save Bookmarks As.."); - menu->AppendSeparator(); - menu->Append(wxID_RESET_BOOKMARKS, "Reset Bookmarks"); + wxMenu *bookmarkMenu = new wxMenu; + + bookmarkMenu->Append(wxID_OPEN_BOOKMARKS, "Open Bookmarks"); + bookmarkMenu->Append(wxID_SAVE_BOOKMARKS, "Save Bookmarks"); + bookmarkMenu->Append(wxID_SAVEAS_BOOKMARKS, "Save Bookmarks As.."); + bookmarkMenu->AppendSeparator(); + bookmarkMenu->Append(wxID_RESET_BOOKMARKS, "Reset Bookmarks"); + + menu->AppendSubMenu(bookmarkMenu, "Bookmarks"); + #ifndef __APPLE__ menu->AppendSeparator(); menu->Append(wxID_CLOSE); @@ -1570,10 +1597,8 @@ bool AppFrame::actionOnMenuLoadSave(wxCommandEvent& event) { } else if (event.GetId() == wxID_RESET_BOOKMARKS) { - wxGetApp().getBookmarkMgr().resetBookmarks(); - wxGetApp().getBookmarkMgr().updateBookmarks(); - wxGetApp().getBookmarkMgr().updateActiveList(); - + ActionDialog::showDialog(new ActionDialogBookmarkReset()); + return true; } diff --git a/src/AppFrame.h b/src/AppFrame.h index 081db1c5..03344ae4 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -221,6 +221,7 @@ class AppFrame: public wxFrame { std::vector devices; std::map inputDevices; std::map outputDevices; + std::map outputDeviceMenuItems; std::map sampleRateMenuItems; std::map antennaMenuItems; diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index dfb2e5e4..7585faa0 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -21,15 +21,32 @@ class AudioThreadInput { int channels; float peak; int type; - bool is_squelch_active = false; + bool is_squelch_active; std::vector data; AudioThreadInput() : - frequency(0), inputRate(0), sampleRate(0), channels(0), peak(0), type(0) { + frequency(0), inputRate(0), sampleRate(0), channels(0), peak(0), type(0), is_squelch_active(false) { } + + AudioThreadInput(AudioThreadInput *copyFrom) { + copy(copyFrom); + } + + void copy(AudioThreadInput *copyFrom) { + frequency = copyFrom->frequency; + inputRate = copyFrom->inputRate; + sampleRate = copyFrom->sampleRate; + channels = copyFrom->channels; + peak = copyFrom->peak; + type = copyFrom->type; + is_squelch_active = copyFrom->is_squelch_active; + data.assign(copyFrom->data.begin(), copyFrom->data.end()); + } + + virtual ~AudioThreadInput() { } diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index d7014d66..39e90146 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -187,7 +187,7 @@ void DemodulatorInstance::terminate() { demodulatorPreThread->terminate(); if (audioSinkThread != nullptr) { - audioSinkThread->terminate(); + stopRecording(); } //that will actually unblock the currently blocked push(). diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 82cb1983..d828d26a 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -225,13 +225,6 @@ void DemodulatorThread::run() { } } - // Capture audioSinkOutputQueue state in a local variable - DemodulatorThreadOutputQueuePtr localAudioSinkOutputQueue = nullptr; - { - std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); - localAudioSinkOutputQueue = audioSinkOutputQueue; - } - //compute audio peak: if (audioOutputQueue != nullptr && ati) { @@ -250,21 +243,6 @@ void DemodulatorThread::run() { ati->is_squelch_active = squelched; } - //Push to audio sink, if any: - if (ati && localAudioSinkOutputQueue != nullptr) { - - if (!localAudioSinkOutputQueue->try_push(ati)) { - std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; - std::this_thread::yield(); - } - } - - //now we can nullify ati if squelched, to skip the next processing entirely. - if (ati && squelched) { - - ati = nullptr; - } - //At that point, capture the current state of audioVisOutputQueue in a local //variable, and works with it with now on until the next while-turn. DemodulatorThreadOutputQueuePtr localAudioVisOutputQueue = nullptr; @@ -273,7 +251,7 @@ void DemodulatorThread::run() { localAudioVisOutputQueue = audioVisOutputQueue; } - if ((ati || modemDigital) && localAudioVisOutputQueue != nullptr && localAudioVisOutputQueue->empty()) { + if (!squelched && (ati || modemDigital) && localAudioVisOutputQueue != nullptr && localAudioVisOutputQueue->empty()) { AudioThreadInputPtr ati_vis = std::make_shared(); @@ -343,7 +321,7 @@ void DemodulatorThread::run() { } } - if (ati != nullptr) { + if (!squelched && ati != nullptr) { if (!muted.load() && (!wxGetApp().getSoloMode() || (demodInstance == wxGetApp().getDemodMgr().getLastActiveDemodulator().get()))) { //non-blocking push needed for audio out if (!audioOutputQueue->try_push(ati)) { @@ -354,6 +332,23 @@ void DemodulatorThread::run() { } } + + // Capture audioSinkOutputQueue state in a local variable + DemodulatorThreadOutputQueuePtr localAudioSinkOutputQueue = nullptr; + { + std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); + localAudioSinkOutputQueue = audioSinkOutputQueue; + } + + //Push to audio sink, if any: + if (ati && localAudioSinkOutputQueue != nullptr) { + + if (!localAudioSinkOutputQueue->try_push(ati)) { + std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; + std::this_thread::yield(); + } + } + DemodulatorThreadControlCommand command; //empty command queue, execute commands From 7588e77d947a59c82cc5a8193f62f920513bc3f8 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Sun, 14 Jan 2018 19:41:41 -0500 Subject: [PATCH 22/22] Donation list update --- src/forms/Dialog/AboutDialog.fbp | 83 ++++++++++++++++++++++++++++ src/forms/Dialog/AboutDialogBase.cpp | 6 +- src/forms/Dialog/AboutDialogBase.h | 1 + 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/forms/Dialog/AboutDialog.fbp b/src/forms/Dialog/AboutDialog.fbp index c3534f10..3d915944 100644 --- a/src/forms/Dialog/AboutDialog.fbp +++ b/src/forms/Dialog/AboutDialog.fbp @@ -6866,6 +6866,89 @@ + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Marvin Calvert + + 0 + + + 0 + + 1 + m_dMarvinCalvert + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/forms/Dialog/AboutDialogBase.cpp b/src/forms/Dialog/AboutDialogBase.cpp index 8ab39df1..fd26f24a 100644 --- a/src/forms/Dialog/AboutDialogBase.cpp +++ b/src/forms/Dialog/AboutDialogBase.cpp @@ -179,7 +179,7 @@ AboutDialogBase::AboutDialogBase( wxWindow* parent, wxWindowID id, const wxStrin m_dbScroll->SetSizer( m_dbPane ); m_dbScroll->Layout(); m_dbPane->Fit( m_dbScroll ); - m_aboutNotebook->AddPage( m_dbScroll, wxT("Developers"), true ); + m_aboutNotebook->AddPage( m_dbScroll, wxT("Developers"), false ); m_dScroll = new wxScrolledWindow( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); m_dScroll->SetScrollRate( 5, 5 ); wxBoxSizer* m_dBSizer; @@ -381,6 +381,10 @@ AboutDialogBase::AboutDialogBase( wxWindow* parent, wxWindowID id, const wxStrin m_dRobertChave->Wrap( -1 ); m_dSizer->Add( m_dRobertChave, 0, wxALL, 5 ); + m_dMarvinCalvert = new wxStaticText( m_dScroll, wxID_ANY, wxT("Marvin Calvert"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dMarvinCalvert->Wrap( -1 ); + m_dSizer->Add( m_dMarvinCalvert, 0, wxALL, 5 ); + m_dBSizer->Add( m_dSizer, 1, wxALL|wxEXPAND, 5 ); diff --git a/src/forms/Dialog/AboutDialogBase.h b/src/forms/Dialog/AboutDialogBase.h index 13b1eaa9..a6b9cbd4 100644 --- a/src/forms/Dialog/AboutDialogBase.h +++ b/src/forms/Dialog/AboutDialogBase.h @@ -118,6 +118,7 @@ class AboutDialogBase : public wxDialog wxStaticText* m_dCharlieBruckner; wxStaticText* m_dJordanParker; wxStaticText* m_dRobertChave; + wxStaticText* m_dMarvinCalvert; wxScrolledWindow* m_stScroll; wxStaticText* m_stHeader; wxStaticLine* m_stDivider1;