1 // Copyright (c) Charles J. Cliffe
2 // SPDX-License-Identifier: GPL-2.0+
3 
4 #include <memory>
5 #include <iomanip>
6 
7 #include "DemodulatorInstance.h"
8 #include "CubicSDR.h"
9 
10 #include "DemodulatorThread.h"
11 #include "DemodulatorPreThread.h"
12 #include "AudioSinkFileThread.h"
13 #include "AudioFileWAV.h"
14 
15 #if USE_HAMLIB
16 #include "RigThread.h"
17 #endif
18 
DemodVisualCue()19 DemodVisualCue::DemodVisualCue() {
20     squelchBreak.store(false);
21 }
22 
23 DemodVisualCue::~DemodVisualCue() = default;
24 
triggerSquelchBreak(int counter)25 void DemodVisualCue::triggerSquelchBreak(int counter) {
26     squelchBreak.store(counter);
27 }
28 
getSquelchBreak()29 int DemodVisualCue::getSquelchBreak() {
30     return squelchBreak.load();
31 }
32 
step()33 void DemodVisualCue::step() {
34     if (squelchBreak.load()) {
35         squelchBreak--;
36         if (squelchBreak.load() < 0) {
37             squelchBreak.store(0);
38         }
39     }
40 }
41 
DemodulatorInstance()42 DemodulatorInstance::DemodulatorInstance() {
43 
44 #if ENABLE_DIGITAL_LAB
45     activeOutput = nullptr;
46 #endif
47 
48 	active.store(false);
49     muted.store(false);
50     recording.store(false);
51     deltaLock.store(false);
52     deltaLockOfs.store(0);
53 	currentOutputDevice.store(-1);
54     currentAudioGain.store(1.0);
55     follow.store(false);
56     tracking.store(false);
57 
58     label.store(new std::string("Unnamed"));
59     user_label.store(new std::wstring());
60 
61     pipeIQInputData = std::make_shared<DemodulatorThreadInputQueue>();
62     pipeIQInputData->set_max_num_items(100);
63     pipeIQDemodData = std::make_shared< DemodulatorThreadPostInputQueue>();
64     pipeIQDemodData->set_max_num_items(100);
65 
66     audioThread = new AudioThread();
67 
68     demodulatorPreThread = new DemodulatorPreThread(this);
69     demodulatorPreThread->setInputQueue("IQDataInput",pipeIQInputData);
70     demodulatorPreThread->setOutputQueue("IQDataOutput",pipeIQDemodData);
71 
72     pipeAudioData = std::make_shared<AudioThreadInputQueue>();
73     pipeAudioData->set_max_num_items(100);
74 
75     demodulatorThread = new DemodulatorThread(this);
76     demodulatorThread->setInputQueue("IQDataInput",pipeIQDemodData);
77     demodulatorThread->setOutputQueue("AudioDataOutput", pipeAudioData);
78 
79     audioThread->setInputQueue("AudioDataInput", pipeAudioData);
80 }
81 
~DemodulatorInstance()82 DemodulatorInstance::~DemodulatorInstance() {
83 
84     std::lock_guard < std::recursive_mutex > lockData(m_thread_control_mutex);
85 
86     //now that DemodulatorInstance are managed through shared_ptr, we
87     //should enter here ONLY when it is no longer used by any piece of code, anywhere.
88     //so active wait on IsTerminated(), then die.
89 #define TERMINATION_SPIN_WAIT_MS (20)
90 #define MAX_WAIT_FOR_TERMINATION_MS (3000.0)
91     //this is a stupid busy plus sleep loop
92     int  nbCyclesToWait = (MAX_WAIT_FOR_TERMINATION_MS / TERMINATION_SPIN_WAIT_MS) + 1;
93     int currentCycle = 0;
94 
95     while (currentCycle < nbCyclesToWait) {
96 
97         if (isTerminated()) {
98             std::cout << "Garbage collected demodulator instance '" << getLabel() << "'... " << std::endl << std::flush;
99 
100 #if ENABLE_DIGITAL_LAB
101             delete activeOutput;
102 #endif
103             delete demodulatorPreThread;
104             delete demodulatorThread;
105             delete audioThread;
106             delete audioSinkThread;
107 
108             break;
109         }
110         else {
111             std::this_thread::sleep_for(std::chrono::milliseconds(TERMINATION_SPIN_WAIT_MS));
112         }
113         currentCycle++;
114     } //end while
115 }
116 
setVisualOutputQueue(const DemodulatorThreadOutputQueuePtr & tQueue)117 void DemodulatorInstance::setVisualOutputQueue(const DemodulatorThreadOutputQueuePtr& tQueue) {
118     demodulatorThread->setOutputQueue("AudioVisualOutput", tQueue);
119 }
120 
run()121 void DemodulatorInstance::run() {
122 
123     std::lock_guard < std::recursive_mutex > lockData(m_thread_control_mutex);
124 
125     if (active) {
126         return;
127     }
128 
129     t_Audio = new std::thread(&AudioThread::threadMain, audioThread);
130 
131 #ifdef __APPLE__    // Already using pthreads, might as well do some custom init..
132     pthread_attr_t attr;
133     size_t size;
134 
135     pthread_attr_init(&attr);
136     pthread_attr_setstacksize(&attr, 2048000);
137     pthread_attr_getstacksize(&attr, &size);
138     pthread_create(&t_PreDemod, &attr, &DemodulatorPreThread::pthread_helper, demodulatorPreThread);
139     pthread_attr_destroy(&attr);
140 
141     pthread_attr_init(&attr);
142     pthread_attr_setstacksize(&attr, 2048000);
143     pthread_attr_getstacksize(&attr, &size);
144     pthread_create(&t_Demod, &attr, &DemodulatorThread::pthread_helper, demodulatorThread);
145     pthread_attr_destroy(&attr);
146 
147 //    std::cout << "Initialized demodulator stack size of " << size << std::endl;
148 
149 #else
150     t_PreDemod = new std::thread(&DemodulatorPreThread::threadMain, demodulatorPreThread);
151     t_Demod = new std::thread(&DemodulatorThread::threadMain, demodulatorThread);
152 #endif
153 
154     active = true;
155 }
156 
updateLabel(long long freq)157 void DemodulatorInstance::updateLabel(long long freq) {
158     std::stringstream newLabel;
159     newLabel.precision(3);
160     newLabel << std::fixed << ((long double) freq / 1000000.0);
161     setLabel(newLabel.str());
162     wxGetApp().getBookmarkMgr().updateActiveList();
163 }
164 
terminate()165 void DemodulatorInstance::terminate() {
166 
167 #if ENABLE_DIGITAL_LAB
168     if (activeOutput) {
169         closeOutput();
170     }
171 #endif
172 
173 //    std::cout << "Terminating demodulator audio thread.." << std::endl;
174     audioThread->terminate();
175 
176 //    std::cout << "Terminating demodulator thread.." << std::endl;
177     demodulatorThread->terminate();
178 
179 //    std::cout << "Terminating demodulator preprocessor thread.." << std::endl;
180     demodulatorPreThread->terminate();
181 
182     if (audioSinkThread != nullptr) {
183         stopRecording();
184     }
185 
186     //that will actually unblock the currently blocked push().
187     pipeIQInputData->flush();
188     pipeAudioData->flush();
189     pipeIQDemodData->flush();
190 }
191 
getLabel()192 std::string DemodulatorInstance::getLabel() {
193     return *(label.load());
194 }
195 
setLabel(std::string labelStr)196 void DemodulatorInstance::setLabel(std::string labelStr) {
197 
198     delete label.exchange(new std::string(labelStr));
199 }
200 
isTerminated()201 bool DemodulatorInstance::isTerminated() {
202 
203     std::lock_guard < std::recursive_mutex > lockData(m_thread_control_mutex);
204 
205     bool audioTerminated = audioThread->isTerminated();
206     bool demodTerminated = demodulatorThread->isTerminated();
207     bool preDemodTerminated = demodulatorPreThread->isTerminated();
208     bool audioSinkTerminated = (audioSinkThread == nullptr) || audioSinkThread->isTerminated();
209 
210     //Cleanup the worker threads, if the threads are indeed terminated.
211     // threads are linked as  t_PreDemod ==> t_Demod ==> t_Audio
212     //so terminate in the same order to starve the following threads in succession.
213     //i.e waiting on timed-pop so able to se their stopping flag.
214 
215     if (preDemodTerminated) {
216 
217          if (t_PreDemod) {
218 
219 #ifdef __APPLE__
220             pthread_join(t_PreDemod, NULL);
221 #else
222             t_PreDemod->join();
223             delete t_PreDemod;
224 #endif
225             t_PreDemod = nullptr;
226          }
227     }
228 
229     if (demodTerminated) {
230 
231         if (t_Demod) {
232 #ifdef __APPLE__
233             pthread_join(t_Demod, nullptr);
234 #else
235             t_Demod->join();
236             delete t_Demod;
237 #endif
238             t_Demod = nullptr;
239         }
240     }
241 
242     if (audioTerminated) {
243 
244         if (t_Audio) {
245 #ifdef __APPLE__
246             pthread_join(t_PreDemod, NULL);
247 #else
248             t_Audio->join();
249             delete t_Audio;
250 #endif
251             t_Audio = nullptr;
252         }
253     }
254 
255     if (audioSinkTerminated) {
256 
257         if (t_AudioSink != nullptr) {
258             t_AudioSink->join();
259 
260             delete t_AudioSink;
261             t_AudioSink = nullptr;
262         }
263     }
264 
265     bool terminated = audioTerminated && demodTerminated && preDemodTerminated && audioSinkTerminated;
266 
267     return terminated;
268 }
269 
isActive()270 bool DemodulatorInstance::isActive() {
271     return active;
272 }
273 
setActive(bool state)274 void DemodulatorInstance::setActive(bool state) {
275     if (active && !state) {
276 #if ENABLE_DIGITAL_LAB
277         if (activeOutput) {
278             activeOutput->Hide();
279         }
280 #endif
281         audioThread->setActive(state);
282 
283         DemodulatorThread::releaseSquelchLock(this);
284 
285     } else if (!active && state) {
286 #if ENABLE_DIGITAL_LAB
287         if (activeOutput && getModemType() == "digital") {
288             activeOutput->Show();
289         }
290 #endif
291         audioThread->setActive(state);
292     }
293     if (!state) {
294         tracking = false;
295     }
296     active = state;
297 
298     wxGetApp().getBookmarkMgr().updateActiveList();
299 }
300 
squelchAuto()301 void DemodulatorInstance::squelchAuto() {
302     demodulatorThread->setSquelchEnabled(true);
303 }
304 
isSquelchEnabled()305 bool DemodulatorInstance::isSquelchEnabled() {
306     return demodulatorThread->isSquelchEnabled();
307 }
308 
setSquelchEnabled(bool state)309 void DemodulatorInstance::setSquelchEnabled(bool state) {
310     demodulatorThread->setSquelchEnabled(state);
311 }
312 
getSignalLevel()313 float DemodulatorInstance::getSignalLevel() {
314     return demodulatorThread->getSignalLevel();
315 }
316 
getSignalFloor()317 float DemodulatorInstance::getSignalFloor() {
318     return demodulatorThread->getSignalFloor();
319 }
320 
getSignalCeil()321 float DemodulatorInstance::getSignalCeil() {
322     return demodulatorThread->getSignalCeil();
323 }
324 
setSquelchLevel(float signal_level_in)325 void DemodulatorInstance::setSquelchLevel(float signal_level_in) {
326     demodulatorThread->setSquelchLevel(signal_level_in);
327     wxGetApp().getDemodMgr().setLastSquelchLevel(signal_level_in);
328     wxGetApp().getDemodMgr().setLastSquelchEnabled(true);
329 }
330 
getSquelchLevel()331 float DemodulatorInstance::getSquelchLevel() {
332     return demodulatorThread->getSquelchLevel();
333 }
334 
setOutputDevice(int device_id)335 void DemodulatorInstance::setOutputDevice(int device_id) {
336     if (!active) {
337         audioThread->setInitOutputDevice(device_id);
338     } else if (audioThread) {
339         AudioThreadCommand command;
340         command.cmdType = AudioThreadCommand::Type::AUDIO_THREAD_CMD_SET_DEVICE;
341         command.int_value = device_id;
342         //VSO: blocking push
343         audioThread->getCommandQueue()->push(command);
344     }
345     setAudioSampleRate(AudioThread::deviceSampleRate[device_id]);
346     currentOutputDevice = device_id;
347 }
348 
getOutputDevice()349 int DemodulatorInstance::getOutputDevice() {
350     if (currentOutputDevice == -1) {
351         if (audioThread) {
352             currentOutputDevice = audioThread->getOutputDevice();
353         }
354     }
355 
356     return currentOutputDevice;
357 }
358 
setDemodulatorType(const std::string & demod_type_in)359 void DemodulatorInstance::setDemodulatorType(const std::string& demod_type_in) {
360     setGain(getGain());
361     if (demodulatorPreThread) {
362         std::string currentDemodType = demodulatorPreThread->getDemodType();
363         if ((!currentDemodType.empty()) && (currentDemodType != demod_type_in)) {
364             lastModemSettings[currentDemodType] = demodulatorPreThread->readModemSettings();
365             lastModemBandwidth[currentDemodType] = demodulatorPreThread->getBandwidth();
366         }
367 #if ENABLE_DIGITAL_LAB
368         if (activeOutput) {
369             activeOutput->Hide();
370         }
371 #endif
372 
373         demodulatorPreThread->setDemodType(demod_type_in);
374         int lastbw = 0;
375         if (!currentDemodType.empty() && lastModemBandwidth.find(demod_type_in) != lastModemBandwidth.end()) {
376             lastbw = lastModemBandwidth[demod_type_in];
377         }
378         if (!lastbw) {
379             lastbw = Modem::getModemDefaultSampleRate(demod_type_in);
380         }
381         if (lastbw) {
382             setBandwidth(lastbw);
383         }
384 
385 #if ENABLE_DIGITAL_LAB
386         if (isModemInitialized() && getModemType() == "digital") {
387             auto *outp = (ModemDigitalOutputConsole *)getOutput();
388             outp->setTitle(getDemodulatorType() + ": " + frequencyToStr(getFrequency()));
389         }
390 #endif
391     }
392 
393     wxGetApp().getBookmarkMgr().updateActiveList();
394 }
395 
getDemodulatorType()396 std::string DemodulatorInstance::getDemodulatorType() {
397     return demodulatorPreThread->getDemodType();
398 }
399 
getDemodulatorUserLabel()400 std::wstring DemodulatorInstance::getDemodulatorUserLabel() {
401     return *(user_label.load());
402 }
403 
setDemodulatorUserLabel(const std::wstring & demod_user_label)404 void DemodulatorInstance::setDemodulatorUserLabel(const std::wstring& demod_user_label) {
405 
406     delete user_label.exchange(new std::wstring(demod_user_label));
407 }
408 
setDemodulatorLock(bool demod_lock_in)409 void DemodulatorInstance::setDemodulatorLock(bool demod_lock_in) {
410     Modem *cModem = demodulatorPreThread->getModem();
411     if (cModem && cModem->getType() == "digital") {
412         ((ModemDigital *)cModem)->setDemodulatorLock(demod_lock_in);
413     }
414 }
415 
getDemodulatorLock()416 int DemodulatorInstance::getDemodulatorLock() {
417     Modem *cModem = demodulatorPreThread->getModem();
418 
419     if (cModem && cModem->getType() == "digital") {
420         return ((ModemDigital *)cModem)->getDemodulatorLock();
421     }
422 
423     return 0;
424 }
425 
setBandwidth(int bw)426 void DemodulatorInstance::setBandwidth(int bw) {
427     demodulatorPreThread->setBandwidth(bw);
428 }
429 
getBandwidth()430 int DemodulatorInstance::getBandwidth() {
431     return demodulatorPreThread->getBandwidth();
432 }
433 
setFrequency(long long freq)434 void DemodulatorInstance::setFrequency(long long freq) {
435     if ((freq - getBandwidth() / 2) <= 0) {
436         freq = getBandwidth() / 2;
437     }
438 
439     demodulatorPreThread->setFrequency(freq);
440 #if ENABLE_DIGITAL_LAB
441     if (activeOutput) {
442         if (isModemInitialized() && getModemType() == "digital") {
443             auto *outp = (ModemDigitalOutputConsole *)getOutput();
444             outp->setTitle(getDemodulatorType() + ": " + frequencyToStr(getFrequency()));
445         }
446     }
447 #endif
448 #if USE_HAMLIB
449     if (wxGetApp().rigIsActive() && wxGetApp().getRigThread()->getFollowModem() &&
450             wxGetApp().getDemodMgr().getCurrentModem().get() == this) {
451         wxGetApp().getRigThread()->setFrequency(freq,true);
452     }
453 #endif
454 
455     if (this->isActive()) {
456         wxGetApp().getBookmarkMgr().updateActiveList();
457     }
458 }
459 
getFrequency()460 long long DemodulatorInstance::getFrequency() {
461     return demodulatorPreThread->getFrequency();
462 }
463 
setAudioSampleRate(int sampleRate)464 void DemodulatorInstance::setAudioSampleRate(int sampleRate) {
465     demodulatorPreThread->setAudioSampleRate(sampleRate);
466 }
467 
getAudioSampleRate() const468 int DemodulatorInstance::getAudioSampleRate() const {
469     if (!audioThread) {
470         return 0;
471     }
472     return audioThread->getSampleRate();
473 }
474 
475 
setGain(float gain_in)476 void DemodulatorInstance::setGain(float gain_in) {
477 	currentAudioGain = gain_in;
478     audioThread->setGain(gain_in);
479 }
480 
getGain()481 float DemodulatorInstance::getGain() {
482    return currentAudioGain;
483 }
484 
isFollow()485 bool DemodulatorInstance::isFollow()  {
486     return follow.load();
487 }
488 
setFollow(bool follow_in)489 void DemodulatorInstance::setFollow(bool follow_in) {
490     follow.store(follow_in);
491 }
492 
isTracking()493 bool DemodulatorInstance::isTracking()  {
494     return tracking.load();
495 }
496 
setTracking(bool tracking_in)497 void DemodulatorInstance::setTracking(bool tracking_in) {
498     tracking.store(tracking_in);
499 }
500 
isDeltaLock()501 bool DemodulatorInstance::isDeltaLock() {
502     return deltaLock.load();
503 }
504 
setDeltaLock(bool lock)505 void DemodulatorInstance::setDeltaLock(bool lock) {
506     deltaLock.store(lock);
507 }
508 
setDeltaLockOfs(int lockOfs)509 void DemodulatorInstance::setDeltaLockOfs(int lockOfs) {
510     deltaLockOfs.store(lockOfs);
511 }
512 
getDeltaLockOfs()513 int DemodulatorInstance::getDeltaLockOfs() {
514     return deltaLockOfs.load();
515 }
516 
isMuted()517 bool DemodulatorInstance::isMuted() {
518     return demodulatorThread->isMuted();
519 }
520 
setMuted(bool muted_in)521 void DemodulatorInstance::setMuted(bool muted_in) {
522     muted = muted_in;
523     demodulatorThread->setMuted(muted_in);
524     wxGetApp().getDemodMgr().setLastMuted(muted_in);
525 }
526 
isRecording()527 bool DemodulatorInstance::isRecording()
528 {
529     return recording.load();
530 }
531 
setRecording(bool recording_in)532 void DemodulatorInstance::setRecording(bool recording_in)
533 {
534     if (recording_in) {
535         startRecording();
536     }
537     else {
538         stopRecording();
539     }
540 }
541 
getVisualCue()542 DemodVisualCue *DemodulatorInstance::getVisualCue() {
543     return &visualCue;
544 }
545 
getIQInputDataPipe()546 DemodulatorThreadInputQueuePtr DemodulatorInstance::getIQInputDataPipe() {
547     return pipeIQInputData;
548 }
549 
getModemArgs()550 ModemArgInfoList DemodulatorInstance::getModemArgs() {
551     Modem *m = demodulatorPreThread->getModem();
552 
553     ModemArgInfoList args;
554     if (m != nullptr) {
555         args = m->getSettings();
556     }
557     return args;
558 }
559 
readModemSetting(const std::string & setting)560 std::string DemodulatorInstance::readModemSetting(const std::string& setting) {
561     return demodulatorPreThread->readModemSetting(setting);
562 }
563 
writeModemSetting(const std::string & setting,std::string value)564 void DemodulatorInstance::writeModemSetting(const std::string& setting, std::string value) {
565     demodulatorPreThread->writeModemSetting(setting, value);
566 }
567 
readModemSettings()568 ModemSettings DemodulatorInstance::readModemSettings() {
569     return demodulatorPreThread->readModemSettings();
570 }
571 
writeModemSettings(ModemSettings settings)572 void DemodulatorInstance::writeModemSettings(ModemSettings settings) {
573     demodulatorPreThread->writeModemSettings(settings);
574 }
575 
isModemInitialized()576 bool DemodulatorInstance::isModemInitialized() {
577     if (!demodulatorPreThread || isTerminated()) {
578         return false;
579     }
580     return demodulatorPreThread->isInitialized();
581 }
582 
getModemType()583 std::string DemodulatorInstance::getModemType() {
584     if (isModemInitialized()) {
585         return demodulatorPreThread->getModem()->getType();
586     }
587     return "";
588 }
589 
getLastModemSettings(const std::string & demodType)590 ModemSettings DemodulatorInstance::getLastModemSettings(const std::string& demodType) {
591     if (lastModemSettings.find(demodType) != lastModemSettings.end()) {
592         return lastModemSettings[demodType];
593     } else {
594         ModemSettings mods;
595         return mods;
596     }
597 }
598 
599 
startRecording()600 void DemodulatorInstance::startRecording() {
601     if (recording.load()) {
602         return;
603     }
604 
605     auto *newSinkThread = new AudioSinkFileThread();
606     auto *afHandler = new AudioFileWAV();
607 
608     std::stringstream fileName;
609 
610     std::wstring userLabel = getDemodulatorUserLabel();
611 
612     wxString userLabelForFileName(userLabel);
613     std::string userLabelStr = userLabelForFileName.ToStdString();
614 
615     if (!userLabelStr.empty()) {
616         fileName << userLabelStr;
617     } else {
618         fileName << getLabel();
619     }
620 
621 	newSinkThread->setAudioFileNameBase(fileName.str());
622 
623 	//attach options:
624     newSinkThread->setSquelchOption(wxGetApp().getConfig()->getRecordingSquelchOption());
625 	newSinkThread->setFileTimeLimit(wxGetApp().getConfig()->getRecordingFileTimeLimit());
626 
627     newSinkThread->setAudioFileHandler(afHandler);
628 
629     audioSinkThread = newSinkThread;
630     t_AudioSink = new std::thread(&AudioSinkThread::threadMain, audioSinkThread);
631 
632     demodulatorThread->setOutputQueue("AudioSink", audioSinkThread->getInputQueue("input"));
633 
634     recording.store(true);
635 }
636 
637 
stopRecording()638 void DemodulatorInstance::stopRecording() {
639     if (!recording.load()) {
640         return;
641     }
642 
643     demodulatorThread->setOutputQueue("AudioSink", nullptr);
644     audioSinkThread->terminate();
645 
646     t_AudioSink->join();
647 
648     delete t_AudioSink;
649     delete audioSinkThread;
650 
651     t_AudioSink = nullptr;
652     audioSinkThread = nullptr;
653 
654     recording.store(false);
655 }
656 
657 
658 #if ENABLE_DIGITAL_LAB
getOutput()659 ModemDigitalOutput *DemodulatorInstance::getOutput() {
660     if (activeOutput == nullptr) {
661         activeOutput = new ModemDigitalOutputConsole();
662     }
663     return activeOutput;
664 }
665 
showOutput()666 void DemodulatorInstance::showOutput() {
667     if (activeOutput != nullptr) {
668         activeOutput->Show();
669     }
670 }
671 
hideOutput()672 void DemodulatorInstance::hideOutput() {
673     if (activeOutput != nullptr) {
674         activeOutput->Hide();
675     }
676 }
677 
closeOutput()678 void DemodulatorInstance::closeOutput() {
679     if (isModemInitialized()) {
680         if (getModemType() == "digital") {
681             auto *dModem = (ModemDigital *)demodulatorPreThread->getModem();
682             dModem->setOutput(nullptr);
683         }
684     }
685     if (activeOutput) {
686         activeOutput->Close();
687     }
688 }
689 #endif
690