From de3b9899800ceeff1ede9c98cea62fbfb8b89e75 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sun, 26 Jul 2015 23:02:40 +0000 Subject: [PATCH] checkin after sourceforge outage, improved scatter diagram, remembers mode, blinking on from radio file playback and warning of sample rate with raw files and handling Fs=8000Hz sample rate wave file on 700, freq scale of waterfall/spectrum in analog mode, portaudio framesperbuffer to prevent breakup on linux git-svn-id: https://svn.code.sf.net/p/freetel/code@2240 01035d8c-6547-0410-b346-abe4f91aad63 --- fdmdv2-dev/README.txt | 76 ++- fdmdv2-dev/src/fdmdv2_main.cpp | 668 ++++++++++++++----------- fdmdv2-dev/src/fdmdv2_main.h | 2 + fdmdv2-dev/src/fdmdv2_plot_scatter.cpp | 75 ++- 4 files changed, 502 insertions(+), 319 deletions(-) diff --git a/fdmdv2-dev/README.txt b/fdmdv2-dev/README.txt index 86310b0e..73b276aa 100644 --- a/fdmdv2-dev/README.txt +++ b/fdmdv2-dev/README.txt @@ -148,21 +148,26 @@ configure emacs: TODO ==== -[ ] test frames +[ ] Open R&D questions + + Goal is to develop an open source DV mode that performs comparably to SSB + [ ] Does 700 perform OK next to SSB? + + approx same tx pk level (hard to measure exactly) + + try some low SNR channels + + try some fast fading/nasty channels + [ ] Is 700 speech quality acceptable? + +[X] test frames [X] freedv API support [X] BER displayed on GUI for 700 and 1600 - [ ] plot error patterns for 700 and 1600 + [X] plot error patterns for 700 and 1600 + callback for error patterns, or poll via stats interface - [ ] plot error histograms for 700 and 1600 + [X] plot error histograms for 700 and 1600 + map bit error to carrier, have done this in tcohpsk? + how to reset histogram? On error reset? + histogram screen ... new code? + test with filter - [ ] Plot per carrier average Es/No, or even just Es - + good proxy for error histograms if No is constant - + this is just a "slow" FFT option I think...... -[X] Mel Bugs +[X] Bugs [X] resync issue [X] equalise power on 700 and 1600 [X] research real and complex PAPR @@ -175,20 +180,34 @@ TODO [X] space bar keys PTT when entering text info box [X] checksum based txt reception + only print if valid + [X] IC7200 audio breakup [ ] short varicode doesn't work + #ifdef-ed out for now + cld be broken in freedv_api + [X] On 700 audio sounds tinny and clicky when out of sync compared to 1600 why? + + clue: only when analog not pressed + + this was 7.5 to 8kHz interpolator bug + [X] spectrum and waterfall scale changes when analog pressed + [ ] ocassional test frames error counter goes crazy + [ ] 700 syncs up to 1000Hz sine waves + + shouldn't trigger sync logic, will be a problem with carriers + [ ] "clip" led, encourage people to adjust gain to hit that occ when speaking [ ] FreeDV 700 improvements [ ] bpf filter after clipping to remove clicks [ ] tcohpsk first, measure PAPR, impl loss [ ] error masking + [ ] C version + [ ] training off air? Switchable? [ ] excitation params [ ] training [ ] plotting other demod stats like ch ampl and phase ests [ ] profile with perf, different libresample routine [ ] check for occassional freedv 700 loss of sync + scatter seems to jump + [ ] switchable diversity (narrowband) option + + measure difference on a few channels + + blog [X] win32 [X] X-compile works @@ -203,11 +222,48 @@ TODO [X] long varicode default [X] option to _not_ require checksum, on by default [X] default squelch 2dB + [X] scatter diagram tweaks + + e.g. meaningful plots on fading channels in real time + [X] agc with hysteresis + + changed to log steps + [X] longer persistance + + changed to 6 seconds + [X] diversity addtions on 700 + + still not real obvious on plot + + might be useful to make this switchable + [X] scatter diagram different colours/carrier + [ ] remember what mode you were in [ ] cmd line file decode [ ] Waterfall direction - [ ] documentation or use, walk through,you tube, blog posts - [ ] scatter diagram agc with hysteresis - [ ] scatter diagram useful on fading channels in real time (longer persistance?) + [ ] documentation or use, walk through, you tube, blog posts [ ] Web support for Presence/spotting hooks +================= +USER GUIDE NOTES +================= + +TODO: Put this in a more usable form, video tutorials etc + +1/ Error Histogram. Displays BER of each carrier when in "test frame" + mode. As each QPSK carrier has 2 bits there are 2*Nc histogram + points. + + Ideally all carriers will have about the same BER (+/- 20% after 5000 + total bit errors). However problems can occur with filtering in the + tx path. If one carrier has less power, then it will have a higher + BER. The errors in this carrier will tend to dominate overall + BER. For example if one carrier is attenuated due to SSB filter + ripple in the tx path then the BER on that carrier will be higher. + This is bad news for DV. + + Suggested usage: Transmit FreeDV in test frame mode. Use a 2nd rx + (or get a friend) to monitor your rx signal with FreeDV in test frame + mode. Adjust your rx SNR to get a BER of a few % (e.g. reduce tx power, + use a short antenna for the rx, point your beam away, adjust rx RF + gain). Monitor the error histogram for a few minutes, until you have + say 5000 total bit errors. You have a problem if the BER of any + carrier is more than 20% different from the rest. + + A typical issue will be one carrier at 1.0, the others at 0.5, + indicating the poorer carrier BER is twice the larger. diff --git a/fdmdv2-dev/src/fdmdv2_main.cpp b/fdmdv2-dev/src/fdmdv2_main.cpp index a523f3e7..2adec2c6 100644 --- a/fdmdv2-dev/src/fdmdv2_main.cpp +++ b/fdmdv2-dev/src/fdmdv2_main.cpp @@ -19,6 +19,7 @@ // along with this program; if not, see . // //========================================================================== + #include "fdmdv2_main.h" #define wxUSE_FILEDLG 1 @@ -33,6 +34,8 @@ // back functions // ------------------------------------------------------------------ +int g_in, g_out; + // Global Codec2 & modem states - just one reqd for tx & rx int g_Nc; int g_mode; @@ -101,8 +104,10 @@ int g_recFileFromRadioEventId; SNDFILE *g_sfPlayFileFromRadio; bool g_playFileFromRadio; +int g_sfFs; bool g_loopPlayFileFromRadio; int g_playFileFromRadioEventId; +float g_blink; wxWindow *g_parent; @@ -239,7 +244,7 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent) wxGetApp().m_rxNbookCtrl = pConfig->Read(wxT("/MainFrame/rxNbookCtrl"), (long)0); - g_SquelchActive = pConfig->Read(wxT("/Audio/SquelchActive"), 1); + g_SquelchActive = pConfig->Read(wxT("/Audio/SquelchActive"), (long)0); g_SquelchLevel = pConfig->Read(wxT("/Audio/SquelchLevel"), (int)(SQ_DEFAULT_SNR*2)); g_SquelchLevel /= 2.0; @@ -314,7 +319,7 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent) if(wxGetApp().m_show_test_frame_errors_hist) { // Add Test Frame Errors window - m_panelTestFrameErrorsHist = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 1.0, 1.0/FDMDV_NC_MAX, 0.0, 1.0, 1.0/FDMDV_NC_MAX, 0.1, "%3.2f", 0); + m_panelTestFrameErrorsHist = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 1.0, 1.0/(2*FDMDV_NC_MAX), 0.0, 1.0, 1.0/FDMDV_NC_MAX, 0.1, "%3.2f", 0); m_auiNbookCtrl->AddPage(m_panelTestFrameErrorsHist, L"Test Frame Histogram", true, wxNullBitmap); } @@ -415,6 +420,12 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent) wxGetApp().m_FreeDV700txClip = (float)pConfig->Read(wxT("/FreeDV700/txClip"), t); + int mode = pConfig->Read(wxT("/Audio/mode"), (long)0); + if (mode == 0) + m_rb1600->SetValue(1); + if (mode == 1) + m_rb700->SetValue(1); + pConfig->SetPath(wxT("/")); // this->Connect(m_menuItemHelpUpdates->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnHelpCheckUpdatesUI)); @@ -608,6 +619,13 @@ MainFrame::~MainFrame() pConfig->Write(wxT("/Filter/SpkOutEQEnable"), wxGetApp().m_SpkOutEQEnable); pConfig->Write(wxT("/FreeDV700/txClip"), wxGetApp().m_FreeDV700txClip); + + int mode; + if (m_rb1600->GetValue()) + mode = 0; + if (m_rb700->GetValue()) + mode = 1; + pConfig->Write(wxT("/Audio/mode"), mode); } //m_togRxID->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnRxIDUI), NULL, this); @@ -841,7 +859,8 @@ void MainFrame::lowerRTS(void) //---------------------------------------------------------------- void MainFrame::OnTimer(wxTimerEvent &evt) { - int r; + + int r,c; if (m_panelWaterfall->checkDT()) { m_panelWaterfall->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz); @@ -853,8 +872,33 @@ void MainFrame::OnTimer(wxTimerEvent &evt) m_panelSpectrum->m_newdata = true; m_panelSpectrum->Refresh(); - for (r=0; radd_new_samples(&g_stats.rx_symbols[r][0]); + /* update scatter plot -----------------------------------------------------------------*/ + + for (r=0; rmode == FREEDV_MODE_1600) { + m_panelScatter->add_new_samples(&g_stats.rx_symbols[r][0]); + } + + if (g_pfreedv->mode == FREEDV_MODE_700) { + + /* + FreeDV 700 uses diversity, so combine symbols for + scatter plot, as combined symbols are used for + demodulation. Note we need to use a copy of the + symbols, as we are not sure when the stats will be + updated. + */ + + COMP rx_symbols_copy[g_Nc/2]; + + for(c=0; cadd_new_samples(rx_symbols_copy); + } + + } + m_panelScatter->Refresh(); // Oscilliscope type speech plots ------------------------------------------------------- @@ -1130,7 +1174,7 @@ void MainFrame::OnTimer(wxTimerEvent &evt) for(b=0; badd_new_sample(b, b + 0.8*error_pattern[i]); - g_error_hist[b/2] += error_pattern[i]; + g_error_hist[b] += error_pattern[i]; } //if (b%2) // printf("g_error_hist[%d]: %d\n", b/2, g_error_hist[b/2]); @@ -1141,30 +1185,29 @@ void MainFrame::OnTimer(wxTimerEvent &evt) if (g_error_hist[b] > max_hist) max_hist = g_error_hist[b]; - m_panelTestFrameErrorsHist->add_new_short_samples(0, g_error_hist, FDMDV_NC_MAX, max_hist); + m_panelTestFrameErrorsHist->add_new_short_samples(0, g_error_hist, 2*FDMDV_NC_MAX, max_hist); } if (g_pfreedv->mode == FREEDV_MODE_700) { + int c; + /* FreeDV 700 mapping from error pattern to bit on each carrier. Note we don't have access to carriers before diversity re-combination, so this won't give us the full picture, we have to assume Nc/2 carriers. */ - for(b=0; badd_new_sample(b, b + 0.8*error_pattern[i]); - g_error_hist[b/2] += error_pattern[i]; - } - //if (b%2) - // printf("g_error_hist[%d]: %d\n", b/2, g_error_hist[b/2]); + for(i=0; iadd_new_sample(c, c + 0.8*error_pattern[i]); + g_error_hist[c] += error_pattern[i]; + //printf("i: %d c: %d\n", i, c); } int max_hist = 0; - for(b=0; b max_hist) max_hist = g_error_hist[b]; - m_panelTestFrameErrorsHist->add_new_short_samples(0, g_error_hist, FDMDV_NC_MAX, max_hist); - + m_panelTestFrameErrorsHist->add_new_short_samples(0, g_error_hist, 2*FDMDV_NC_MAX, max_hist); } m_panelTestFrameErrors->Refresh(); @@ -1187,10 +1230,24 @@ void MainFrame::OnTimer(wxTimerEvent &evt) for(i=0; iSetSpamTimerLight(true); -} + // Blink file playback status line + + if (g_playFileFromRadio) { + g_blink += DT; + //fprintf(g_logfile, "g_blink: %f\n", g_blink); + if ((g_blink >= 1.0) && (g_blink < 2.0)) + SetStatusText(wxT("Playing into from radio"), 0); + if (g_blink >= 2.0) { + SetStatusText(wxT(""), 0); + g_blink = 0.0; + } + } + +} #endif + //------------------------------------------------------------------------- // OnCloseFrame() //------------------------------------------------------------------------- @@ -1436,10 +1493,16 @@ void MainFrame::OnTogBtnSplitClick(wxCommandEvent& event) { //------------------------------------------------------------------------- void MainFrame::OnTogBtnAnalogClick (wxCommandEvent& event) { - if (g_analog == 0) + if (g_analog == 0) { g_analog = 1; - else + m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(FS/2))); + m_panelWaterfall->setFs(FS); + } + else { g_analog = 0; + m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(g_pfreedv->modem_sample_rate/2))); + m_panelWaterfall->setFs(g_pfreedv->modem_sample_rate); + } g_State = 0; g_stats.snr_est = 0; @@ -1464,7 +1527,7 @@ void MainFrame::OnBerReset(wxCommandEvent& event) g_pfreedv->total_bits = 0; g_pfreedv->total_bit_errors = 0; int i; - for(i=0; imodem_sample_rate; } } g_sfPlayFileFromRadio = sf_open(soundFile, SFM_READ, &sfInfo); + g_sfFs = sfInfo.samplerate; if(g_sfPlayFileFromRadio == NULL) { wxString strErr = sf_strerror(NULL); @@ -1642,9 +1707,14 @@ void MainFrame::OnPlayFileFromRadio(wxCommandEvent& event) // Huh?! I just copied wxWidgets-2.9.4/samples/dialogs .... g_loopPlayFileFromRadio = static_cast(ctrl)->getLoopPlayFileToMicIn(); - SetStatusText(wxT("Playing File: ") + fileName + wxT(" into From Radio") , 0); - printf("OnPlayFileFromRadio:: Playing File\n"); + SetStatusText(wxT("Playing into from radio"), 0); + if(extension == wxT("raw")) { + wxString stringnumber = wxString::Format(wxT("%d"), (int)sfInfo.samplerate); + SetStatusText(wxT("raw file assuming Fs=") + stringnumber, 1); + } + fprintf(g_logfile, "OnPlayFileFromRadio:: Playing File\n"); g_playFileFromRadio = true; + g_blink = 0.0; } } @@ -2041,10 +2111,12 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event) if (m_rb1600->GetValue()) { g_mode = FREEDV_MODE_1600; g_Nc = 16; + m_panelScatter->setNc(g_Nc); } if (m_rb700->GetValue()) { g_mode = FREEDV_MODE_700; g_Nc = 14; + m_panelScatter->setNc(g_Nc/2-1); /* due to diversity, -1 due to no pilot like FreeDV 1600 */ } // init freedv states @@ -2057,9 +2129,9 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event) g_pfreedv->error_pattern_callback_state = (void*)m_panelTestFrameErrors; g_pfreedv->freedv_put_error_pattern = &my_freedv_put_error_pattern; g_error_pattern_fifo = fifo_create(2*g_pfreedv->sz_error_pattern); - g_error_hist = new short[FDMDV_NC_MAX]; + g_error_hist = new short[FDMDV_NC_MAX*2]; int i; - for(i=0; isetNc(g_Nc); - // init Codec 2 LPC Post Filter codec2_set_lpc_post_filter(g_pfreedv->codec2, @@ -2250,6 +2320,7 @@ void MainFrame::destroy_src(void) src_delete(g_rxUserdata->outsrc1); src_delete(g_rxUserdata->insrc2); src_delete(g_rxUserdata->outsrc2); + src_delete(g_rxUserdata->insrcsf); } void MainFrame::initPortAudioDevice(PortAudioWrap *pa, int inDevice, int outDevice, @@ -2263,31 +2334,36 @@ void MainFrame::initPortAudioDevice(PortAudioWrap *pa, int inDevice, int outDev pa->setInputDevice(inDevice); if(inDevice != paNoDevice) { - pa->setInputChannelCount(inputChannels); // stereo input - pa->setInputSampleFormat(PA_SAMPLE_TYPE); - pa->setInputLatency(pa->getInputDefaultLowLatency()); - pa->setInputHostApiStreamInfo(NULL); - } + pa->setInputChannelCount(inputChannels); // stereo input + pa->setInputSampleFormat(PA_SAMPLE_TYPE); + pa->setInputLatency(pa->getInputDefaultLowLatency()); + pa->setInputHostApiStreamInfo(NULL); + } + pa->setOutputDevice(paNoDevice); + // init output params - - pa->setOutputDevice(outDevice); - if(outDevice != paNoDevice) { - pa->setOutputChannelCount(2); // stereo output - pa->setOutputSampleFormat(PA_SAMPLE_TYPE); - pa->setOutputLatency(pa->getOutputDefaultLowLatency()); - pa->setOutputHostApiStreamInfo(NULL); - } + + pa->setOutputDevice(outDevice); + if(outDevice != paNoDevice) { + pa->setOutputChannelCount(2); // stereo output + pa->setOutputSampleFormat(PA_SAMPLE_TYPE); + pa->setOutputLatency(pa->getOutputDefaultLowLatency()); + pa->setOutputHostApiStreamInfo(NULL); + } // init params that affect input and output /* - On a good day, framesPerBuffer in callback will be PA_FPB. It - was found that you don't always get framesPerBuffer exactly - equal PA_PFB, for example when I set PA_FPB to 960 I got - framesPerBuffer = 1024. + On Linux, setting this to wxGetApp().m_framesPerBuffer caused + intermittant break up on the audio from my IC7200 on Ubuntu 14. + After a day of bug hunting I found that 0, as recommended by the + PortAudio docunmentation, fixed the problem. */ - pa->setFramesPerBuffer(wxGetApp().m_framesPerBuffer); + + //pa->setFramesPerBuffer(wxGetApp().m_framesPerBuffer); + pa->setFramesPerBuffer(0); + pa->setSampleRate(sampleRate); pa->setStreamFlags(paClipOff); } @@ -2313,20 +2389,21 @@ void MainFrame::startRxStream() m_rxInPa = new PortAudioWrap(); if(g_soundCard1InDeviceNum != g_soundCard1OutDeviceNum) - two_rx=true; + two_rx=true; if(g_soundCard2InDeviceNum != g_soundCard2OutDeviceNum) - two_tx=true; + two_tx=true; + fprintf(g_logfile, "two_rx: %d two_tx: %d\n", two_rx, two_tx); if(two_rx) - m_rxOutPa = new PortAudioWrap(); - else - m_rxOutPa = m_rxInPa; + m_rxOutPa = new PortAudioWrap(); + else + m_rxOutPa = m_rxInPa; if (g_nSoundCards == 0) { wxMessageBox(wxT("No Sound Cards configured, use Tools - Audio Config to configure"), wxT("Error"), wxOK); delete m_rxInPa; if(two_rx) - delete m_rxOutPa; + delete m_rxOutPa; m_RxRunning = false; return; } @@ -2376,9 +2453,9 @@ void MainFrame::startRxStream() m_txInPa = new PortAudioWrap(); if(two_tx) - m_txOutPa = new PortAudioWrap(); - else - m_txOutPa = m_txInPa; + m_txOutPa = new PortAudioWrap(); + else + m_txOutPa = m_txInPa; // sanity check on sound card device numbers @@ -2391,10 +2468,10 @@ void MainFrame::startRxStream() wxMessageBox(wxT("Sound Card 2 not present"), wxT("Error"), wxOK); delete m_rxInPa; if(two_rx) - delete m_rxOutPa; + delete m_rxOutPa; delete m_txInPa; if(two_tx) - delete m_txOutPa; + delete m_txOutPa; m_RxRunning = false; return; } @@ -2445,12 +2522,16 @@ void MainFrame::startRxStream() g_rxUserdata->outsrc2 = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(g_rxUserdata->outsrc2 != NULL); + g_rxUserdata->insrcsf = src_new(SRC_SINC_FASTEST, 1, &src_error); + assert(g_rxUserdata->insrcsf != NULL); + // create FIFOs used to interface between different buffer sizes g_rxUserdata->infifo1 = fifo_create(8*N48); g_rxUserdata->outfifo1 = fifo_create(10*N48); g_rxUserdata->outfifo2 = fifo_create(8*N48); g_rxUserdata->infifo2 = fifo_create(8*N48); + printf("N48: %d\n", N48); g_rxUserdata->rxinfifo = fifo_create(3 * g_pfreedv->n_speech_samples); g_rxUserdata->rxoutfifo = fifo_create(2 * g_pfreedv->n_speech_samples); @@ -2462,7 +2543,7 @@ void MainFrame::startRxStream() g_rxUserdata->micInEQEnable = wxGetApp().m_MicInEQEnable; g_rxUserdata->spkOutEQEnable = wxGetApp().m_SpkOutEQEnable; - // otional tone in left channel to reliably trigger vox + // optional tone in left channel to reliably trigger vox g_rxUserdata->leftChannelVoxTone = wxGetApp().m_leftChannelVoxTone; g_rxUserdata->voxTonePhase = 0; @@ -2507,47 +2588,47 @@ void MainFrame::startRxStream() return; } - // Start separate output stream if needed + // Start separate output stream if needed - if(two_rx) { - m_rxOutPa->setUserData(g_rxUserdata); - m_rxErr = m_rxOutPa->setCallback(rxCallback); + if(two_rx) { + m_rxOutPa->setUserData(g_rxUserdata); + m_rxErr = m_rxOutPa->setCallback(rxCallback); - m_rxErr = m_rxOutPa->streamOpen(); + m_rxErr = m_rxOutPa->streamOpen(); - if(m_rxErr != paNoError) { - wxMessageBox(wxT("Sound Card 1 Second Stream Open/Setup error."), wxT("Error"), wxOK); - delete m_rxInPa; - delete m_rxOutPa; - delete m_txOutPa; - if(two_tx) - delete m_txOutPa; - destroy_fifos(); - destroy_src(); - deleteEQFilters(g_rxUserdata); - delete g_rxUserdata; - m_RxRunning = false; - return; - } + if(m_rxErr != paNoError) { + wxMessageBox(wxT("Sound Card 1 Second Stream Open/Setup error."), wxT("Error"), wxOK); + delete m_rxInPa; + delete m_rxOutPa; + delete m_txOutPa; + if(two_tx) + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } - m_rxErr = m_rxOutPa->streamStart(); - if(m_rxErr != paNoError) { - wxMessageBox(wxT("Sound Card 1 Second Stream Start Error."), wxT("Error"), wxOK); - m_rxInPa->stop(); - m_rxInPa->streamClose(); - delete m_rxInPa; - delete m_rxOutPa; - delete m_txOutPa; - if(two_tx) - delete m_txOutPa; - destroy_fifos(); - destroy_src(); - deleteEQFilters(g_rxUserdata); - delete g_rxUserdata; - m_RxRunning = false; - return; - } - } + m_rxErr = m_rxOutPa->streamStart(); + if(m_rxErr != paNoError) { + wxMessageBox(wxT("Sound Card 1 Second Stream Start Error."), wxT("Error"), wxOK); + m_rxInPa->stop(); + m_rxInPa->streamClose(); + delete m_rxInPa; + delete m_rxOutPa; + delete m_txOutPa; + if(two_tx) + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + } // Start sound card 2 ---------------------------------------------------------- @@ -2569,13 +2650,13 @@ void MainFrame::startRxStream() m_rxInPa->streamClose(); delete m_rxInPa; if(two_rx) { - m_rxOutPa->stop(); - m_rxOutPa->streamClose(); - delete m_rxOutPa; - } + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } delete m_txInPa; if(two_tx) - delete m_txOutPa; + delete m_txOutPa; destroy_fifos(); destroy_src(); deleteEQFilters(g_rxUserdata); @@ -2590,13 +2671,13 @@ void MainFrame::startRxStream() m_rxInPa->streamClose(); delete m_rxInPa; if(two_rx) { - m_rxOutPa->stop(); - m_rxOutPa->streamClose(); - delete m_rxOutPa; - } + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } delete m_txInPa; if(two_tx) - delete m_txOutPa; + delete m_txOutPa; destroy_fifos(); destroy_src(); deleteEQFilters(g_rxUserdata); @@ -2604,63 +2685,64 @@ void MainFrame::startRxStream() m_RxRunning = false; return; } - // Start separate output stream if needed - - if (two_tx) { - - // question: can we use same callback data - // (g_rxUserdata)or both sound card callbacks? Is there a - // chance of them both being called at the same time? We - // could need a mutex ... - - m_txOutPa->setUserData(g_rxUserdata); - m_txErr = m_txOutPa->setCallback(txCallback); - m_txErr = m_txOutPa->streamOpen(); - - if(m_txErr != paNoError) { - wxMessageBox(wxT("Sound Card 2 Second Stream Open/Setup error."), wxT("Error"), wxOK); - m_rxInPa->stop(); - m_rxInPa->streamClose(); - delete m_rxInPa; - if(two_rx) { - m_rxOutPa->stop(); - m_rxOutPa->streamClose(); - delete m_rxOutPa; - } - m_txInPa->stop(); - m_txInPa->streamClose(); - delete m_txInPa; - delete m_txOutPa; - destroy_fifos(); - destroy_src(); - deleteEQFilters(g_rxUserdata); - delete g_rxUserdata; - m_RxRunning = false; - return; - } - m_txErr = m_txOutPa->streamStart(); - if(m_txErr != paNoError) { - wxMessageBox(wxT("Sound Card 2 Second Stream Start Error."), wxT("Error"), wxOK); - m_rxInPa->stop(); - m_rxInPa->streamClose(); - m_txInPa->stop(); - m_txInPa->streamClose(); - delete m_txInPa; - if(two_rx) { - m_rxOutPa->stop(); - m_rxOutPa->streamClose(); - delete m_rxOutPa; - } - delete m_txInPa; - delete m_txOutPa; - destroy_fifos(); - destroy_src(); - deleteEQFilters(g_rxUserdata); - delete g_rxUserdata; - m_RxRunning = false; - return; - } - } + + // Start separate output stream if needed + + if (two_tx) { + + // question: can we use same callback data + // (g_rxUserdata)or both sound card callbacks? Is there a + // chance of them both being called at the same time? We + // could need a mutex ... + + m_txOutPa->setUserData(g_rxUserdata); + m_txErr = m_txOutPa->setCallback(txCallback); + m_txErr = m_txOutPa->streamOpen(); + + if(m_txErr != paNoError) { + wxMessageBox(wxT("Sound Card 2 Second Stream Open/Setup error."), wxT("Error"), wxOK); + m_rxInPa->stop(); + m_rxInPa->streamClose(); + delete m_rxInPa; + if(two_rx) { + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } + m_txInPa->stop(); + m_txInPa->streamClose(); + delete m_txInPa; + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + m_txErr = m_txOutPa->streamStart(); + if(m_txErr != paNoError) { + wxMessageBox(wxT("Sound Card 2 Second Stream Start Error."), wxT("Error"), wxOK); + m_rxInPa->stop(); + m_rxInPa->streamClose(); + m_txInPa->stop(); + m_txInPa->streamClose(); + delete m_txInPa; + if(two_rx) { + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } + delete m_txInPa; + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + } } // start tx/rx processing thread @@ -2678,6 +2760,7 @@ void MainFrame::startRxStream() { wxLogError(wxT("Can't start thread!")); } + } } @@ -2908,6 +2991,7 @@ void resample_for_plot(struct FIFO *plotFifo, short buf[], int length, int fs) void txRxProcessing() { + paCallBackData *cbData = g_rxUserdata; // Buffers re-used by tx and rx processing @@ -2920,7 +3004,7 @@ void txRxProcessing() short out48k_short[4*N48]; int nout, samplerate, n_samples; - //wxLogDebug("start infifo1: %5d outfifo1: %5d\n", fifo_n(cbData->infifo1), fifo_n(cbData->outfifo1)); + //fprintf(g_logfile, "start infifo1: %5d outfifo2: %5d\n", fifo_used(cbData->infifo1), fifo_used(cbData->outfifo2)); // FreeDV 700 uses a modem sample rate of 7500 Hz which requires some special treatment @@ -2972,8 +3056,14 @@ void txRxProcessing() g_mutexProtectingCallbackData.Lock(); if (g_playFileFromRadio && (g_sfPlayFileFromRadio != NULL)) { - unsigned int n = sf_read_short(g_sfPlayFileFromRadio, in8k_short, n8k); - if (n != n8k) { + unsigned int nsf = n8k*g_sfFs/samplerate; + short insf_short[nsf]; + unsigned int n = sf_read_short(g_sfPlayFileFromRadio, insf_short, nsf); + n8k = resample(cbData->insrcsf, in8k_short, insf_short, samplerate, g_sfFs, N8, nsf); + //fprintf(g_logfile, "n: %d nsnf: %d n8k: %d\n", n, nsf, n8k); + assert(n8k <= N8); + + if (n == 0) { if (g_loopPlayFileFromRadio) sf_seek(g_sfPlayFileFromRadio, 0, SEEK_SET); else { @@ -3146,7 +3236,7 @@ void txRxProcessing() freedv_comptx(g_pfreedv, tx_fdm, in8k_short); - fdmdv_freq_shift(tx_fdm_offset, tx_fdm, g_TxFreqOffsetHz, &g_TxFreqOffsetPhaseRect, g_pfreedv->n_nom_modem_samples); + fdmdv_freq_shift_coh(tx_fdm_offset, tx_fdm, g_TxFreqOffsetHz, g_pfreedv->modem_sample_rate, &g_TxFreqOffsetPhaseRect, g_pfreedv->n_nom_modem_samples); for(i=0; in_nom_modem_samples; i++) out8k_short[i] = tx_fdm_offset[i].real; } @@ -3162,87 +3252,8 @@ void txRxProcessing() g_mutexProtectingCallbackData.Unlock(); } - //wxLogDebug(" end infifo1: %5d outfifo1: %5d\n", fifo_n(cbData->infifo1), fifo_n(cbData->outfifo1)); -} + //fprintf(g_logfile, " end infifo1: %5d outfifo2: %5d\n", fifo_used(cbData->infifo1), fifo_used(cbData->outfifo2)); -//------------------------------------------------------------------------- -// rxCallback() -//------------------------------------------------------------------------- -int MainFrame::rxCallback( - const void *inputBuffer, - void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData - ) -{ - paCallBackData *cbData = (paCallBackData*)userData; - short *rptr = (short*)inputBuffer; - short *wptr = (short*)outputBuffer; - - short indata[MAX_FPB]; - short outdata[MAX_FPB]; - - unsigned int i; - - (void) timeInfo; - (void) statusFlags; - - wxMutexLocker lock(g_mutexProtectingCallbackData); - - //if (statusFlags) - // printf("cb1 statusFlags: 0x%x\n", (int)statusFlags); - - // - // RX side processing -------------------------------------------- - // - - // assemble a mono buffer and write to FIFO - - assert(framesPerBuffer < MAX_FPB); - - if(rptr) { - for(i = 0; i < framesPerBuffer; i++, rptr += cbData->inputChannels1) - indata[i] = rptr[0]; - if (fifo_write(cbData->infifo1, indata, framesPerBuffer)) { - //wxLogDebug("infifo1 full\n"); - } - } - - // OK now set up output samples for this callback - - if(wptr) { - if (fifo_read(cbData->outfifo1, outdata, framesPerBuffer) == 0) - { - // write signal to both channels */ - for(i = 0; i < framesPerBuffer; i++, wptr += 2) - { - if (cbData->leftChannelVoxTone) { - cbData->voxTonePhase += 2.0*M_PI*VOX_TONE_FREQ/g_soundCard1SampleRate; - cbData->voxTonePhase -= 2.0*M_PI*floor(cbData->voxTonePhase/(2.0*M_PI)); - wptr[0] = VOX_TONE_AMP*cos(cbData->voxTonePhase); - //printf("%f %d\n", cbData->voxTonePhase, wptr[0]); - } - else - wptr[0] = outdata[i]; - - wptr[1] = outdata[i]; - } - } - else - { - //wxLogDebug("outfifo1 empty\n"); - // zero output if no data available - for(i = 0; i < framesPerBuffer; i++, wptr += 2) - { - wptr[0] = 0; - wptr[1] = 0; - } - } - } - - return paContinue; } //---------------------------------------------------------------- @@ -3288,7 +3299,7 @@ void per_frame_rx_processing( snr = -1.0; fdmdv_simulate_channel(&g_sig_pwr_av, rx_fdm, nin, snr); } - fdmdv_freq_shift(rx_fdm_offset, rx_fdm, g_RxFreqOffsetHz, &g_RxFreqOffsetPhaseRect, nin); + fdmdv_freq_shift_coh(rx_fdm_offset, rx_fdm, g_RxFreqOffsetHz, g_pfreedv->modem_sample_rate, &g_RxFreqOffsetPhaseRect, nin); nout = freedv_comprx(g_pfreedv, output_buf, rx_fdm_offset); fifo_write(output_fifo, output_buf, nout); @@ -3316,6 +3327,97 @@ void per_frame_rx_processing( } +//------------------------------------------------------------------------- +// rxCallback() +// +// Sound card 1 callback from PortAudio, that is used for processing rx +// side: +// +// + infifo1 is the "from radio" off air modem signal from the SSB rx that we send to the demod. +// + In single sound card mode outfifo1 is the "to speaker/headphones" decoded speech output. +// + In dual sound card mode outfifo1 is the "to radio" modulator signal to the SSB tx. +// +//------------------------------------------------------------------------- +int MainFrame::rxCallback( + const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData + ) +{ + paCallBackData *cbData = (paCallBackData*)userData; + short *rptr = (short*)inputBuffer; + short *wptr = (short*)outputBuffer; + + short indata[MAX_FPB]; + short outdata[MAX_FPB]; + + unsigned int i; + + (void) timeInfo; + (void) statusFlags; + + wxMutexLocker lock(g_mutexProtectingCallbackData); + + //fprintf(g_logfile, "cb1 statusFlags: 0x%x framesPerBuffer: %d rptr: 0x%x wptr: 0x%x \n", (int)statusFlags, + // framesPerBuffer, rptr, wptr); + + // + // RX side processing -------------------------------------------- + // + + // assemble a mono buffer and write to FIFO + + assert(framesPerBuffer < MAX_FPB); + + if(rptr) { + //fprintf(g_logfile,"in %ld %d\n", framesPerBuffer, g_in++); + //g_indata += framesPerBuffer; + for(i = 0; i < framesPerBuffer; i++, rptr += cbData->inputChannels1) + indata[i] = rptr[0]; + if (fifo_write(cbData->infifo1, indata, framesPerBuffer)) { + //fprintf(g_logfile, "infifo1 full\n"); + } + //fifo_write(cbData->outfifo1, indata, framesPerBuffer); + } + + // OK now set up output samples for this callback + + if(wptr) { + //fprintf(g_logfile,"out %ld %d\n", framesPerBuffer, g_out++); + if (fifo_read(cbData->outfifo1, outdata, framesPerBuffer) == 0) { + + // write signal to both channels + + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + if (cbData->leftChannelVoxTone) { + cbData->voxTonePhase += 2.0*M_PI*VOX_TONE_FREQ/g_soundCard1SampleRate; + cbData->voxTonePhase -= 2.0*M_PI*floor(cbData->voxTonePhase/(2.0*M_PI)); + wptr[0] = VOX_TONE_AMP*cos(cbData->voxTonePhase); + //printf("%f %d\n", cbData->voxTonePhase, wptr[0]); + } + else + wptr[0] = outdata[i]; + + wptr[1] = outdata[i]; + } + } + else { + //fprintf(g_logfile, "outfifo1 empty\n"); + // zero output if no data available + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + wptr[0] = 0; + wptr[1] = 0; + } + } + } + + return paContinue; +} + + //------------------------------------------------------------------------- // txCallback() //------------------------------------------------------------------------- @@ -3338,55 +3440,55 @@ int MainFrame::txCallback( wxMutexLocker lock(g_mutexProtectingCallbackData); // if (statusFlags) - // printf("cb2 statusFlags: 0x%x\n", (int)statusFlags); + // printf("cb2 statusFlags: 0x%x\n", (int)statusFlags); // assemble a mono buffer and write to FIFO assert(framesPerBuffer < MAX_FPB); - if(rptr) { - for(i = 0; i < framesPerBuffer; i++, rptr += cbData->inputChannels2) - indata[i] = rptr[0]; - } + if(rptr) { + for(i = 0; i < framesPerBuffer; i++, rptr += cbData->inputChannels2) + indata[i] = rptr[0]; + } //#define SC2_LOOPBACK #ifdef SC2_LOOPBACK - //TODO: This doesn't work unless using the same soundcard! + //TODO: This doesn't work unless using the same soundcard! - if(wptr) { - for(i = 0; i < framesPerBuffer; i++, wptr += 2) - { - wptr[0] = indata[i]; - wptr[1] = indata[i]; - } - } + if(wptr) { + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + wptr[0] = indata[i]; + wptr[1] = indata[i]; + } + } #else - if(rptr) - fifo_write(cbData->infifo2, indata, framesPerBuffer); + if(rptr) { + if (fifo_write(cbData->infifo2, indata, framesPerBuffer)) { + //fprintf(g_logfile, "infifo2 full\n"); + } + } // OK now set up output samples for this callback - if(wptr) { - if (fifo_read(cbData->outfifo2, outdata, framesPerBuffer) == 0) - { - // write signal to both channels */ - for(i = 0; i < framesPerBuffer; i++, wptr += 2) - { - wptr[0] = outdata[i]; - wptr[1] = outdata[i]; - } - } - else - { - // zero output if no data available - for(i = 0; i < framesPerBuffer; i++, wptr += 2) - { - wptr[0] = 0; - wptr[1] = 0; - } - } - } + if(wptr) { + if (fifo_read(cbData->outfifo2, outdata, framesPerBuffer) == 0) { + + // write signal to both channels */ + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + wptr[0] = outdata[i]; + wptr[1] = outdata[i]; + } + } + else { + //fprintf(g_logfile, "outfifo2 empty\n"); + // zero output if no data available + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + wptr[0] = 0; + wptr[1] = 0; + } + } + } #endif return paContinue; } diff --git a/fdmdv2-dev/src/fdmdv2_main.h b/fdmdv2-dev/src/fdmdv2_main.h index cde723be..25a1e708 100644 --- a/fdmdv2-dev/src/fdmdv2_main.h +++ b/fdmdv2-dev/src/fdmdv2_main.h @@ -86,6 +86,7 @@ #include "dlg_options.h" #include "varicode.h" #include "sox_biquad.h" +#include "comp_prim.h" #define _USE_TIMER 1 #define _USE_ONIDLE 1 @@ -272,6 +273,7 @@ typedef struct SRC_STATE *outsrc1; SRC_STATE *insrc2; SRC_STATE *outsrc2; + SRC_STATE *insrcsf; // FIFOs attached to first sound card diff --git a/fdmdv2-dev/src/fdmdv2_plot_scatter.cpp b/fdmdv2-dev/src/fdmdv2_plot_scatter.cpp index b9f5f7eb..3c0b155e 100644 --- a/fdmdv2-dev/src/fdmdv2_plot_scatter.cpp +++ b/fdmdv2-dev/src/fdmdv2_plot_scatter.cpp @@ -54,7 +54,7 @@ PlotScatter::PlotScatter(wxFrame* parent) : PlotPanel(parent) } -// changing number of carriers changes number of symoles to plot +// changing number of carriers changes number of symbols to plot void PlotScatter::setNc(int Nc) { Nsym = Nc+1; assert(Nsym <= (FDMDV_NC_MAX+1)); @@ -69,9 +69,29 @@ void PlotScatter::draw(wxAutoBufferedPaintDC& dc) { float x_scale; float y_scale; - int i,j; + int i; int x; int y; + wxColour sym_to_colour[] = {wxColor(0,0,255), + wxColor(0,255,0), + wxColor(0,255,255), + wxColor(255,0,0), + wxColor(255,0,255), + wxColor(255,255,0), + wxColor(255,255,255), + wxColor(0,0,255), + wxColor(0,255,0), + wxColor(0,255,255), + wxColor(255,0,0), + wxColor(255,0,255), + wxColor(255,255,0), + wxColor(255,255,255), + wxColor(0,0,255), + wxColor(0,255,0), + wxColor(0,255,255), + wxColor(255,0,0), + wxColor(255,0,255) + }; m_rCtrl = GetClientRect(); m_rGrid = m_rCtrl; @@ -87,25 +107,9 @@ void PlotScatter::draw(wxAutoBufferedPaintDC& dc) dc.DrawRectangle(m_rPlot); wxPen pen; - pen.SetColour(LIGHT_GREEN_COLOR); - pen.SetWidth(1); - dc.SetPen(pen); + pen.SetWidth(1); // note this is ignored by DrawPoint - // shift memory - - for(i = 0; i < scatterMemSyms - Nsym; i++) - { - m_mem[i] = m_mem[i+Nsym]; - } - - // new samples - - for(j=0; i < scatterMemSyms; i++,j++) - { - m_mem[i] = m_new_samples[j]; - } - - // automatically scale + // automatically scale, first measure the maximum value float max_xy = 1E-12; float real,imag; @@ -117,13 +121,21 @@ void PlotScatter::draw(wxAutoBufferedPaintDC& dc) if (imag > max_xy) max_xy = imag; } + + // smooth it out and set a lower limit to prevent didev by 0 issues + m_filter_max_xy = BETA*m_filter_max_xy + (1 - BETA)*2.5*max_xy; if (m_filter_max_xy < 0.001) m_filter_max_xy = 0.001; - //printf("max_xy: %f m_filter_max_xy: %f\n", max_xy, m_filter_max_xy); - x_scale = (float)m_rGrid.GetWidth()/m_filter_max_xy; - y_scale = (float)m_rGrid.GetHeight()/m_filter_max_xy; + // quantise to log steps to prevent scatter scaling bobbing about too + // much as scaling varies + + float quant_m_filter_max_xy = exp(floor(0.5+log(m_filter_max_xy))); + //printf("max_xy: %f m_filter_max_xy: %f quant_m_filter_max_xy: %f\n", max_xy, m_filter_max_xy, quant_m_filter_max_xy); + + x_scale = (float)m_rGrid.GetWidth()/quant_m_filter_max_xy; + y_scale = (float)m_rGrid.GetHeight()/quant_m_filter_max_xy; // draw all samples @@ -133,6 +145,8 @@ void PlotScatter::draw(wxAutoBufferedPaintDC& dc) y = y_scale * m_mem[i].imag + m_rGrid.GetHeight()/2; x += PLOT_BORDER + XLEFT_OFFSET; y += PLOT_BORDER; + pen.SetColour(sym_to_colour[i%Nsym]); + dc.SetPen(pen); dc.DrawPoint(x, y); } } @@ -142,11 +156,20 @@ void PlotScatter::draw(wxAutoBufferedPaintDC& dc) //---------------------------------------------------------------- void PlotScatter::add_new_samples(COMP samples[]) { - int i; + int i,j; + + // shift memory + + for(i = 0; i < scatterMemSyms - Nsym; i++) + { + m_mem[i] = m_mem[i+Nsym]; + } - for(i = 0; i < Nsym; i++) + // new samples + + for(j=0; i < scatterMemSyms; i++,j++) { - m_new_samples[i] = samples[i]; + m_mem[i] = samples[j]; } } -- 2.25.1