// ----------------------------------------------------------------------------
// trx.cxx -- Main transmit/receive control loop / thread
//
// Copyright (C) 2006-2010
// Dave Freese, W1HKJ
// Copyright (C) 2007-2010
// Stelios Bounanos, M0GLD
//
// This file is part of fldigi. Adapted in part from code contained in gmfsk
// source code distribution.
//
// Fldigi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Fldigi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with fldigi. If not, see .
// ----------------------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#include
#include "trx.h"
#include "main.h"
#include "fl_digi.h"
#include "ascii.h"
#include "misc.h"
#include "configuration.h"
#include "status.h"
#include "dtmf.h"
#include "soundconf.h"
#include "ringbuffer.h"
#include "qrunner.h"
#include "debug.h"
#include "nullmodem.h"
#include "macros.h"
#include "rigsupport.h"
#include "psm/psm.h"
#include "icons.h"
#include "fft-monitor.h"
#include "audio_alert.h"
extern fftmon *fft_modem;
#include "spectrum_viewer.h"
#if BENCHMARK_MODE
# include "benchmark.h"
#endif
LOG_FILE_SOURCE(debug::LOG_MODEM);
using namespace std;
void trx_reset_loop();
void trx_start_modem_loop();
void trx_receive_loop();
void trx_transmit_loop();
void trx_tune_loop();
static void trx_signal_state(void);
//#define DEBUG
/* ---------------------------------------------------------------------- */
static sem_t* trx_sem;
static pthread_t trx_thread;
state_t trx_state;
modem *active_modem = 0;
cRsId *ReedSolomon = 0;
cDTMF *dtmf = 0;
SoundBase *RXscard = 0;
bool RXsc_is_open = false;
bool TXsc_is_open = false;
SoundBase *TXscard = 0;
static int current_RXsamplerate = 0;
static int current_TXsamplerate = 0;
static int _trx_tune;
// Ringbuffer for the audio "history". A pointer into this buffer
// is also passed to the waterfall signal drawing routines.
#define NUMMEMBUFS 1024
static ringbuffer trxrb(ceil2(NUMMEMBUFS * SCBLOCKSIZE));
static float fbuf[SCBLOCKSIZE];
bool bHistory = false;
bool bHighSpeed = false;
static double hsbuff[SCBLOCKSIZE];
static bool trxrunning = false;
extern bool trx_inhibit;
bool rx_only = false;
#include "tune.cxx"
//=============================================================================
// Draws the xmit data one WF_BLOCKSIZE-sized block at a time
static void trx_xmit_wfall_draw(int samplerate)
{
ENSURE_THREAD(TRX_TID);
ringbuffer::vector_type rv[2];
rv[0].buf = 0;
rv[1].buf = 0;
#define block_read_(vec_) \
while (vec_.len >= WF_BLOCKSIZE) { \
wf->sig_data(vec_.buf, WF_BLOCKSIZE); \
REQ(&waterfall::handle_sig_data, wf); \
vec_.len -= WF_BLOCKSIZE; \
vec_.buf += WF_BLOCKSIZE; \
trxrb.read_advance(WF_BLOCKSIZE); \
}
trxrb.get_rv(rv);
block_read_(rv[0]); // read blocks from the first vector
if (rv[0].len + rv[1].len < WF_BLOCKSIZE)
return;
if (rv[0].len == 0)
block_read_(rv[1]);
#undef block_read_
// read non-contiguous data into tmp buffer so that we can
// still draw it one block at a time
if (unlikely(trxrb.read_space() >= WF_BLOCKSIZE)) {
double buf[WF_BLOCKSIZE];
do {
trxrb.read(buf, WF_BLOCKSIZE);
wf->sig_data(buf, WF_BLOCKSIZE);
REQ(&waterfall::handle_sig_data, wf);
} while (trxrb.read_space() >= WF_BLOCKSIZE);
}
}
// Called by trx_trx_transmit_loop() to handle data that may be left in the
// ringbuffer when we stop transmitting. Will pad with zeroes to a multiple of
// WF_BLOCKSIZE.
static void trx_xmit_wfall_end(int samplerate)
{
ENSURE_THREAD(TRX_TID);
size_t pad = WF_BLOCKSIZE - trxrb.read_space() % WF_BLOCKSIZE;
if (pad == WF_BLOCKSIZE) // rb empty or multiple of WF_BLOCKSIZE
return;
ringbuffer::vector_type wv[2];
wv[0].buf = wv[1].buf = 0;
trxrb.get_wv(wv, pad);
assert(wv[0].len + wv[1].len == pad);
if (likely(wv[0].len)) { // fill first vector, write rest to second vector
memset(wv[0].buf, 0, wv[0].len * sizeof(*wv[0].buf));
if (pad > wv[0].len)
memset(wv[1].buf, 0, (pad - wv[0].len) * sizeof(*wv[1].buf));
}
else // all write space is in the second write vector
memset(wv[1].buf, 0, pad * sizeof(*wv[1].buf));
trxrb.write_advance(pad);
trx_xmit_wfall_draw(samplerate);
}
// Copy buf to the ringbuffer if it has enough space. Queue a waterfall
// request whenever there are at least WF_BLOCKSIZE samples to draw.
void trx_xmit_wfall_queue(int samplerate, const double* buf, size_t len)
{
ENSURE_THREAD(TRX_TID);
ringbuffer::vector_type wv[2];
wv[0].buf = wv[1].buf = 0;
trxrb.get_wv(wv, len);
if (unlikely(wv[0].len + wv[1].len < len)) // not enough space
return;
size_t n = MIN(wv[0].len, len);
for (size_t i = 0; i < n; i++)
wv[0].buf[i] = buf[i] * progdefaults.TxMonitorLevel;
if (len > n) { // write the remainder to the second vector
buf += n;
n = len - n;
for (size_t i = 0; i < n; i++)
wv[1].buf[i] = buf[i] * progdefaults.TxMonitorLevel;
}
trxrb.write_advance(len);
if (trxrb.read_space() >= WF_BLOCKSIZE)
trx_xmit_wfall_draw(samplerate);
}
//=============================================================================
void audio_select_failure(std::string errmsg)
{
progdefaults.btnAudioIOis = SND_IDX_NULL; // file i/o
sound_update(progdefaults.btnAudioIOis);
btnAudioIO[0]->value(0);
btnAudioIO[1]->value(0);
btnAudioIO[2]->value(0);
btnAudioIO[3]->value(1);
delete RXscard;
RXscard = 0;
fl_alert2("Could not open audio device: %s\nCheck for h/w connection, and restart fldigi", errmsg.c_str());
}
void trx_trx_receive_loop()
{
size_t numread;
assert(powerof2(SCBLOCKSIZE));
if (unlikely(!active_modem)) {
MilliSleep(10);
return;
}
#if BENCHMARK_MODE
do_benchmark();
trx_state = STATE_ENDED;
return;
#endif
if (!RXscard) {
MilliSleep(10);
return;
}
try {
if (!progdefaults.is_full_duplex || !RXsc_is_open ||
current_RXsamplerate != active_modem->get_samplerate() ) {
current_RXsamplerate = active_modem->get_samplerate();
if (RXscard) {
RXscard->Close(O_RDONLY);
RXscard->Open(O_RDONLY, current_RXsamplerate);
REQ(sound_update, progdefaults.btnAudioIOis);
RXsc_is_open = true;
}
}
}
catch (const SndException& e) {
LOG_ERROR("%s. line: %i", e.what(), __LINE__);
put_status(e.what(), 5);
if (RXscard) RXscard->Close();
RXsc_is_open = false;
current_RXsamplerate = 0;
if (progdefaults.btnAudioIOis == SND_IDX_PORT) {
sound_close();
sound_init();
}
REQ(audio_select_failure, e.what());
MilliSleep(100);
return;
}
active_modem->rx_init();
ringbuffer::vector_type rbvec[2];
rbvec[0].buf = rbvec[1].buf = 0;
if (RXscard) RXscard->flush(O_RDONLY);
while (1) {
try {
numread = 0;
if (current_RXsamplerate != active_modem->get_samplerate() ) {
current_RXsamplerate = active_modem->get_samplerate();
if (RXscard) {
RXscard->Close(O_RDONLY);
RXscard->Open(O_RDONLY, current_RXsamplerate);
REQ(sound_update, progdefaults.btnAudioIOis);
RXsc_is_open = true;
}
}
if (RXscard) {
while (numread < SCBLOCKSIZE && trx_state == STATE_RX)
numread += RXscard->Read(fbuf + numread, SCBLOCKSIZE - numread);
}
if (numread > SCBLOCKSIZE) {
LOG_ERROR("numread error %lu", (unsigned long) numread);
numread = SCBLOCKSIZE;
}
if (bHighSpeed) {
for (size_t i = 0; i < numread; i++)
hsbuff[i] = fbuf[i];
} else {
if (trxrb.write_space() == 0) // diRXscard some old data
trxrb.read_advance(SCBLOCKSIZE);
size_t room = trxrb.get_wv(rbvec, numread);
if (room < numread) {
LOG_ERROR("trxrb.get_wv(rbvec) = %d, numread = %d", (int)room, (int)numread);
} else {
// convert to double and write to rb
for (size_t i = 0; i < numread; i++)
rbvec[0].buf[i] = fbuf[i];
}
}
}
catch (const SndException& e) {
if (RXscard) RXscard->Close();
RXsc_is_open = false;
LOG_ERROR("%s. line: %i", e.what(), __LINE__);
put_status(e.what(), 5);
MilliSleep(10);
return;
}
if (trx_state != STATE_RX)
break;
if (bHighSpeed) {
bool afc = progStatus.afconoff;
progStatus.afconoff = false;
QRUNNER_DROP(true);
if (progdefaults.rsid)
ReedSolomon->receive(fbuf, numread);
else if (active_modem->get_mode() == MODE_OFDM_500F || active_modem->get_mode() == MODE_OFDM_750F || active_modem->get_mode() == MODE_OFDM_2000F)
ReedSolomon->receive(fbuf, numread); // OFDM modes use RSID as AFC mechanism. Force RxRSID.
active_modem->HistoryON(true);
active_modem->rx_process(hsbuff, numread);
QRUNNER_DROP(false);
progStatus.afconoff = afc;
active_modem->HistoryON(false);
} else {
trxrb.write_advance(numread);
wf->sig_data(rbvec[0].buf, numread);
if (!trx_inhibit)
REQ(&waterfall::handle_sig_data, wf);
if (!bHistory) {
if (fft_modem && spectrum_viewer->visible())
fft_modem->rx_process(rbvec[0].buf, numread);
active_modem->rx_process(rbvec[0].buf, numread);
if (audio_alert)
audio_alert->monitor(rbvec[0].buf, numread, current_RXsamplerate);
if (progdefaults.rsid)
ReedSolomon->receive(fbuf, numread);
else if (active_modem->get_mode() == MODE_OFDM_500F || active_modem->get_mode() == MODE_OFDM_750F || active_modem->get_mode() == MODE_OFDM_2000F)
ReedSolomon->receive(fbuf, numread); // OFDM modes use RSID as AFC mechanism. Force RxRSID.
dtmf->receive(fbuf, numread);
} else {
bool afc = progStatus.afconoff;
progStatus.afconoff = false;
QRUNNER_DROP(true);
active_modem->HistoryON(true);
trxrb.get_rv(rbvec);
if (rbvec[0].len)
active_modem->rx_process(rbvec[0].buf, rbvec[0].len);
if (rbvec[1].len)
active_modem->rx_process(rbvec[1].buf, rbvec[1].len);
QRUNNER_DROP(false);
progStatus.afconoff = afc;
bHistory = false;
active_modem->HistoryON(false);
}
}
}
if (trx_state == STATE_RESTART)
return;
if (!progdefaults.is_full_duplex ) {
if (RXscard) RXscard->Close(O_RDONLY);
RXsc_is_open = false;
}
}
//=============================================================================
void trx_trx_transmit_loop()
{
if (rx_only) return;
if (!TXscard) {
MilliSleep(10);
return;
}
if (active_modem) {
try {
if (current_TXsamplerate != active_modem->get_samplerate() || !TXsc_is_open) {
current_TXsamplerate = active_modem->get_samplerate();
if (TXscard) {
TXscard->Close(O_WRONLY);
TXscard->Open(O_WRONLY, current_TXsamplerate);
TXsc_is_open = true;
}
}
} catch (const SndException& e) {
LOG_ERROR("%s. line: %i", e.what(), __LINE__);
put_status(e.what(), 1);
current_TXsamplerate = 0;
MilliSleep(10);
return;
}
if ((active_modem != ssb_modem) &&
(active_modem != anal_modem) &&
!active_modem->XMLRPC_CPS_TEST &&
!PERFORM_CPS_TEST ) {
push2talk->set(true);
REQ(&waterfall::set_XmtRcvBtn, wf, true);
}
active_modem->tx_init();
bool _txrsid = false;
if ( ReedSolomon->assigned(active_modem->get_mode()) && (progdefaults.TransmitRSid || progStatus.n_rsids != 0))
_txrsid = true;
else if (ReedSolomon->assigned(active_modem->get_mode()) && (active_modem->get_mode() == MODE_OFDM_500F || active_modem->get_mode() == MODE_OFDM_750F || active_modem->get_mode() == MODE_OFDM_2000F) )
_txrsid = true; // RSID is used as header/preamble for OFDM modes. Make mandatory.
if (_txrsid) {
if (progStatus.n_rsids < 0) {
for (int i = 0; i > progStatus.n_rsids; i--) {
ReedSolomon->send(true);
MilliSleep(200);
}
} else if ( progStatus.n_rsids > 0 ) {
for (int i = 0; i < progStatus.n_rsids; i++) {
ReedSolomon->send(true);
MilliSleep(200);
}
MilliSleep(200);
if (progStatus.n_rsids == 1) progStatus.n_rsids = 0;
} else
ReedSolomon->send(true);
}
if (progStatus.n_rsids >= 0) {
active_modem->tx_sample_count = 0;
active_modem->tx_sample_rate = active_modem->get_samplerate();
while (trx_state == STATE_TX) {
try {
if (!progdefaults.DTMFstr.empty())
dtmf->send();
if (active_modem->tx_process() < 0) {
active_modem->cwid();
if (trx_state != STATE_ABORT)
trx_state = STATE_RX;
}
}
catch (const SndException& e) {
if (TXscard) TXscard->Close();
TXsc_is_open = false;
LOG_ERROR("%s", e.what());
put_status(e.what(), 5);
current_TXsamplerate = 0;
MilliSleep(10);
return;
}
}
} else
if (trx_state != STATE_ABORT && trx_state != STATE_RESTART)
trx_state = STATE_RX;
if (ReedSolomon->assigned(active_modem->get_mode()) &&
progdefaults.TransmitRSid &&
progdefaults.rsid_post &&
progStatus.n_rsids >= 0) ReedSolomon->send(false);
progStatus.n_rsids = 0;
trx_xmit_wfall_end(current_TXsamplerate);
if (TXscard) TXscard->flush();
if (trx_state == STATE_RX) {
if (!progdefaults.is_full_duplex) {
if (TXscard) TXscard->Close(O_WRONLY);
TXsc_is_open = false;
}
}
} else
MilliSleep(10);
push2talk->set(false);
REQ(&waterfall::set_XmtRcvBtn, wf, false);
psm_transmit_ended(PSM_STOP);
if (progStatus.timer)
REQ(startMacroTimer);
WriteARQ(0x06);
}
//=============================================================================
void trx_tune_loop()
{
if (rx_only) return;
if (!TXscard) {
MilliSleep(10);
return;
}
if (active_modem) {
try {
if (!progdefaults.is_full_duplex || !TXsc_is_open ||
current_TXsamplerate != active_modem->get_samplerate() ) {
current_TXsamplerate = active_modem->get_samplerate();
if (TXscard) TXscard->Close(O_WRONLY);
if (TXscard) {
TXscard->Open(O_WRONLY, current_TXsamplerate);
TXsc_is_open = true;
}
}
} catch (const SndException& e) {
LOG_ERROR("%s. line: %i", e.what(), __LINE__);
put_status(e.what(), 1);
MilliSleep(10);
current_TXsamplerate = 0;
return;
}
push2talk->set(true);
active_modem->tx_init();
try {
if (active_modem->get_mode() == MODE_CW &&
(use_nanoIO ||
progStatus.WK_online ||
progdefaults.CW_KEYLINE_on_cat_port ||
CW_KEYLINE_isopen)) {
if (CW_KEYLINE_isopen || progdefaults.CW_KEYLINE_on_cat_port)
active_modem->CW_KEYLINE(1);
else if (use_nanoIO)
nanoCW_tune(1);
else WK_tune(1);
cwio_ptt(1);
cwio_key(1);
REQ(&waterfall::set_XmtRcvBtn, wf, true);
while (trx_state == STATE_TUNE) MilliSleep(10);
if (CW_KEYLINE_isopen) active_modem->CW_KEYLINE(0);
else if (use_nanoIO) nanoCW_tune(0);
else WK_tune(0);
cwio_key(0);
cwio_ptt(0);
} else {
while (trx_state == STATE_TUNE) {
if (_trx_tune == 0) {
REQ(&waterfall::set_XmtRcvBtn, wf, true);
xmttune::keydown(active_modem->get_txfreq_woffset(), TXscard);
_trx_tune = 1;
} else
xmttune::tune(active_modem->get_txfreq_woffset(), TXscard);
}
xmttune::keyup(active_modem->get_txfreq_woffset(), TXscard);
}
}
catch (const SndException& e) {
if (TXscard) TXscard->Close();
TXsc_is_open = false;
LOG_ERROR("%s. line: %i", e.what(), __LINE__);
put_status(e.what(), 5);
MilliSleep(10);
current_TXsamplerate = 0;
return;
}
if (TXscard) TXscard->flush();
if (trx_state == STATE_RX) {
if (!progdefaults.is_full_duplex) {
if (TXscard) TXscard->Close(O_WRONLY);
TXsc_is_open = false;
}
}
_trx_tune = 0;
} else
MilliSleep(10);
push2talk->set(false);
REQ(&waterfall::set_XmtRcvBtn, wf, false);
}
//=============================================================================
void *trx_loop(void *args)
{
SET_THREAD_ID(TRX_TID);
state_t old_state = STATE_NOOP;
for (;;) {
if (unlikely(old_state != trx_state)) {
old_state = trx_state;
if (trx_state == STATE_TX || trx_state == STATE_TUNE)
trxrb.reset();
trx_signal_state();
}
LOG_DEBUG("trx state %s",
trx_state == STATE_ABORT ? "abort" :
trx_state == STATE_ENDED ? "ended" :
trx_state == STATE_RESTART ? "restart" :
trx_state == STATE_NEW_MODEM ? "new modem" :
trx_state == STATE_TX ? "tx" :
trx_state == STATE_TUNE ? "tune" :
trx_state == STATE_RX ? "rx" :
"unknown");
switch (trx_state) {
case STATE_ABORT:
delete RXscard;
RXscard = 0;
delete TXscard;
TXscard = 0;
trx_state = STATE_ENDED;
// fall through
case STATE_ENDED:
REQ(set_flrig_ptt, 0);
stop_deadman();
return 0;
case STATE_RESTART:
REQ(set_flrig_ptt, 0);
stop_deadman();
trx_reset_loop();
break;
case STATE_NEW_MODEM:
REQ(set_flrig_ptt, 0);
trx_start_modem_loop();
break;
case STATE_TX:
REQ(set_flrig_ptt, 1);
start_deadman();
trx_trx_transmit_loop();
break;
case STATE_TUNE:
REQ(set_flrig_ptt, 1);
start_deadman();
trx_tune_loop();
break;
case STATE_RX:
REQ(set_flrig_ptt, 0);
stop_deadman();
trx_trx_receive_loop();
break;
default:
LOG(debug::ERROR_LEVEL, debug::LOG_MODEM, "trx in bad state %d\n", trx_state);
MilliSleep(100);
}
}
}
//=============================================================================
static modem* new_modem;
static int new_freq;
void trx_start_modem_loop()
{
if (new_modem == active_modem) {
if (new_freq > 0 && !progdefaults.retain_freq_lock)
active_modem->set_freq(new_freq);
else if (new_freq > 0 && (active_modem->get_mode() == MODE_OFDM_500F || active_modem->get_mode() == MODE_OFDM_750F || active_modem->get_mode() == MODE_OFDM_2000F || active_modem->get_mode() == MODE_OFDM_2000))
active_modem->set_freq(new_freq); // OFDM modes use RSID as AFC mechanism. Always allow QSY of Rx Frequency.
active_modem->restart();
trx_state = STATE_RX;
if (progdefaults.show_psm_btn &&
progStatus.kpsql_enabled &&
progStatus.psm_use_histogram)
psm_reset_histogram();
return;
}
modem* old_modem = active_modem;
new_modem->init();
active_modem = new_modem;
if (new_freq > 0 && !progdefaults.retain_freq_lock)
active_modem->set_freq(new_freq);
else if (new_freq > 0 && (active_modem->get_mode() == MODE_OFDM_500F || active_modem->get_mode() == MODE_OFDM_750F || active_modem->get_mode() == MODE_OFDM_2000F || active_modem->get_mode() == MODE_OFDM_2000))
active_modem->set_freq(new_freq); // OFDM modes use RSID as AFC mechanism. Always allow QSY of Rx Frequency.
trx_state = STATE_RX;
REQ(&waterfall::opmode, wf);
REQ(set599);
if (old_modem) {
*mode_info[old_modem->get_mode()].modem = 0;
delete old_modem;
}
}
//=============================================================================
void trx_start_modem(modem* m, int f)
{
new_modem = m;
new_freq = f;
trx_state = STATE_NEW_MODEM;
}
//=============================================================================
static string reset_loop_msg;
void show_reset_loop_alert()
{
// if (btnAudioIO[0]) {
btnAudioIO[0]->value(0);
btnAudioIO[1]->value(0);
btnAudioIO[2]->value(0);
btnAudioIO[3]->value(1);
fl_alert2("%s", reset_loop_msg.c_str());
// }
}
void trx_reset_loop()
{
if (RXscard) {
RXscard->Close();
RXsc_is_open = false;
delete RXscard;
RXscard = 0;
}
if (TXscard) {
TXscard->Close();
TXsc_is_open = false;
delete TXscard;
TXscard = 0;
}
switch (progdefaults.btnAudioIOis) {
#if USE_OSS
case SND_IDX_OSS:
try {
RXscard = new SoundOSS(scDevice[0].c_str());
if (!RXscard) break;
RXscard->Open(O_RDONLY, current_RXsamplerate = 8000);
RXsc_is_open = true;
TXscard = new SoundOSS(scDevice[0].c_str());
if (!TXscard) break;
TXscard->Open(O_WRONLY, current_TXsamplerate = 8000);
TXsc_is_open = true;
} catch (...) {
reset_loop_msg = "OSS open failure";
progdefaults.btnAudioIOis = SND_IDX_NULL; // file i/o
sound_update(progdefaults.btnAudioIOis);
REQ(show_reset_loop_alert);
}
break;
#endif
#if USE_PORTAUDIO
/// All of this very convoluted logic is needed to allow a Linux user
/// to switch from PulseAudio to PortAudio. PulseAudio does not immediately
/// release the sound card resources after closing the pulse audio object.
case SND_IDX_PORT:
{
RXscard = new SoundPort(scDevice[0].c_str(), scDevice[1].c_str());
TXscard = new SoundPort(scDevice[0].c_str(), scDevice[1].c_str());
unsigned long tm1 = zmsec();
int RXret = 0, TXret = 0;
int i;
RXsc_is_open = false;
TXsc_is_open = false;
for (i = 0; i < 10; i++) { // try 10 times
try {
if (!RXret)
RXret = RXscard->Open(O_RDONLY, current_RXsamplerate = 8000);
if (progdefaults.is_full_duplex) {
if (!TXret)
TXret = TXscard->Open(O_WRONLY, current_TXsamplerate = 8000);
}
if (RXret) RXsc_is_open = true;
if (TXret) TXsc_is_open = true;
break;
} catch (const SndException& e) {
MilliSleep(50);
Fl::awake();
}
}
unsigned long tm = zmsec() - tm1;
if (tm < 0) tm = 0;
if (i == 10) {
if (RXscard) delete RXscard;
if (TXscard) delete TXscard;
RXscard = 0;
TXscard = 0;
LOG_PERROR("Port Audio device not available");
reset_loop_msg = "Port Audio device not available";
progdefaults.btnAudioIOis = SND_IDX_NULL; // file i/o
sound_update(progdefaults.btnAudioIOis);
REQ(show_reset_loop_alert);
} else {
LOG_INFO ("Port Audio device available after %0.1f seconds", tm / 1000.0 );
}
break;
}
#endif
#if USE_PULSEAUDIO
case SND_IDX_PULSE:
try {
RXscard = new SoundPulse(scDevice[0].c_str());
if (!RXscard) break;
RXscard->Open(O_RDONLY, current_RXsamplerate = 8000);
RXsc_is_open = true;
TXscard = new SoundPulse(scDevice[0].c_str());
if (!TXscard) break;
// needed to open playback device in PaVolumeControl
TXscard->Open(O_WRONLY, current_TXsamplerate = 8000);
double buffer[1024];
for (int i = 0; i < 1024; buffer[i++] = 0);
TXscard->Write_stereo(buffer, buffer, 1024);
if (progdefaults.is_full_duplex)
TXsc_is_open = true;
else {
TXscard->Close();
TXsc_is_open = false;
}
} catch (const SndException& e) {
LOG_ERROR("%s", e.what());
if (RXscard) delete RXscard;
if (TXscard) delete TXscard;
RXscard = 0;
TXscard = 0;
reset_loop_msg = "Pulse Audio error:\n";
reset_loop_msg.append(e.what());
reset_loop_msg.append("\n\nIs the server running?\nClose fldigi and execute 'pulseaudio --start'");
progdefaults.btnAudioIOis = SND_IDX_NULL; // file i/o
sound_update(progdefaults.btnAudioIOis);
REQ(show_reset_loop_alert);
}
break;
#endif
case SND_IDX_NULL:
RXscard = new SoundNull;
TXscard = new SoundNull;
current_RXsamplerate = current_TXsamplerate = 0;
break;
default:
abort();
}
trx_state = STATE_RX;
}
//=============================================================================
void trx_reset(void)
{
trx_state = STATE_RESTART;
}
//=============================================================================
void trx_start(void)
{
#if !BENCHMARK_MODE
if (trxrunning) {
LOG(debug::ERROR_LEVEL, debug::LOG_MODEM, "trx already running!");
return;
}
if (RXscard) {
delete RXscard;
RXscard = 0;
}
if (TXscard) {
delete TXscard;
TXscard = 0;
}
if (ReedSolomon) delete ReedSolomon;
if (dtmf) delete dtmf;
switch (progdefaults.btnAudioIOis) {
#if USE_OSS
case SND_IDX_OSS:
RXscard = new SoundOSS(scDevice[0].c_str());
TXscard = new SoundOSS(scDevice[0].c_str());
break;
#endif
#if USE_PORTAUDIO
case SND_IDX_PORT:
RXscard = new SoundPort(scDevice[0].c_str(), scDevice[1].c_str());
TXscard = new SoundPort(scDevice[0].c_str(), scDevice[1].c_str());
break;
#endif
#if USE_PULSEAUDIO
case SND_IDX_PULSE:
try {
RXscard = new SoundPulse(scDevice[0].c_str());
if (!RXscard) break;
TXscard = new SoundPulse(scDevice[0].c_str());
if (!TXscard) break;
// needed to open playback device in PaVolumeControl
TXscard->Open(O_WRONLY, current_TXsamplerate = 8000);
double buffer[1024];
for (int i = 0; i < 1024; buffer[i++] = 0);
TXscard->Write_stereo(buffer, buffer, 1024);
if (progdefaults.is_full_duplex)
TXsc_is_open = true;
else {
TXscard->Close();
TXsc_is_open = false;
}
} catch (const SndException& e) {
LOG_ERROR("%s", e.what());
if (RXscard) delete RXscard;
if (TXscard) delete TXscard;
RXscard = 0;
TXscard = 0;
reset_loop_msg = "Pulse Audio error:\n";
reset_loop_msg.append(e.what());
reset_loop_msg.append("\n\nIs the server running?");
progdefaults.btnAudioIOis = SND_IDX_NULL; // file i/o
sound_update(progdefaults.btnAudioIOis);
REQ(show_reset_loop_alert);
}
break;
#endif
case SND_IDX_NULL:
RXscard = new SoundNull;
TXscard = new SoundNull;
break;
default:
abort();
}
current_RXsamplerate = current_TXsamplerate = 0;
ReedSolomon = new cRsId;
dtmf = new cDTMF;
#endif // !BENCHMARK_MODE
#if USE_NAMED_SEMAPHORES
char sname[32];
snprintf(sname, sizeof(sname), "trx-%u-%s", getpid(), PACKAGE_TARNAME);
if ((trx_sem = sem_open(sname, O_CREAT | O_EXCL, 0600, 0)) == (sem_t*)SEM_FAILED) {
LOG_PERROR("sem_open");
abort();
}
# if HAVE_SEM_UNLINK
if (sem_unlink(sname) == -1) {
LOG_PERROR("sem_unlink");
abort();
}
# endif
#else
trx_sem = new sem_t;
if (sem_init(trx_sem, 0, 0) == -1) {
LOG_PERROR("sem_init");
abort();
}
#endif
trx_state = STATE_RX;
_trx_tune = 0;
active_modem = 0;
if (pthread_create(&trx_thread, NULL, trx_loop, NULL) < 0) {
LOG(debug::ERROR_LEVEL, debug::LOG_MODEM, "pthread_create failed");
trxrunning = false;
exit(1);
}
trxrunning = true;
}
//=============================================================================
void trx_close()
{
LOG_INFO("%s", "closing trx thread");
int count = 1000;
active_modem->set_stopflag(true);
while (trx_state != STATE_RX && count--)
MilliSleep(10);
if (trx_state != STATE_RX) {
LOG_INFO("%s", "trx_state != STATE_RX");
exit(1);
}
count = 1000;
trx_state = STATE_ABORT;
while (trx_state != STATE_ENDED && count--)
MilliSleep(10);
if (trx_state != STATE_ENDED) {
LOG_INFO("%s", "trx_state != STATE_ENDED");
exit(2);
}
#if USE_NAMED_SEMAPHORES
if (sem_close(trx_sem) == -1)
LOG_PERROR("sem_close");
#else
if (sem_destroy(trx_sem) == -1)
LOG_PERROR("sem_destroy");
delete trx_sem;
#endif
if (RXscard) {
delete RXscard;
RXscard = 0;
}
LOG_INFO("%s", "trx thread closed");
}
//=============================================================================
void trx_transmit_psm(void) { trx_state = STATE_TX; };
void trx_transmit(void) {
if (progdefaults.show_psm_btn &&
progStatus.kpsql_enabled &&
(!PERFORM_CPS_TEST || !active_modem->XMLRPC_CPS_TEST))
psm_transmit();
else
trx_state = STATE_TX;
}
void trx_tune(void) { trx_state = STATE_TUNE; }
void trx_receive(void) { trx_state = STATE_RX; }
//=============================================================================
void trx_wait_state(void)
{
ENSURE_NOT_THREAD(TRX_TID);
sem_wait(trx_sem);
}
static void trx_signal_state(void)
{
ENSURE_THREAD(TRX_TID);
sem_post(trx_sem);
}