1 #include "fftviewer_frFFTviewer.h"
2 #include <wx/timer.h>
3 #include <vector>
4 #include "OpenGLGraph.h"
5 #include <LMSBoards.h>
6 #include "kiss_fft.h"
7 #include "IConnection.h"
8 #include "dataTypes.h"
9 #include "LMS7002M.h"
10 #include "windowFunction.h"
11 #include <fstream>
12 #include "lms7suiteEvents.h"
13 #include "lms7_device.h"
14 #include "Logger.h"
15 
16 using namespace std;
17 using namespace lime;
18 
Initialize(lms_device_t * pDataPort)19 void fftviewer_frFFTviewer::Initialize(lms_device_t* pDataPort)
20 {
21     StopStreaming();
22     lmsControl = pDataPort;
23     lmsIndex = 0;
24     for (unsigned i =0; i < this->cMaxChCount ; i++)
25     {
26         this->rxStreams[i].handle = 0;
27         this->txStreams[i].handle = 0;
28     }
29     cmbStreamType->Clear();
30     const int num_ch = LMS_GetNumChannels(lmsControl, false);
31     if (num_ch>2)
32     {
33         cmbStreamType->Append(_T("LMS1 SISO"));
34         cmbStreamType->Append(_T("LMS1 MIMO"));
35         cmbStreamType->Append(_T("LMS2 SISO"));
36         cmbStreamType->Append(_T("LMS2 MIMO"));
37         cmbStreamType->Append(_T("Ext. ADC/DAC"));
38     }
39     else if (num_ch == 2)
40     {
41         cmbStreamType->Append(_T("LMS SISO"));
42         cmbStreamType->Append(_T("LMS MIMO"));
43     }
44     else
45     {
46         cmbStreamType->Append(_T("LMS SISO"));
47     }
48     cmbStreamType->SetSelection(0);
49     SetNyquistFrequency();
50 
51 }
52 
fftviewer_frFFTviewer(wxWindow * parent)53 fftviewer_frFFTviewer::fftviewer_frFFTviewer( wxWindow* parent )
54 :
55 frFFTviewer(parent),
56 mStreamRunning(false),
57 lmsControl(nullptr)
58 {
59     captureSamples.store(false);
60     averageCount.store(50);
61     spinAvgCount->SetValue(averageCount);
62     updateGUI.store(true);
63 #ifndef __unix__
64     SetIcon(wxIcon(_("aaaaAPPicon")));
65 #endif
66     SetSize(1000, 700);
67     mFFTpanel->settings.useVBO = true;
68     mFFTpanel->AddSerie(new cDataSerie());
69     mFFTpanel->AddSerie(new cDataSerie());
70     mFFTpanel->series[0]->color = 0xFF0000FF;
71     mFFTpanel->series[1]->color = 0x0000FFFF;
72     mFFTpanel->SetDrawingMode(GLG_LINE);
73     mFFTpanel->settings.gridXlines = 15;
74     mFFTpanel->SetInitialDisplayArea(-16000000, 16000000, -115, 0);
75 
76     mFFTpanel->settings.title = "FFT";
77     mFFTpanel->settings.titleXaxis = "Frequency(MHz)";
78     mFFTpanel->settings.titleYaxis = "Amplitude(dBFS)";
79     mFFTpanel->settings.xUnits = "";
80     mFFTpanel->settings.gridXprec = 3;
81     //mFFTpanel->settings.yUnits = "dB";
82     mFFTpanel->settings.markersEnabled = true;
83 
84     mFFTpanel->settings.marginLeft = 40;
85     mFFTpanel->settings.staticGrid = true;
86 
87     mTimeDomainPanel->settings.useVBO = true;
88     mTimeDomainPanel->AddSerie(new cDataSerie());
89     mTimeDomainPanel->AddSerie(new cDataSerie());
90     mTimeDomainPanel->AddSerie(new cDataSerie());
91     mTimeDomainPanel->AddSerie(new cDataSerie());
92     mTimeDomainPanel->SetInitialDisplayArea(0, 1024, -2050, 2050);
93     mTimeDomainPanel->settings.title = "IQ samples";
94     mTimeDomainPanel->series[0]->color = 0xFF0000FF;
95     mTimeDomainPanel->series[1]->color = 0x0000FFFF;
96     mTimeDomainPanel->series[2]->color = 0xFF00FFFF;
97     mTimeDomainPanel->series[3]->color = 0x00FFFFFF;
98     mTimeDomainPanel->settings.marginLeft = 48;
99     mConstelationPanel->settings.useVBO = true;
100     mConstelationPanel->AddSerie(new cDataSerie());
101     mConstelationPanel->AddSerie(new cDataSerie());
102     mConstelationPanel->series[0]->color = 0xFF0000FF;
103     mConstelationPanel->series[1]->color = 0x0000FFFF;
104     mConstelationPanel->SetInitialDisplayArea(-2050, 2050, -2050, 2050);
105     mConstelationPanel->SetDrawingMode(GLG_POINTS);
106     mConstelationPanel->settings.title = "I versus Q";
107     mConstelationPanel->settings.titleXaxis = "I";
108     mConstelationPanel->settings.titleYaxis = "Q";
109     mConstelationPanel->settings.gridXlines = 8;
110     mConstelationPanel->settings.gridYlines = 8;
111     mConstelationPanel->settings.marginLeft = 48;
112 
113     mGUIupdater = new wxTimer(this, wxID_ANY); //timer for updating plots
114     Connect(wxEVT_THREAD, wxThreadEventHandler(fftviewer_frFFTviewer::OnUpdatePlots), NULL, this);
115     Connect(wxEVT_TIMER, wxTimerEventHandler(fftviewer_frFFTviewer::OnUpdateStats), NULL, this);
116 
117     wxCommandEvent evt;
118     //show only A channel at startup
119     evt.SetInt(0);
120     OnChannelVisibilityChange(evt);
121 }
122 
~fftviewer_frFFTviewer()123 fftviewer_frFFTviewer::~fftviewer_frFFTviewer()
124 {
125     if (mStreamRunning == true)
126         StopStreaming();
127 }
128 
OnWindowFunctionChanged(wxCommandEvent & event)129 void fftviewer_frFFTviewer::OnWindowFunctionChanged( wxCommandEvent& event )
130 {
131     windowFunctionID.store(cmbWindowFunc->GetSelection());
132 }
133 
OnbtnStartStop(wxCommandEvent & event)134 void fftviewer_frFFTviewer::OnbtnStartStop( wxCommandEvent& event )
135 {
136     if (threadProcessing.joinable() == false)
137         StartStreaming();
138     else
139         StopStreaming();
140 }
141 
StartStreaming()142 void fftviewer_frFFTviewer::StartStreaming()
143 {
144     auto conn = ((LMS7_Device*)lmsControl)->GetConnection();
145     if (!conn || !conn->IsOpen())
146     {
147         wxMessageBox(_("FFTviewer: Connection not initialized"), _("ERROR"));
148         return;
149     }
150 
151     txtNyquistFreqMHz->Disable();
152     cmbStreamType->Disable();
153     spinFFTsize->Disable();
154     chkEnSync->Disable();
155 
156     stopProcessing.store(false);
157     updateGUI.store(true);
158 
159     const int fftSize = spinFFTsize->GetValue();
160     fftFreqAxis.resize(fftSize);
161     double nyquistMHz;
162     txtNyquistFreqMHz->GetValue().ToDouble(&nyquistMHz);
163     const float step = 2*nyquistMHz / fftSize;
164     for (int i = 0; i < fftSize; ++i)
165         fftFreqAxis[i] = 1e6*(-nyquistMHz + (i+1)*step);
166     timeXAxis.resize(fftSize);
167     for (int i = 0; i < fftSize; ++i)
168         timeXAxis[i] = i;
169 
170     if(chkCaptureToFile->GetValue() == true)
171     {
172         captureSamples.store(true);
173         wxFileDialog dlg(this, _("Save samples file"), "", "", "Text (*.txt)|*.txt", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
174         if (dlg.ShowModal() == wxID_CANCEL)
175             captureSamples.store(false);
176         else
177             captureFilename = dlg.GetPath().To8BitData();
178     }
179     else
180         captureSamples.store(false);
181     chkCaptureToFile->Disable();
182     spinCaptureCount->Disable();
183     cmbFmt->Disable();
184     chkEnTx->Disable();
185     lmsIndex = cmbStreamType->GetSelection()/2;
186     if (mStreamRunning.load() == true)
187         return;
188     switch (cmbStreamType->GetSelection()%2)
189     {
190     case 0: //SISO
191         if (cmbChannelVisibility->GetSelection() > 1)
192             cmbChannelVisibility->SetSelection(0);
193         cmbChannelVisibility->Disable();
194         threadProcessing = std::thread(StreamingLoop, this, fftSize, 1, 0);
195         break;
196     case 1: //MIMO
197         threadProcessing = std::thread(StreamingLoop, this, fftSize, 2, 0);
198         break;
199     }
200 
201     btnStartStop->SetLabel(_("STOP"));
202     mGUIupdater->Start(500);
203 }
204 
StopStreaming()205 void fftviewer_frFFTviewer::StopStreaming()
206 {
207     txtNyquistFreqMHz->Enable();
208     mGUIupdater->Stop();
209     if (mStreamRunning.load() == false)
210         return;
211     stopProcessing.store(true);
212     threadProcessing.join();
213     btnStartStop->SetLabel(_("START"));
214     cmbStreamType->Enable();
215     spinFFTsize->Enable();
216     chkCaptureToFile->Enable();
217     spinCaptureCount->Enable();
218     chkEnSync->Enable();
219     cmbFmt->Enable();
220     cmbChannelVisibility->Enable();
221     chkEnTx->Enable();
222 }
223 
OnUpdateStats(wxTimerEvent & event)224 void fftviewer_frFFTviewer::OnUpdateStats(wxTimerEvent& event)
225 {
226     if (mStreamRunning.load() == false)
227         return;
228     if(rxStreams[0].handle != 0)
229     {
230         lms_stream_status_t rxStats;
231         LMS_GetStreamStatus(&rxStreams[0],&rxStats);
232         float RxFilled = 100*(float)rxStats.fifoFilledCount/rxStats.fifoSize;
233         gaugeRxBuffer->SetValue((int)RxFilled);
234         lblRxDataRate->SetLabel(printDataRate(rxStats.linkRate));
235     }
236     else
237     {
238         gaugeRxBuffer->SetValue(0);
239         lblRxDataRate->SetLabel(printDataRate(0));
240     }
241     if(txStreams[0].handle != 0)
242     {
243         lms_stream_status_t txStats;
244         LMS_GetStreamStatus(&txStreams[0],&txStats);
245         float TxFilled = 100*(float)txStats.fifoFilledCount/txStats.fifoSize;
246         gaugeTxBuffer->SetValue((int)TxFilled);
247         lblTxDataRate->SetLabel(printDataRate(txStats.linkRate));
248     }
249     else
250     {
251         gaugeTxBuffer->SetValue(0);
252         lblTxDataRate->SetLabel(printDataRate(0));
253     }
254 }
255 
OnUpdatePlots(wxThreadEvent & event)256 void fftviewer_frFFTviewer::OnUpdatePlots(wxThreadEvent& event)
257 {
258     double dbOffset = cmbFmt->GetSelection() == 1 ? 93.319 : 69.2369;
259     if (mStreamRunning.load() == false)
260         return;
261 
262     const int fftSize = streamData.fftBins[0].size();
263 
264     if (chkEnPwr->GetValue())
265     {
266         float chPwr[2] = { 0, 0 };
267         double cFreq[2] = { 0, 0 };
268         txtCenterOffset1->GetValue().ToDouble(&cFreq[0]);
269         txtCenterOffset2->GetValue().ToDouble(&cFreq[1]);
270         double bw[2] = {1, 1};
271         txtBW1->GetValue().ToDouble(&bw[0]);
272         txtBW2->GetValue().ToDouble(&bw[1]);
273 
274         for (int c = 0; c<2; ++c)
275         {
276             float f0 = (cFreq[c] - bw[c]/2) * 1e6;
277             float fn = (cFreq[c] + bw[c]/2) * 1e6;
278             float sum = 0;
279             int bins = 0;
280             const int lmsch = mFFTpanel->series[0]->visible ? 0 : 1;
281             for (int i = 0; i<fftSize; ++i)
282                 if (f0 <= fftFreqAxis[i] && fftFreqAxis[i] <= fn)
283                 {
284                     sum += streamData.fftBins[lmsch][i];
285                     ++bins;
286                 }
287             chPwr[c] = sum;
288         }
289 
290         float pwr1 = (chPwr[0] != 0 ? (10 * log10(chPwr[0])) - dbOffset : -300);
291         lblPower1->SetLabel(wxString::Format("%.3f", pwr1));
292         float pwr2 = (chPwr[1] != 0 ? (10 * log10(chPwr[1])) - dbOffset : -300);
293         lblPower2->SetLabel(wxString::Format("%.3f", pwr2));
294         lbldBc->SetLabel(wxString::Format("%.3f", pwr2-pwr1));
295     }
296 
297     if (fftSize > 0)
298     {
299         if (chkFreezeTimeDomain->IsChecked() == false)
300         {
301             mTimeDomainPanel->series[0]->AssignValues(&timeXAxis[0], &streamData.samplesI[0][0], streamData.samplesI[0].size());
302             mTimeDomainPanel->series[1]->AssignValues(&timeXAxis[0], &streamData.samplesQ[0][0], streamData.samplesQ[0].size());
303             mTimeDomainPanel->series[2]->AssignValues(&timeXAxis[0], &streamData.samplesI[1][0], streamData.samplesI[1].size());
304             mTimeDomainPanel->series[3]->AssignValues(&timeXAxis[0], &streamData.samplesQ[1][0], streamData.samplesQ[1].size());
305         }
306         if (chkFreezeConstellation->IsChecked() == false)
307         {
308             mConstelationPanel->series[0]->AssignValues(&streamData.samplesI[0][0], &streamData.samplesQ[0][0], streamData.samplesQ[0].size());
309             mConstelationPanel->series[1]->AssignValues(&streamData.samplesI[1][0], &streamData.samplesQ[1][0], streamData.samplesQ[1].size());
310         }
311         if (chkFreezeFFT->IsChecked() == false)
312         {
313             for (int ch = 0; ch<2; ++ch)
314             {
315                 for (int s = 0; s < fftSize; ++s)
316                 {
317                     streamData.fftBins[ch][s] = (streamData.fftBins[ch][s] != 0 ? (10 * log10(streamData.fftBins[ch][s])) - dbOffset : -300);
318                 }
319             }
320             mFFTpanel->series[0]->AssignValues(&fftFreqAxis[0], &streamData.fftBins[0][0], fftSize);
321             mFFTpanel->series[1]->AssignValues(&fftFreqAxis[0], &streamData.fftBins[1][0], fftSize);
322         }
323     }
324 
325     if (chkFreezeTimeDomain->IsChecked() == false)
326     {
327         mTimeDomainPanel->Refresh();
328         mTimeDomainPanel->Draw();
329     }
330 
331     if (chkFreezeConstellation->IsChecked() == false)
332     {
333         mConstelationPanel->Refresh();
334         mConstelationPanel->Draw();
335     }
336 
337     if (chkFreezeFFT->IsChecked() == false)
338     {
339         mFFTpanel->Refresh();
340         mFFTpanel->Draw();
341         enableFFT.store(true);
342     }
343     else enableFFT.store(false);
344 
345     updateGUI.store(true);
346 }
347 
StreamingLoop(fftviewer_frFFTviewer * pthis,const unsigned int fftSize,const int channelsCount,const uint32_t format)348 void fftviewer_frFFTviewer::StreamingLoop(fftviewer_frFFTviewer* pthis, const unsigned int fftSize, const int channelsCount, const uint32_t format)
349 {
350     const bool runTx = pthis->chkEnTx->GetValue();
351     const int fifoSize = fftSize*512;
352     int avgCount = pthis->spinAvgCount->GetValue();
353     int wndFunction = pthis->windowFunctionID.load();
354     bool fftEnabled = true;
355     int ch_offset = 0;
356     bool syncTx = pthis->chkEnSync->GetValue();
357     if (channelsCount == 1)
358     {
359         if (pthis->cmbChannelVisibility->GetSelection() == 1)
360             ch_offset = 1;
361     }
362     vector<float> wndCoef;
363     GenerateWindowCoefficients(wndFunction, fftSize, wndCoef, 1);
364 
365     lime::complex16_t** buffers;
366 
367     DataToGUI localDataResults;
368     localDataResults.nyquist_Hz = 7.68e6;
369     for (unsigned i = 0; i < cMaxChCount; i++)
370     {
371         localDataResults.samplesI[i].resize(fftSize, 0);
372         localDataResults.samplesQ[i].resize(fftSize, 0);
373         localDataResults.fftBins[i].resize(fftSize, 0);
374         pthis->streamData.samplesI[i].resize(fftSize);
375         pthis->streamData.samplesQ[i].resize(fftSize);
376         pthis->streamData.fftBins[i].resize(fftSize);
377     }
378     buffers = new lime::complex16_t*[channelsCount];
379     for (int i = 0; i < channelsCount; ++i)
380         buffers[i] = new complex16_t[fftSize];
381 
382     vector<complex16_t> captureBuffer[cMaxChCount];
383     uint32_t samplesToCapture[cMaxChCount];
384     uint32_t samplesCaptured[cMaxChCount];
385     if(pthis->captureSamples.load() == true)
386         for(int ch=0; ch<channelsCount; ++ch)
387         {
388             samplesToCapture[ch] = pthis->spinCaptureCount->GetValue();
389             captureBuffer[ch].resize(samplesToCapture[ch]);
390             samplesCaptured[ch] = 0;
391         }
392 
393     if (LMS_GetNumChannels(pthis->lmsControl, false)>2)
394         ch_offset += 2*pthis->lmsIndex;
395 
396     auto fmt = pthis->cmbFmt->GetSelection() == 1 ? lms_stream_t::LMS_FMT_I16 : lms_stream_t::LMS_FMT_I12;
397     for(int i=0; i<channelsCount; ++i)
398     {
399         pthis->rxStreams[i].channel = i + ch_offset;
400         pthis->rxStreams[i].fifoSize = fifoSize;
401         pthis->rxStreams[i].isTx = false;
402         pthis->rxStreams[i].dataFmt = fmt;
403         pthis->rxStreams[i].throughputVsLatency = 0.8;
404         LMS_SetupStream(pthis->lmsControl, &pthis->rxStreams[i]);
405 
406         pthis->txStreams[i].handle = 0;
407         pthis->txStreams[i].channel = i + ch_offset;
408         pthis->txStreams[i].fifoSize = fifoSize;
409         pthis->txStreams[i].isTx = true;
410         pthis->txStreams[i].dataFmt = fmt;
411         pthis->txStreams[i].throughputVsLatency = 0.8;
412         if(runTx)
413             LMS_SetupStream(pthis->lmsControl, &pthis->txStreams[i]);
414     }
415 
416     kiss_fft_cfg m_fftCalcPlan = kiss_fft_alloc(fftSize, 0, 0, 0);
417     kiss_fft_cpx* m_fftCalcIn = new kiss_fft_cpx[fftSize];
418     kiss_fft_cpx* m_fftCalcOut = new kiss_fft_cpx[fftSize];
419 
420     for(int i=0; i<channelsCount; ++i)
421     {
422         LMS_StartStream(&pthis->rxStreams[i]);
423         if(runTx)
424             LMS_StartStream(&pthis->txStreams[i]);
425     }
426 
427     uint16_t regVal = 0;
428     if (LMS_ReadFPGAReg(pthis->lmsControl, 0x0008, &regVal) == 0)
429     {
430         wxCommandEvent* e = new wxCommandEvent(wxEVT_COMMAND_CHOICE_SELECTED);
431         e->SetInt((regVal&2) ? 0 : 1);
432         wxQueueEvent(pthis->cmbFmt, e);
433     }
434 
435     pthis->mStreamRunning.store(true);
436     lms_stream_meta_t meta;
437     meta.waitForTimestamp = syncTx;
438     meta.flushPartialPacket = false;
439     int fftCounter = 0;
440 
441     while (pthis->stopProcessing.load() == false)
442     {
443         do
444         {
445             uint32_t samplesPopped[cMaxChCount];
446             uint64_t ts[cMaxChCount];
447             for(int i=0; i<channelsCount; ++i)
448             {
449                 samplesPopped[i] = LMS_RecvStream(&pthis->rxStreams[i], &buffers[i][0], fftSize, &meta, 1000);
450                 ts[i] = meta.timestamp + fifoSize/4;
451             }
452 
453             for(int i=0; runTx && i<channelsCount; ++i)
454             {
455                 meta.timestamp = ts[i];
456                 meta.waitForTimestamp = syncTx;
457                 LMS_SendStream(&pthis->txStreams[i], &buffers[i][0], fftSize, &meta, 1000);
458             }
459 
460             if(pthis->captureSamples.load())
461             {
462                 for(int ch=0; ch<channelsCount; ++ch)
463                 {
464                     uint32_t samplesToCopy = min(samplesPopped[ch], samplesToCapture[ch]);
465                     if(samplesToCopy <= 0)
466                         break;
467                     memcpy((captureBuffer[ch].data() + samplesCaptured[ch]), buffers[ch], samplesToCopy*sizeof(complex16_t));
468                     samplesToCapture[ch] -= samplesToCopy;
469                     samplesCaptured[ch] += samplesToCopy;
470                 }
471             }
472 
473             for (int ch = 0; ch < channelsCount; ++ch)
474             {
475                 //take only first buffer for time domain display
476                 //reset fftBins for accumulation
477                 for (unsigned i = 0; fftCounter==0 && i < fftSize; ++i)
478                 {
479                     if (fftEnabled)
480                         localDataResults.fftBins[ch][i] = 0;
481                     localDataResults.samplesI[ch][i] = buffers[ch][i].i;
482                     localDataResults.samplesQ[ch][i] = buffers[ch][i].q;
483                 }
484                 if (fftEnabled)
485                 {
486                     if (wndFunction == 0)
487                     {
488                         for (unsigned i = 0; i < fftSize; ++i)
489                         {
490                             m_fftCalcIn[i].r = buffers[ch][i].i;
491                             m_fftCalcIn[i].i = buffers[ch][i].q;
492                         }
493                     }
494                     else
495                     {
496                         for (unsigned i = 0; i < fftSize; ++i)
497                         {
498                             m_fftCalcIn[i].r = buffers[ch][i].i * wndCoef[i];
499                             m_fftCalcIn[i].i = buffers[ch][i].q * wndCoef[i];
500                         }
501                     }
502 
503                     kiss_fft(m_fftCalcPlan, m_fftCalcIn, m_fftCalcOut);
504 
505                     int output_index = 0;
506                     for (unsigned i = fftSize / 2 + 1; i < fftSize; ++i)
507                         localDataResults.fftBins[ch][output_index++] += m_fftCalcOut[i].r * m_fftCalcOut[i].r + m_fftCalcOut[i].i * m_fftCalcOut[i].i;
508                     for (unsigned i = 0; i < fftSize / 2 + 1; ++i)
509                         localDataResults.fftBins[ch][output_index++] += m_fftCalcOut[i].r * m_fftCalcOut[i].r + m_fftCalcOut[i].i * m_fftCalcOut[i].i;
510                 }
511             }
512         } while (++fftCounter < avgCount && pthis->stopProcessing.load() == false);
513 
514         if (fftCounter >= avgCount && pthis->updateGUI.load() == true)
515         {
516             //shift fft
517             if (fftEnabled)
518             {
519                 for(int ch=0; ch<channelsCount; ++ch)
520                 {
521                     for (unsigned s = 0; s < fftSize; ++s)
522                     {
523                         const float div = (float)fftCounter*fftSize*fftSize;
524                         localDataResults.fftBins[ch][s] /= div;
525                     }
526                 }
527             }
528             if(pthis->stopProcessing.load() == false)
529             {
530                 pthis->streamData = localDataResults;
531                 wxThreadEvent* evt = new wxThreadEvent;
532                 evt->SetEventObject(pthis);
533                 pthis->updateGUI.store(false);
534                 pthis->QueueEvent(evt);
535             }
536             fftCounter = 0;
537             fftEnabled = pthis->enableFFT.load();
538             avgCount = pthis->averageCount.load();
539             int wndFunctionSelection = pthis->windowFunctionID.load();
540             if(wndFunctionSelection != wndFunction)
541             {
542                 wndFunction = wndFunctionSelection;
543                 GenerateWindowCoefficients(wndFunction, fftSize, wndCoef, 1);
544             }
545         }
546     }
547 
548     if(pthis->captureSamples.load() == true)
549     {
550         ofstream fout;
551         fout.open(pthis->captureFilename.c_str());
552         fout << "AI\tAQ";
553         if(channelsCount > 1)
554             fout << "\tBI\tBQ";
555         fout << endl;
556 
557         int samplesCnt = captureBuffer[0].size();
558         for(int i=0; i<samplesCnt; ++i)
559         {
560             for(int ch=0; ch<channelsCount; ++ch)
561             {
562                 fout << captureBuffer[ch][i].i << "\t" << captureBuffer[ch][i].q << "\t";
563             }
564             fout << endl;
565         }
566         fout.close();
567 
568         string filename = pthis->captureFilename+".absdbfs";
569         fout.open(filename.c_str());
570         fout << "AI\tAQ";
571         if(channelsCount > 1)
572             fout << "\tBI\tBQ";
573         fout << endl;
574 
575         for(int i=0; i<samplesCnt; ++i)
576         {
577             for(int ch=0; ch<channelsCount; ++ch)
578             {
579                 fout
580                 << (captureBuffer[ch][i].i == 0 ? -67 : 20*log10(abs(captureBuffer[ch][i].i)/2048))<< "\t"
581                 << (captureBuffer[ch][i].q == 0 ? -67 : 20*log10(abs(captureBuffer[ch][i].q)/2048))<< "\t";
582             }
583             fout << endl;
584         }
585         fout.close();
586     }
587 
588     kiss_fft_free(m_fftCalcPlan);
589     pthis->stopProcessing.store(true);
590     pthis->mStreamRunning.store(false);
591     for(int i=0; i<channelsCount; ++i)
592     {
593         if(runTx)
594             LMS_StopStream(&pthis->txStreams[i]);
595         LMS_StopStream(&pthis->rxStreams[i]);
596     }
597     for(int i=0; i<channelsCount; ++i)
598     {
599         if(runTx)
600             LMS_DestroyStream(pthis->lmsControl, &pthis->txStreams[i]);
601         LMS_DestroyStream(pthis->lmsControl, &pthis->rxStreams[i]);
602     }
603     for (int i = 0; i < channelsCount; ++i)
604         delete [] buffers[i];
605     delete [] buffers;
606     delete [] m_fftCalcIn;
607     delete [] m_fftCalcOut;
608 }
609 
printDataRate(float dataRate)610 wxString fftviewer_frFFTviewer::printDataRate(float dataRate)
611 {
612     if (dataRate > 1000000)
613         return wxString::Format(_("%.3f MB/s"), dataRate / 1000000.0);
614     else if (dataRate > 1000)
615         return wxString::Format(_("%.3f KB/s"), dataRate / 1000.0);
616     else
617         return wxString::Format(_("%.0f B/s"), dataRate / 1000.0);
618 }
619 
SetNyquistFrequency()620 void fftviewer_frFFTviewer::SetNyquistFrequency()
621 {
622     double freqHz;
623     LMS_GetSampleRate(lmsControl,LMS_CH_RX,cmbStreamType->GetSelection()/2*2,&freqHz,nullptr);
624     txtNyquistFreqMHz->SetValue(wxString::Format(_("%2.5f"), freqHz / 2e6));
625     mFFTpanel->SetInitialDisplayArea(-freqHz/2, freqHz/2, -115, 0);
626 }
627 
OnStreamChange(wxCommandEvent & event)628 void fftviewer_frFFTviewer::OnStreamChange(wxCommandEvent& event)
629 {
630     SetNyquistFrequency();
631 
632 
633     int tmp = cmbChannelVisibility->GetSelection();
634     cmbChannelVisibility->Clear();
635     cmbChannelVisibility->Append(_T("A"));
636     cmbChannelVisibility->Append(_T("B"));
637     if (cmbStreamType->GetSelection()%2==1)
638         cmbChannelVisibility->Append(_T("A&B"));
639     else if (tmp > 1)
640         tmp = 0;
641     cmbChannelVisibility->SetSelection(tmp);
642 }
643 
OnFmtChange(wxCommandEvent & event)644 void fftviewer_frFFTviewer::OnFmtChange(wxCommandEvent& event)
645 {
646     int val = event.GetInt();
647     int max = val == 1 ? 32800 : 2050;
648     if (val != cmbFmt->GetSelection())
649         cmbFmt->SetSelection(val);
650     mTimeDomainPanel->SetInitialDisplayArea(0, 1024, -max, max);
651     mConstelationPanel->SetInitialDisplayArea(-max, max, -max, max);
652 }
653 
OnEnTx(wxCommandEvent & event)654 void fftviewer_frFFTviewer::OnEnTx(wxCommandEvent& event)
655 {
656     chkEnSync->Enable(event.GetInt());
657 }
658 
OnEnPwr(wxCommandEvent & event)659 void fftviewer_frFFTviewer::OnEnPwr(wxCommandEvent& event)
660 {
661     bool en = event.GetInt();
662     txtCenterOffset1->Enable(en);
663     txtCenterOffset2->Enable(en);
664     txtBW1->Enable(en);
665     txtBW2->Enable(en);
666 }
667 
668 
OnChannelVisibilityChange(wxCommandEvent & event)669 void fftviewer_frFFTviewer::OnChannelVisibilityChange(wxCommandEvent& event)
670 {
671     bool visibilities[cMaxChCount];
672 
673     if (cmbStreamType->GetSelection()%2)
674     {
675         switch (event.GetInt())
676         {
677         case 0:
678             visibilities[0] = true;
679             visibilities[1] = false;
680             break;
681         case 1:
682             visibilities[0] = false;
683             visibilities[1] = true;
684             break;
685         case 2:
686             visibilities[0] = true;
687             visibilities[1] = true;
688             break;
689         default:
690             visibilities[0] = false;
691             visibilities[1] = false;
692             break;
693         }
694     }
695     else
696     {
697         visibilities[0] = true;
698         visibilities[1] = false;
699     }
700     mTimeDomainPanel->series[0]->visible = visibilities[0];
701     mTimeDomainPanel->series[1]->visible = visibilities[0];
702     mTimeDomainPanel->series[2]->visible = visibilities[1];
703     mTimeDomainPanel->series[3]->visible = visibilities[1];
704     mConstelationPanel->series[0]->visible = visibilities[0];
705     mConstelationPanel->series[1]->visible = visibilities[1];
706     mFFTpanel->series[0]->visible = visibilities[0];
707     mFFTpanel->series[1]->visible = visibilities[1];
708 }
709 
OnAvgChange(wxSpinEvent & event)710 void fftviewer_frFFTviewer::OnAvgChange(wxSpinEvent& event)
711 {
712     averageCount.store(spinAvgCount->GetValue());
713 }
714 
OnAvgChangeEnter(wxCommandEvent & event)715 void fftviewer_frFFTviewer::OnAvgChangeEnter(wxCommandEvent& event)
716 {
717     averageCount.store(spinAvgCount->GetValue());
718 }
719 
OnWindowFunctionChange(wxCommandEvent & event)720 void fftviewer_frFFTviewer::OnWindowFunctionChange(wxCommandEvent& event)
721 {
722     windowFunctionID.store(cmbWindowFunc->GetSelection());
723 }
724