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