401 lines
12 KiB
C++
401 lines
12 KiB
C++
/*
|
|
util.h
|
|
|
|
Miscellaneous utility functions
|
|
*/
|
|
|
|
#include "main.h"
|
|
#include "codec2_fdmdv.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <strsafe.h>
|
|
#endif
|
|
|
|
// Callback from plot_spectrum & plot_waterfall. would be nice to
|
|
// work out a way to do this without globals.
|
|
extern float g_RxFreqOffsetHz;
|
|
extern float g_TxFreqOffsetHz;
|
|
extern FreeDVInterface freedvInterface;
|
|
extern int g_tx;
|
|
|
|
void clickTune(float freq) {
|
|
|
|
// The demod is hard-wired to expect a centre frequency of
|
|
// FDMDV_FCENTRE. So we want to take the signal centered on the
|
|
// click tune freq and re-centre it on FDMDV_FCENTRE. For example
|
|
// if the click tune freq is 1500Hz, and FDMDV_CENTRE is 1200 Hz,
|
|
// we need to shift the input signal centred on 1500Hz down to
|
|
// 1200Hz, an offset of -300Hz.
|
|
|
|
// Bit of an "indent" as we are often trying to get it back
|
|
// exactly in the centre
|
|
|
|
if (fabs(FDMDV_FCENTRE - freq) < 10.0) {
|
|
log_info("Requested frequency close to center, just using center.");
|
|
freq = FDMDV_FCENTRE;
|
|
}
|
|
|
|
g_TxFreqOffsetHz = freq - FDMDV_FCENTRE;
|
|
g_RxFreqOffsetHz = FDMDV_FCENTRE - freq;
|
|
log_info("g_TxFreqOffsetHz: %f g_RxFreqOffsetHz: %f", g_TxFreqOffsetHz, g_RxFreqOffsetHz);
|
|
}
|
|
|
|
bool MainApp::CanAccessSerialPort(std::string portName)
|
|
{
|
|
bool couldOpen = true;
|
|
com_handle_t com_handle = COM_HANDLE_INVALID;
|
|
|
|
#ifdef _WIN32
|
|
{
|
|
if (portName.substr(0, 3) != "COM")
|
|
{
|
|
// assume we can open if we don't have a valid port name.
|
|
return couldOpen;
|
|
}
|
|
|
|
TCHAR nameWithStrangePrefix[100];
|
|
StringCchPrintf(nameWithStrangePrefix, 100, TEXT("\\\\.\\%hs"), portName.c_str());
|
|
|
|
if((com_handle=CreateFile(nameWithStrangePrefix
|
|
,GENERIC_READ | GENERIC_WRITE/* Access */
|
|
,0 /* Share mode */
|
|
,NULL /* Security attributes */
|
|
,OPEN_EXISTING /* Create access */
|
|
,0 /* File attributes */
|
|
,NULL /* Template */
|
|
))==INVALID_HANDLE_VALUE) {
|
|
couldOpen = false;
|
|
}
|
|
else
|
|
{
|
|
CloseHandle(com_handle);
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
if (portName.substr(0, 5) != "/dev/")
|
|
{
|
|
// assume we can open if we don't have a valid port name.
|
|
return couldOpen;
|
|
}
|
|
|
|
if((com_handle=open(portName.c_str(), O_NONBLOCK|O_RDWR))== COM_HANDLE_INVALID)
|
|
{
|
|
couldOpen = false;
|
|
}
|
|
else
|
|
{
|
|
close(com_handle);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!couldOpen)
|
|
{
|
|
std::string errorMessage = "Could not open serial port " + portName + ".";
|
|
|
|
#ifdef _WIN32
|
|
errorMessage += " Please ensure that no other applications are accessing the port.";
|
|
#elif __linux
|
|
errorMessage += " Please ensure that you have permission to access the port. Adding yourself to the 'dialout' group (and logging out/back in) along with reattaching your radio to your PC will typically ensure this.";
|
|
#else
|
|
errorMessage += " Please ensure that you have permission to access the port.";
|
|
#endif
|
|
|
|
CallAfter([&, errorMessage]() {
|
|
wxMessageBox(
|
|
errorMessage,
|
|
wxT("Error"), wxOK | wxICON_ERROR, GetTopWindow());
|
|
});
|
|
}
|
|
|
|
return couldOpen;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// isReceiveOnly()
|
|
//----------------------------------------------------------------
|
|
|
|
bool MainFrame::isReceiveOnly()
|
|
{
|
|
return
|
|
wxGetApp().appConfiguration.reportingConfiguration.freedvReporterForceReceiveOnly ||
|
|
g_nSoundCards <= 1;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// OpenSerialPort()
|
|
//----------------------------------------------------------------
|
|
|
|
void MainFrame::OpenSerialPort(void)
|
|
{
|
|
if(!wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPort->IsEmpty())
|
|
{
|
|
if (wxGetApp().CanAccessSerialPort((const char*)wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPort->ToUTF8()))
|
|
{
|
|
wxGetApp().rigPttController = std::make_shared<SerialPortOutRigController>(
|
|
(const char*)wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPort->c_str(),
|
|
wxGetApp().appConfiguration.rigControlConfiguration.serialPTTUseRTS,
|
|
wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPolarityRTS,
|
|
wxGetApp().appConfiguration.rigControlConfiguration.serialPTTUseDTR,
|
|
wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPolarityDTR);
|
|
wxGetApp().rigFrequencyController = nullptr;
|
|
|
|
wxGetApp().rigPttController->onRigError += [&](IRigController*, std::string err) {
|
|
std::string fullErrMsg = "Couldn't open serial port for PTT output: " + err;
|
|
CallAfter([&]()
|
|
{
|
|
wxMessageBox(fullErrMsg, wxT("Error"), wxOK | wxICON_ERROR, this);
|
|
});
|
|
};
|
|
|
|
wxGetApp().rigPttController->connect();
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// OpenPTTInPort()
|
|
//----------------------------------------------------------------
|
|
|
|
void MainFrame::OpenPTTInPort(void)
|
|
{
|
|
if(!wxGetApp().appConfiguration.rigControlConfiguration.serialPTTInputPort->IsEmpty())
|
|
{
|
|
if (wxGetApp().CanAccessSerialPort((const char*)wxGetApp().appConfiguration.rigControlConfiguration.serialPTTInputPort->ToUTF8()))
|
|
{
|
|
wxGetApp().m_pttInSerialPort = std::make_shared<SerialPortInRigController>(
|
|
(const char*)wxGetApp().appConfiguration.rigControlConfiguration.serialPTTInputPort->c_str(),
|
|
wxGetApp().appConfiguration.rigControlConfiguration.serialPTTInputPolarityCTS);
|
|
|
|
wxGetApp().m_pttInSerialPort->onRigError += [&](IRigController*, std::string err)
|
|
{
|
|
std::string fullErr = "Couldn't open PTT input port: " + err;
|
|
CallAfter([&]()
|
|
{
|
|
wxMessageBox(fullErr, wxT("Error"), wxOK | wxICON_ERROR, this);
|
|
});
|
|
};
|
|
|
|
wxGetApp().m_pttInSerialPort->onPttChange += [&](IRigController*, bool pttState)
|
|
{
|
|
log_info("PTT input state is now %d", pttState);
|
|
GetEventHandler()->CallAfter([this, pttState]() {
|
|
if (pttState != m_btnTogPTT->GetValue())
|
|
{
|
|
m_btnTogPTT->SetValue(pttState);
|
|
|
|
// Update background color of button here because when toggling PTT via CTS,
|
|
// the background color for some reason doesn't update inside togglePTT().
|
|
m_btnTogPTT->SetBackgroundColour(m_btnTogPTT->GetValue() ? *wxRED : wxNullColour);
|
|
|
|
togglePTT();
|
|
}
|
|
});
|
|
};
|
|
|
|
wxGetApp().m_pttInSerialPort->connect();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
// ClosePTTInPort()
|
|
//----------------------------------------------------------------
|
|
|
|
void MainFrame::ClosePTTInPort(void)
|
|
{
|
|
if (wxGetApp().m_pttInSerialPort)
|
|
{
|
|
wxGetApp().m_pttInSerialPort->disconnect();
|
|
wxGetApp().m_pttInSerialPort = nullptr;
|
|
}
|
|
}
|
|
|
|
struct FIFO extern *g_txDataInFifo;
|
|
struct FIFO extern *g_rxDataOutFifo;
|
|
|
|
char my_get_next_tx_char(void *callback_state) {
|
|
short ch = 0;
|
|
|
|
codec2_fifo_read(g_txDataInFifo, &ch, 1);
|
|
return (char)ch;
|
|
}
|
|
|
|
void my_put_next_rx_char(void *callback_state, char c) {
|
|
short ch = (short)((unsigned char)c);
|
|
codec2_fifo_write(g_rxDataOutFifo, &ch, 1);
|
|
}
|
|
|
|
void freq_shift_coh(COMP rx_fdm_fcorr[], COMP rx_fdm[], float foff, float Fs, COMP *foff_phase_rect, int nin)
|
|
{
|
|
COMP foff_rect;
|
|
float mag;
|
|
int i;
|
|
|
|
foff_rect.real = cosf(2.0*M_PI*foff/Fs);
|
|
foff_rect.imag = sinf(2.0*M_PI*foff/Fs);
|
|
for(i=0; i<nin; i++) {
|
|
*foff_phase_rect = cmult(*foff_phase_rect, foff_rect);
|
|
rx_fdm_fcorr[i] = cmult(rx_fdm[i], *foff_phase_rect);
|
|
}
|
|
|
|
/* normalise digital oscillator as the magnitude can drift over time */
|
|
|
|
mag = cabsolute(*foff_phase_rect);
|
|
foff_phase_rect->real /= mag;
|
|
foff_phase_rect->imag /= mag;
|
|
}
|
|
|
|
// returns number of output samples generated by resampling
|
|
int resample(SRC_STATE *src,
|
|
short output_short[],
|
|
short input_short[],
|
|
int output_sample_rate,
|
|
int input_sample_rate,
|
|
int length_output_short, // maximum output array length in samples
|
|
int length_input_short
|
|
)
|
|
{
|
|
SRC_DATA src_data;
|
|
float* input = new float[length_input_short];
|
|
assert(input != nullptr);
|
|
|
|
float* output = new float[length_output_short];
|
|
assert(output != nullptr);
|
|
|
|
int ret;
|
|
|
|
assert(src != NULL);
|
|
|
|
src_short_to_float_array(input_short, input, length_input_short);
|
|
|
|
src_data.data_in = input;
|
|
src_data.data_out = output;
|
|
src_data.input_frames = length_input_short;
|
|
src_data.output_frames = length_output_short;
|
|
src_data.end_of_input = 0;
|
|
src_data.src_ratio = (float)output_sample_rate/input_sample_rate;
|
|
|
|
ret = src_process(src, &src_data);
|
|
if (ret != 0)
|
|
{
|
|
log_warn("Resampling failed: %s", src_strerror(ret));
|
|
}
|
|
assert(ret == 0);
|
|
|
|
assert(src_data.output_frames_gen <= length_output_short);
|
|
src_float_to_short_array(output, output_short, src_data.output_frames_gen);
|
|
|
|
delete[] input;
|
|
delete[] output;
|
|
|
|
return src_data.output_frames_gen;
|
|
}
|
|
|
|
|
|
// Decimates samples using an algorithm that produces nice plots of
|
|
// speech signals at a low sample rate. We want a low sample rate so
|
|
// we don't hammer the graphics system too hard. Saves decimated data
|
|
// to a fifo for plotting on screen.
|
|
|
|
void resample_for_plot(struct FIFO *plotFifo, short buf[], int length, int fs)
|
|
{
|
|
int decimation = fs/WAVEFORM_PLOT_FS;
|
|
int nSamples, sample;
|
|
int i, st, en, max, min;
|
|
short* dec_samples = new short[length];
|
|
assert(dec_samples != nullptr);
|
|
|
|
nSamples = length/decimation;
|
|
if (nSamples % 2) nSamples++; // dec_samples is populated in groups of two
|
|
|
|
for(sample = 0; sample < nSamples; sample += 2)
|
|
{
|
|
st = decimation*sample;
|
|
en = decimation*(sample+2);
|
|
max = min = 0;
|
|
for(i=st; i<en && i<length; i++ )
|
|
{
|
|
if (max < buf[i]) max = buf[i];
|
|
if (min > buf[i]) min = buf[i];
|
|
}
|
|
dec_samples[sample] = max;
|
|
dec_samples[sample+1] = min;
|
|
}
|
|
codec2_fifo_write(plotFifo, dec_samples, nSamples);
|
|
delete[] dec_samples;
|
|
}
|
|
|
|
// State machine to detect sync
|
|
|
|
void MainFrame::DetectSyncProcessEvent(void) {
|
|
int next_state = ds_state;
|
|
|
|
switch(ds_state) {
|
|
|
|
case DS_IDLE:
|
|
if (freedvInterface.getSync() == 1) {
|
|
next_state = DS_SYNC_WAIT;
|
|
ds_rx_time = 0;
|
|
}
|
|
break;
|
|
|
|
case DS_SYNC_WAIT:
|
|
|
|
// In this state we wait for a few seconds of valid sync.
|
|
|
|
if (freedvInterface.getSync() == 0) {
|
|
next_state = DS_IDLE;
|
|
} else {
|
|
ds_rx_time += DT;
|
|
}
|
|
|
|
if (ds_rx_time >= DS_SYNC_WAIT_TIME) {
|
|
ds_rx_time = 0;
|
|
next_state = DS_UNSYNC_WAIT;
|
|
}
|
|
break;
|
|
|
|
case DS_UNSYNC_WAIT:
|
|
|
|
// In this state we wait for sync to end
|
|
|
|
if (freedvInterface.getSync() == 0) {
|
|
ds_rx_time += DT;
|
|
if (ds_rx_time >= DS_SYNC_WAIT_TIME) {
|
|
next_state = DS_IDLE;
|
|
}
|
|
} else {
|
|
ds_rx_time = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// catch anything we missed
|
|
|
|
next_state = DS_IDLE;
|
|
}
|
|
|
|
ds_state = next_state;
|
|
}
|
|
|
|
|
|
void MainFrame::executeOnUiThreadAndWait_(std::function<void()> fn)
|
|
{
|
|
std::mutex funcMutex;
|
|
std::condition_variable funcConditionVariable;
|
|
std::unique_lock<std::mutex> funcLock(funcMutex);
|
|
|
|
CallAfter([&]() {
|
|
std::unique_lock<std::mutex> guiLock(funcMutex);
|
|
|
|
fn();
|
|
|
|
funcConditionVariable.notify_one();
|
|
});
|
|
|
|
funcConditionVariable.wait(funcLock);
|
|
}
|