kopia lustrzana https://github.com/jamescoxon/dl-fldigi
2227 wiersze
64 KiB
C++
2227 wiersze
64 KiB
C++
// ----------------------------------------------------------------------------
|
|
// wefax.cxx -- Weather Fax modem
|
|
//
|
|
// Copyright (C) 2010
|
|
// Remi Chateauneu, F4ECW
|
|
//
|
|
// This file is part of fldigi. Adapted from code contained in HAMFAX source code
|
|
// distribution.
|
|
// Hamfax Copyright (C) Christof Schmitt
|
|
//
|
|
// 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 2 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, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include <config.h>
|
|
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <cstring>
|
|
#include <valarray>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <queue>
|
|
#include <map>
|
|
#include <libgen.h>
|
|
|
|
#include "debug.h"
|
|
#include "gettext.h"
|
|
#include "wefax.h"
|
|
#include "modem.h"
|
|
#include "main.h"
|
|
|
|
#include "misc.h"
|
|
#include "fl_digi.h"
|
|
#include "configuration.h"
|
|
#include "status.h"
|
|
#include "filters.h"
|
|
|
|
#include "wefax-pic.h"
|
|
|
|
#include "ascii.h"
|
|
|
|
#include "qrunner.h"
|
|
|
|
using namespace std;
|
|
|
|
//=============================================================================
|
|
//
|
|
//=============================================================================
|
|
|
|
#define PUT_STATUS( A ) \
|
|
{ \
|
|
std::stringstream strm_status ; \
|
|
strm_status << A ; \
|
|
put_status( strm_status.str().c_str() ); \
|
|
LOG_DEBUG("%s", strm_status.str().c_str() ); \
|
|
}
|
|
|
|
//=============================================================================
|
|
// Core of the code taken from Hamfax.
|
|
//=============================================================================
|
|
|
|
/// No reasonable FIR filter should have more coefficients than that.
|
|
#define MAX_FILT_SIZE 256
|
|
|
|
struct fir_coeffs
|
|
{
|
|
const char * _name ;
|
|
int _size ;
|
|
const double _coefs[MAX_FILT_SIZE];
|
|
};
|
|
|
|
// Narrow, middle and wide fir low pass filter from ACfax
|
|
static const fir_coeffs input_filters[] = {
|
|
{ "Narrow", 17,
|
|
{ -7,-18,-15, 11, 56,116,177,223,240,223,177,116, 56, 11,-15,-18, -7}
|
|
},
|
|
{ "Middle", 17,
|
|
{ 0,-18,-38,-39, 0, 83,191,284,320,284,191, 83, 0,-39,-38,-18, 0}
|
|
},
|
|
{ "Wide", 17,
|
|
{ 6, 20, 7,-42,-74,-12,159,353,440,353,159,-12,-74,-42, 7, 20, 6}
|
|
},
|
|
{ "Blackman", 17,
|
|
{
|
|
-2.7756e-15,
|
|
2.9258e+00,
|
|
1.3289e+01,
|
|
3.4418e+01,
|
|
6.8000e+01,
|
|
1.1095e+02,
|
|
1.5471e+02,
|
|
1.8770e+02,
|
|
2.0000e+02,
|
|
1.8770e+02,
|
|
1.5471e+02,
|
|
1.1095e+02,
|
|
6.8000e+01,
|
|
3.4418e+01,
|
|
1.3289e+01,
|
|
2.9258e+00,
|
|
-2.7756e-15
|
|
}
|
|
},
|
|
{ "Hanning", 17,
|
|
{
|
|
0.00000,
|
|
7.61205,
|
|
29.28932,
|
|
61.73166,
|
|
100.00000,
|
|
138.26834,
|
|
170.71068,
|
|
192.38795,
|
|
200.00000,
|
|
192.38795,
|
|
170.71068,
|
|
138.26834,
|
|
100.00000,
|
|
61.73166,
|
|
29.28932,
|
|
7.61205,
|
|
0.00000
|
|
}
|
|
},
|
|
{ "Hamming", 17,
|
|
{
|
|
16.000,
|
|
23.003,
|
|
42.946,
|
|
72.793,
|
|
108.000,
|
|
143.207,
|
|
173.054,
|
|
192.997,
|
|
200.000,
|
|
192.997,
|
|
173.054,
|
|
143.207,
|
|
108.000,
|
|
72.793,
|
|
42.946,
|
|
23.003,
|
|
16.000
|
|
}
|
|
}
|
|
};
|
|
static const size_t nb_filters = sizeof(input_filters)/sizeof(input_filters[0]); ;
|
|
|
|
/// This contains all possible reception filters.
|
|
class fir_filter_pair_set : public std::vector< C_FIR_filter >
|
|
{
|
|
/// This, because C_FIR_filter cannot be copied.
|
|
fir_filter_pair_set(const fir_filter_pair_set &);
|
|
fir_filter_pair_set & operator=(const fir_filter_pair_set &);
|
|
public:
|
|
static const char ** filters_list(void)
|
|
{
|
|
/// There will be a small memory leak when leaving. No problem at all.
|
|
static const char ** stt_filters = NULL ;
|
|
if( stt_filters == NULL ) {
|
|
stt_filters = new const char * [nb_filters + 1];
|
|
for( size_t ix_filt = 0 ; ix_filt < nb_filters ; ++ix_filt ) {
|
|
stt_filters[ix_filt] = input_filters[ix_filt]._name ;
|
|
}
|
|
stt_filters[nb_filters] = NULL ;
|
|
}
|
|
return stt_filters ;
|
|
}
|
|
|
|
fir_filter_pair_set()
|
|
{
|
|
/// Beware that C_FIR_filter cannot be copied with its content.
|
|
resize( nb_filters );
|
|
for( size_t ix_filt = 0 ; ix_filt < nb_filters ; ++ix_filt )
|
|
{
|
|
// Same filter for real and imaginary.
|
|
const fir_coeffs * ptr_filt = input_filters + ix_filt ;
|
|
// init() should take const double pointers.
|
|
operator[]( ix_filt ).init( ptr_filt->_size, 1,
|
|
const_cast< double * >( ptr_filt->_coefs ),
|
|
const_cast< double * >( ptr_filt->_coefs ) );
|
|
}
|
|
}
|
|
}; // fir_filter_pair_set
|
|
|
|
/// Speed-up of trigonometric calculations.
|
|
template <class T> class lookup_table {
|
|
T * m_table;
|
|
size_t m_table_size;
|
|
size_t m_next;
|
|
size_t m_increment;
|
|
|
|
/// No default constructor because it would invalidate the buffer pointer.
|
|
lookup_table();
|
|
lookup_table(const lookup_table &);
|
|
lookup_table & operator=(const lookup_table &);
|
|
public:
|
|
lookup_table(const size_t N)
|
|
: m_table_size(N), m_next(0), m_increment(0)
|
|
{
|
|
assert( N != 0 );
|
|
|
|
/// If no more memory, an exception will be thrown.
|
|
m_table=new T[m_table_size];
|
|
}
|
|
|
|
~lookup_table(void)
|
|
{
|
|
delete[] m_table;
|
|
}
|
|
|
|
T& operator[](size_t i)
|
|
{
|
|
return m_table[i];
|
|
}
|
|
|
|
void set_increment(size_t i)
|
|
{
|
|
m_increment=i;
|
|
}
|
|
|
|
T next_value(void)
|
|
{
|
|
m_next+=m_increment;
|
|
if(m_next>=m_table_size) {
|
|
m_next%=m_table_size;
|
|
}
|
|
return m_table[m_next];
|
|
}
|
|
|
|
size_t size(void) const
|
|
{
|
|
return m_table_size;
|
|
}
|
|
|
|
void reset(void)
|
|
{
|
|
m_next=0;
|
|
}
|
|
}; // lookup_table
|
|
|
|
typedef enum {
|
|
RXAPTSTART,
|
|
RXAPTSTOP,
|
|
RXPHASING,
|
|
RXIMAGE,
|
|
TXAPTSTART,
|
|
TXPHASING,
|
|
ENDPHASING,
|
|
TXIMAGE,
|
|
TXAPTSTOP,
|
|
IDLE } fax_state;
|
|
|
|
static const char * state_to_str(fax_state a_state)
|
|
{
|
|
switch( a_state )
|
|
{
|
|
case RXAPTSTART : return "APT reception start" ;
|
|
case RXAPTSTOP : return "APT reception stop" ;
|
|
case RXPHASING : return "Phasing reception" ;
|
|
case RXIMAGE : return "Receiving" ;
|
|
case TXAPTSTART : return "APT transmission start" ;
|
|
case TXAPTSTOP : return "APT stop" ;
|
|
case TXPHASING : return "Phasing transmission" ;
|
|
case ENDPHASING : return "End phasing" ;
|
|
case TXIMAGE : return "Sending image" ;
|
|
case IDLE : return "Idle" ;
|
|
}
|
|
return "UNKNOWN" ;
|
|
};
|
|
|
|
/// TODO: This should be hidden to this class.
|
|
static const int bytes_per_pixel = 3;
|
|
|
|
/// This should match the default value of wefax_pic::normalize_lpm.
|
|
#define LPM_DEFAULT 120
|
|
|
|
#define IOC_576 576
|
|
#define IOC_288 288
|
|
|
|
/// Index of correlation to image width.
|
|
static int ioc_to_width( int ioc )
|
|
{
|
|
return ioc * M_PI ;
|
|
};
|
|
|
|
/// Used by bandwidth.
|
|
static const int fm_deviation = 400 ;
|
|
|
|
class fax_implementation {
|
|
wefax * m_ptr_wefax ; // Points to the modem of which this is the implementation.
|
|
fax_state m_rx_state ; // RXPHASING, RXIMAGE etc...
|
|
int m_sample_rate; // Set at startup: 8000, 11025 etc...
|
|
int m_current_value; // Latest received pixel value.
|
|
bool m_apt_high;
|
|
int m_apt_trans; // APT low-high transitions
|
|
int m_apt_count; // samples counted for m_apt_trans
|
|
int m_apt_start_freq; // Standard APT frequency. Set at startup.
|
|
int m_apt_stop_freq; // Standard APT frequency.
|
|
bool m_phase_high; // When state=RXPHASING
|
|
int m_curr_phase_len;
|
|
int m_curr_phase_high;
|
|
int m_phase_lines;
|
|
int m_num_phase_lines;
|
|
int m_phasing_calls_nb;// Number of calls to decode_phasing for the current image.
|
|
bool m_phase_inverted; // Default is false.
|
|
double m_lpm_img; // Lines per minute.
|
|
double m_lpm_sum_rx; // Sum of the latest LPM values, when RXPHASING.
|
|
int m_img_width; // Calculated with IOC=576 or 288.
|
|
int m_img_sample; // Current received samples number when in RXIMAGE.
|
|
int m_last_col; // Current col based on samples number, decides to set a pixel when changes.
|
|
int m_pixel_val; // Accumulates received samples, then averaged to set a pixel.
|
|
int m_pix_samples_nb;
|
|
bool m_img_color; // Whether this is a color image or not.
|
|
fax_state m_tx_state; // Modem state when transmitting.
|
|
int m_tx_phasing_lin; // Nb of phasing lines sent when transmitting an image.
|
|
int m_start_duration; // Number of seconds for sending ATP start.
|
|
int m_stop_duration; // Number of seconds for sending APT stop.
|
|
int m_img_tx_cols; // Number of columns when transmitting.
|
|
int m_img_tx_rows; // Number of rows when transmitting.
|
|
int m_carrier; // Normalised fax carrier frequency.
|
|
int m_fax_pix_num; // Index of current pixel in received image.
|
|
int m_xmt_bytes ; // Total number of bytes to send.
|
|
const unsigned char * m_xmt_pic_buf ; // Bytes to send. Size is m_xmt_bytes.
|
|
bool m_freq_mod ; // Frequency modulation or AM.
|
|
int m_max_fax_rows ; // Max admissible number of received lines.
|
|
bool m_manual_mode ; // Tells whether everything is read, or apt+phasing detection.
|
|
|
|
/// The number of samples sent for one line. The LPM is given by the GUI. Typically 5512.
|
|
double m_smpl_per_lin ;// Recalculated each time m_lpm_img is updated.
|
|
|
|
int m_ix_filt ; // Index of the current reception filter.
|
|
|
|
/// This is always the same filter for all objects.
|
|
static fir_filter_pair_set m_rx_filters ;
|
|
|
|
/// These are used for transmission.
|
|
lookup_table<double> m_dbl_sine;
|
|
lookup_table<double> m_dbl_cosine;
|
|
lookup_table<double> m_dbl_arc_sine;
|
|
|
|
/// Stores a result based on the previous received sample.
|
|
double m_i_fir_old;
|
|
double m_q_fir_old;
|
|
|
|
void decode(const int* buf, int nb_samples);
|
|
|
|
/// Used for transmission.
|
|
lookup_table<short> m_short_sine;
|
|
|
|
fax_implementation();
|
|
|
|
/// Needed when starting image reception, after phasing.
|
|
void reset_counters(void)
|
|
{
|
|
m_last_col = 0 ;
|
|
m_fax_pix_num = 0 ;
|
|
m_img_sample = 0;
|
|
m_pix_samples_nb = 0;
|
|
}
|
|
|
|
/// Called at init time or when the carrier changes.
|
|
void reset_increments(void)
|
|
{
|
|
/// This might happen at startup. Not a problem.
|
|
if(m_sample_rate == 0) {
|
|
return;
|
|
}
|
|
m_dbl_sine.set_increment(m_dbl_sine.size()*m_carrier/m_sample_rate);
|
|
m_dbl_cosine.set_increment(m_dbl_cosine.size()*m_carrier/m_sample_rate);
|
|
m_dbl_sine.reset();
|
|
m_dbl_cosine.reset();
|
|
|
|
m_short_sine.reset();
|
|
if(!m_freq_mod ) {
|
|
m_short_sine.set_increment(m_short_sine.size()*m_carrier/m_sample_rate);
|
|
}
|
|
}
|
|
|
|
public:
|
|
fax_implementation(int fax_mode, wefax * ptr_wefax );
|
|
void init_rx(int the_smpl_rate);
|
|
void skip_apt_rx(void);
|
|
void skip_phasing_to_image(bool auto_center);
|
|
void skip_phasing_rx(bool auto_center);
|
|
void end_rx(void);
|
|
void rx_new_samples(const double* audio_ptr, int audio_sz);
|
|
void init_tx(int the_smpl_rate);
|
|
void modulate(const double* buffer, int n);
|
|
void tx_params_set(
|
|
int the_lpm,
|
|
const unsigned char * xmtpic_buffer,
|
|
bool is_color,
|
|
int img_w,
|
|
int img_h,
|
|
int xmt_bytes );
|
|
bool trx_do_next(void);
|
|
void tx_apt_stop(void);
|
|
|
|
double carrier(void) const
|
|
{
|
|
return m_carrier ;
|
|
}
|
|
|
|
/// The trigo tables have an increment which depends on the carrier frequency.
|
|
void set_carrier(double freq)
|
|
{
|
|
m_carrier = freq ;
|
|
reset_increments();
|
|
}
|
|
|
|
int fax_width(void) const
|
|
{
|
|
return m_img_width ;
|
|
}
|
|
|
|
void set_filter_rx( int idx_filter )
|
|
{
|
|
m_ix_filt = idx_filter ;
|
|
}
|
|
|
|
/// When set, starts receiving faxes without interruption other than manual.
|
|
void manual_mode_set( bool manual_flag )
|
|
{
|
|
m_manual_mode = manual_flag ;
|
|
}
|
|
|
|
/// Called by the GUI.
|
|
bool manual_mode_get(void) const
|
|
{
|
|
return m_manual_mode ;
|
|
}
|
|
|
|
/// Called by the GUI.
|
|
void max_lines_set( int max_lines )
|
|
{
|
|
m_max_fax_rows = max_lines ;
|
|
}
|
|
|
|
/// Called by the GUI.
|
|
int max_lines_get(void) const
|
|
{
|
|
return m_max_fax_rows ;
|
|
}
|
|
|
|
int lpm_to_samples( int the_lpm ) const {
|
|
return m_sample_rate * 60.0 / the_lpm ;
|
|
}
|
|
|
|
/// Called by the GUI.
|
|
void lpm_set( int the_lpm )
|
|
{
|
|
m_lpm_img = the_lpm ;
|
|
m_smpl_per_lin = lpm_to_samples( m_lpm_img );
|
|
}
|
|
|
|
/// This generates a filename based on the frequency, current time, internal state etc...
|
|
std::string generate_filename( const char *extra_msg ) const ;
|
|
|
|
std::string state_rx_str(void) const
|
|
{
|
|
return state_to_str(m_rx_state);
|
|
}
|
|
|
|
/// Returns a string telling the state. Informational purpose.
|
|
std::string state_string(void) const
|
|
{
|
|
std::stringstream result ;
|
|
result << "tx:" << state_to_str(m_tx_state)
|
|
<< " "
|
|
<< "rx:" << state_to_str(m_rx_state);
|
|
return result.str();
|
|
};
|
|
|
|
private:
|
|
/// Centered around the frequency.
|
|
double power_usb_noise(void) const
|
|
{
|
|
static double avg_pwr = 0.0 ;
|
|
double pwr = wf->powerDensity(m_carrier, 2 * fm_deviation) + 1e-10;
|
|
|
|
return decayavg( avg_pwr, pwr, 10 );
|
|
}
|
|
|
|
/// This evaluates the power signal when APT start frequency. This frequency pattern
|
|
/// can be observed on the waterfall.
|
|
double power_usb_apt_start(void) const
|
|
{
|
|
static double avg_pwr = 0.0 ;
|
|
/// Value approximated by watching the waterfall.
|
|
static const int bandwidth_apt_start = 10 ;
|
|
double pwr
|
|
= wf->powerDensity(m_carrier - 2 * m_apt_start_freq, bandwidth_apt_start)
|
|
+ wf->powerDensity(m_carrier - m_apt_start_freq, bandwidth_apt_start)
|
|
+ wf->powerDensity(m_carrier , bandwidth_apt_start)
|
|
+ wf->powerDensity(m_carrier + m_apt_start_freq, bandwidth_apt_start);
|
|
+ wf->powerDensity(m_carrier + 2 * m_apt_start_freq, bandwidth_apt_start);
|
|
|
|
return decayavg( avg_pwr, pwr, 10 );
|
|
}
|
|
|
|
/// Estimates the signal power when the phasing signal is received.
|
|
double power_usb_phasing(void) const
|
|
{
|
|
static double avg_pwr = 0.0 ;
|
|
/// Rough estimate based on waterfall observation.
|
|
static const int bandwidth_phasing = 1 ;
|
|
double pwr = wf->powerDensity(m_carrier - fm_deviation, bandwidth_phasing);
|
|
|
|
return decayavg( avg_pwr, pwr, 10 );
|
|
}
|
|
|
|
/// There is some power at m_carrier + fm_deviation but this is neglictible.
|
|
double power_usb_image(void) const
|
|
{
|
|
static double avg_pwr = 0.0 ;
|
|
/// This value is obtained by watching the waterfall.
|
|
static const int bandwidth_image = 100 ;
|
|
double pwr = wf->powerDensity(m_carrier + fm_deviation, bandwidth_image);
|
|
|
|
return decayavg( avg_pwr, pwr, 10 );
|
|
}
|
|
|
|
/// Hambourg Radio sends a constant freq which looks like an image.
|
|
/// We eliminate it by comparing with a black signal.
|
|
/// If this is a real image, bith powers will be close.
|
|
/// If not, the image is very important, but not the black.
|
|
double power_usb_black(void) const
|
|
{
|
|
static double avg_pwr = 0.0 ;
|
|
/// This value is obtained by watching the waterfall.
|
|
static const int bandwidth_black = 20 ;
|
|
double pwr = wf->powerDensity(m_carrier + fm_deviation, bandwidth_black);
|
|
|
|
return decayavg( avg_pwr, pwr, 10 );
|
|
}
|
|
|
|
/// Evaluates the signal power for APT stop frequency.
|
|
double power_usb_apt_stop(void) const
|
|
{
|
|
static double avg_pwr = 0.0 ;
|
|
/// This value is obtained by watching the waterfall.
|
|
static const int bandwidth_apt_stop = 50 ;
|
|
double pwr = wf->powerDensity(m_carrier - m_apt_stop_freq, bandwidth_apt_stop);
|
|
|
|
return decayavg( avg_pwr, pwr, 10 );
|
|
}
|
|
|
|
/// This evaluates the signal/noise ratio for some specific bandwidths.
|
|
class fax_signal {
|
|
const fax_implementation * _ptr_fax ;
|
|
int _cnt ; /// The value can be reused a couple of times.
|
|
|
|
double _apt_start ;
|
|
double _phasing ;
|
|
double _image ;
|
|
double _black ;
|
|
double _apt_stop ;
|
|
|
|
fax_state _state ; /// Deduction made based on signal power.
|
|
const char * _text;
|
|
|
|
/// Finer tests can be added. These are all rule-of-thumb values based
|
|
/// on observation of real signals.
|
|
/// TODO: Adds a learning mode using the Hamfax detector to train the signal-based one.
|
|
void set_state(void)
|
|
{
|
|
_state = IDLE ;
|
|
_text = "";
|
|
|
|
if( ( _apt_start > 20.0 ) &&
|
|
( _phasing < 10.0 ) &&
|
|
( _image < 10.0 ) &&
|
|
( _apt_stop < 10.0 ) ) {
|
|
_state = RXAPTSTART ;
|
|
_text = _("Strong APT start signal: Skip to phasing.");
|
|
}
|
|
if( /// The image signal may be weak if the others signals are even weaker.
|
|
( ( _apt_start < 1.0 ) &&
|
|
( _phasing < 1.0 ) &&
|
|
( _image > 10.0 ) &&
|
|
( _apt_stop < 1.0 ) ) ||
|
|
( ( _apt_start < 1.0 ) &&
|
|
( _phasing < 0.5 ) &&
|
|
( _image > 8.0 ) &&
|
|
( _apt_stop < 0.5 ) ) ||
|
|
( ( _apt_start < 3.0 ) &&
|
|
( _phasing < 1.0 ) &&
|
|
( _image > 15.0 ) &&
|
|
( _apt_stop < 1.0 ) ) ) {
|
|
_state = RXIMAGE ;
|
|
_text = _("Strong image signal when getting APT start: Starting phasing.");
|
|
}
|
|
if( ( _apt_start < 10.0 ) &&
|
|
( _phasing > 20.0 ) &&
|
|
( _image < 10.0 ) &&
|
|
( _apt_stop < 10.0 ) ) {
|
|
_state = RXPHASING ;
|
|
_text = _("Strong phasing signal when getting APT start: Starting phasing.");
|
|
}
|
|
if(
|
|
( ( _apt_start < 2.0 ) &&
|
|
( _phasing < 2.0 ) &&
|
|
( _image < 2.0 ) &&
|
|
( _apt_stop > 8.0 ) ) ||
|
|
( ( _apt_start < 1.0 ) &&
|
|
( _phasing < 1.0 ) &&
|
|
( _image < 1.0 ) &&
|
|
( _apt_stop > 6.0 ) ) ) {
|
|
_state = RXAPTSTOP ;
|
|
_text = _("Strong APT stop signal: Stopping reception.");
|
|
}
|
|
|
|
/// Maybe this is a constant freq. If so, the power is very big
|
|
/// for the image, but the center, which should be important,
|
|
/// is very weak: This is a constant freq (Hambourg).
|
|
if( _image > 5 * _black ) {
|
|
_state = IDLE ;
|
|
}
|
|
if( _image > 100 * _black ) {
|
|
_state = RXAPTSTOP ;
|
|
_text = _("Constant frequency detected: Stopping reception.");
|
|
}
|
|
|
|
/// Consecutive lines in a wefax image have a strong statistical correlation.
|
|
fax_state state_corr = _ptr_fax->correlation_state();
|
|
if( ( _state == RXIMAGE ) || ( _state == IDLE ) ) {
|
|
if( state_corr == RXAPTSTOP ) {
|
|
_state = RXAPTSTOP ;
|
|
_text = _("No significant line-to-line correlation: Stopping reception.");
|
|
}
|
|
}
|
|
|
|
if( ( _state == IDLE ) || ( _state == RXAPTSTART ) || ( _state == RXPHASING ) ) {
|
|
if( state_corr == RXIMAGE ) {
|
|
_state = RXIMAGE ;
|
|
_text = _("Significant line-to-line correlation: Starting reception.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void recalc(void)
|
|
{
|
|
/// Adds a small value to avoid division by zero.
|
|
double noise = _ptr_fax->power_usb_noise() + 1e-10 ;
|
|
|
|
/// Multiplications are faster than divisions.
|
|
double inv_noise = 1.0 / noise ;
|
|
|
|
_apt_start = _ptr_fax->power_usb_apt_start() * inv_noise ;
|
|
_phasing = _ptr_fax->power_usb_phasing() * inv_noise ;
|
|
_image = _ptr_fax->power_usb_image() * inv_noise ;
|
|
_black = _ptr_fax->power_usb_black() * inv_noise ;
|
|
_apt_stop = _ptr_fax->power_usb_apt_stop() * inv_noise ;
|
|
|
|
set_state();
|
|
}
|
|
|
|
public:
|
|
/// This recomputes, if needed, the various signal power ratios.
|
|
void refresh(void)
|
|
{
|
|
/// Values reevaluated every X input samples. Must be smaller
|
|
/// than the typical audio input frames (512 samples).
|
|
static const int validity = 1000 ;
|
|
|
|
/// Calculated at least the first time.
|
|
if( ( _cnt % validity ) == 0 ) {
|
|
recalc();
|
|
}
|
|
/// Does not matter if wrapped beyond 2**31.
|
|
++_cnt ;
|
|
}
|
|
|
|
fax_signal( const fax_implementation * ptr_fax)
|
|
: _ptr_fax(ptr_fax), _cnt(0) {}
|
|
|
|
fax_state signal_state(void) const { return _state ; }
|
|
const char * signal_text(void) const { return _text; };
|
|
|
|
double image_noise_ratio(void) const { return _image ; }
|
|
|
|
/// This updates a Fl_Chart widget.
|
|
void display(void) const
|
|
{
|
|
// Protected with REQ, otherwise will segfault. Do not use REQ_SYNC
|
|
// otherwise it hangs when switching to another mode with a macro.
|
|
REQ( wefax_pic::power,
|
|
_apt_start,
|
|
_phasing,
|
|
_image,
|
|
_black,
|
|
_apt_stop );
|
|
}
|
|
|
|
/// For debugging only.
|
|
friend std::ostream & operator<<( std::ostream & refO, const fax_signal & ref_sig )
|
|
{
|
|
refO
|
|
<< " start=" << std::setw(10) << ref_sig._apt_start
|
|
<< " phasing=" << std::setw(10) << ref_sig._phasing
|
|
<< " image=" << std::setw(10) << ref_sig._image
|
|
<< " black=" << std::setw(10) << ref_sig._black
|
|
<< " stop=" << std::setw(10) << ref_sig._apt_stop
|
|
<< " pwr_state=" << state_to_str(ref_sig._state);
|
|
return refO ;
|
|
}
|
|
|
|
std::string to_string(void) const
|
|
{
|
|
std::stringstream tmp_strm ;
|
|
tmp_strm << *this ;
|
|
return tmp_strm.str();
|
|
}
|
|
};
|
|
|
|
/// We tolerate a small difference between the frequencies.
|
|
bool is_near_freq( int f1, int f2, int margin ) const
|
|
{
|
|
int delta = f1 - f2 ;
|
|
if( delta < 0 )
|
|
{
|
|
delta = -delta ;
|
|
}
|
|
if( delta < margin ) {
|
|
return true ;
|
|
} else {
|
|
return false ;
|
|
}
|
|
}
|
|
|
|
void save_automatic(const char * extra_msg);
|
|
|
|
void decode_apt(int x, const fax_signal & the_signal );
|
|
void decode_phasing(int x, const fax_signal & the_signal );
|
|
bool decode_image(int x);
|
|
|
|
private:
|
|
/// Each received file has its name pushed in this queue.
|
|
syncobj m_sync_rx ;
|
|
std::queue< std::string > m_received_files ;
|
|
public:
|
|
/// Called by the engine each time a file is saved.
|
|
void put_received_file( const std::string &filnam )
|
|
{
|
|
guard_lock g( m_sync_rx.mtxp() );
|
|
LOG_INFO("%s", filnam.c_str() );
|
|
m_received_files.push( filnam );
|
|
m_sync_rx.signal();
|
|
}
|
|
|
|
/// Returns a received file name, by chronological order.
|
|
std::string get_received_file( double max_seconds )
|
|
{
|
|
guard_lock g( m_sync_rx.mtxp() );
|
|
|
|
LOG_INFO(_("delay=%f"), max_seconds );
|
|
if( m_received_files.empty() )
|
|
{
|
|
if( ! m_sync_rx.wait(max_seconds) ) return std::string();
|
|
}
|
|
std::string filnam = m_received_files.front();
|
|
m_received_files.pop();
|
|
return filnam ;
|
|
}
|
|
private:
|
|
/// No unicity if the same filename is sent by several clients at the same time.
|
|
/// This does not really matter because the error codes should be the same.
|
|
syncobj m_sync_tx_fil ;
|
|
std::string m_tx_fil ; // Filename being transmitted.
|
|
|
|
syncobj m_sync_tx_msg ;
|
|
typedef std::map< std::string, std::string > sent_files_type ;
|
|
sent_files_type m_sent_files ;
|
|
public:
|
|
/// If the delay is exceeded, returns with an error message.
|
|
std::string send_file( const std::string & filnam, double max_seconds )
|
|
{
|
|
LOG_INFO("%s", filnam.c_str() );
|
|
bool is_acquired = transmit_lock_acquire(filnam, max_seconds);
|
|
if( ! is_acquired ) return "Timeout sending " + filnam ;
|
|
|
|
REQ( wefax_pic::send_image, filnam );
|
|
|
|
guard_lock g( m_sync_tx_fil.mtxp() );
|
|
|
|
sent_files_type::iterator itFi ;
|
|
while(true)
|
|
{
|
|
itFi = m_sent_files.find( filnam );
|
|
if( itFi != m_sent_files.end() )
|
|
{
|
|
break ;
|
|
}
|
|
if( ! m_sync_tx_msg.wait( max_seconds ) )
|
|
{
|
|
return "Timeout";
|
|
}
|
|
}
|
|
std::string err_msg = itFi->second ;
|
|
m_sent_files.erase( itFi );
|
|
return err_msg ;
|
|
}
|
|
|
|
/// Called when loading a file from the GUI, or indirectly from XML-RPC.
|
|
bool transmit_lock_acquire(const std::string & filnam, double delay = wefax::max_delay )
|
|
{
|
|
LOG_INFO("%s", filnam.c_str() );
|
|
guard_lock g( m_sync_tx_fil.mtxp() );
|
|
if ( ! m_tx_fil.empty() )
|
|
{
|
|
if( ! m_sync_tx_fil.wait( delay ) ) return false ;
|
|
}
|
|
m_tx_fil = filnam ;
|
|
return true ;
|
|
}
|
|
|
|
/// Allows to send another file. Called by XML-RPC and the GUI.
|
|
void transmit_lock_release( const std::string & err_msg )
|
|
{
|
|
guard_lock g( m_sync_tx_msg.mtxp() );
|
|
LOG_INFO("%s %s", m_tx_fil.c_str(), err_msg.c_str() );
|
|
if( m_tx_fil.empty() )
|
|
{
|
|
LOG_WARN(_("%s: File name should not be empty"), err_msg.c_str() );
|
|
}
|
|
else
|
|
{
|
|
m_sent_files[ m_tx_fil ] = err_msg ;
|
|
m_tx_fil.clear();
|
|
}
|
|
m_sync_tx_msg.signal();
|
|
}
|
|
|
|
/// Maybe we could reset this buffer when we change the state so that we could
|
|
/// use the current LPM width.
|
|
mutable std::vector<unsigned char> m_correlation_buffer ;
|
|
mutable double m_current_corr ;
|
|
mutable double m_curr_corr_avg ;
|
|
mutable size_t m_corr_calls_nb;
|
|
static const size_t m_min_corr_lines = 20 ;
|
|
|
|
/// Absolute value of statistical correlation between the current line and the previous one.
|
|
/// Called once per maximum sample line.
|
|
void correlation_calc(void) const {
|
|
++m_corr_calls_nb;
|
|
/// We should in fact take the smallest one, 60.
|
|
size_t corr_samples_line = lpm_to_samples( LPM_DEFAULT );
|
|
size_t corr_samples_line_plus_img_sample = corr_samples_line + m_img_sample ;
|
|
size_t corr_buff_sz = 2 * corr_samples_line ;
|
|
|
|
int avg_pred = 0, avg_curr = 0 ;
|
|
for( size_t i = m_img_sample ; i < corr_samples_line_plus_img_sample ; ++i ) {
|
|
int pix_pred = m_correlation_buffer[ ( i ) % corr_buff_sz ];
|
|
int pix_curr = m_correlation_buffer[ ( i + corr_samples_line ) % corr_buff_sz ];
|
|
avg_pred += pix_pred;
|
|
avg_curr += pix_curr ;
|
|
}
|
|
avg_pred /= corr_samples_line;
|
|
avg_curr /= corr_samples_line;
|
|
|
|
/// Use integers because it is faster. Samples are chars, so no overflow possible.
|
|
int numerator = 0, denom_pred = 0, denom_curr = 0 ;
|
|
for( size_t i = m_img_sample ; i < corr_samples_line_plus_img_sample ; ++i ) {
|
|
int pix_pred = m_correlation_buffer[ ( i ) % corr_buff_sz ];
|
|
int pix_curr = m_correlation_buffer[ ( i + corr_samples_line ) % corr_buff_sz ];
|
|
int delta_pred = pix_pred - avg_pred ;
|
|
int delta_curr = pix_curr - avg_curr ;
|
|
numerator += delta_pred * delta_curr ;
|
|
denom_pred += delta_pred * delta_pred ;
|
|
denom_curr += delta_curr * delta_curr ;
|
|
}
|
|
double denominator = sqrt( (double)denom_pred * (double)denom_curr );
|
|
if( denominator == 0.0 ) {
|
|
m_current_corr = 0.0 ;
|
|
} else {
|
|
m_current_corr = fabs( numerator / (double)denominator );
|
|
}
|
|
|
|
/// This never happened, but who knows (Inaccuracy etc...)?
|
|
if( m_current_corr > 1.0 ) {
|
|
LOG_WARN("Inconsistent correlation:%lf", m_current_corr );
|
|
m_current_corr = 1.0;
|
|
}
|
|
|
|
if( m_corr_calls_nb < m_min_corr_lines ) {
|
|
m_curr_corr_avg = m_current_corr ;
|
|
} else {
|
|
m_curr_corr_avg = ( m_curr_corr_avg * m_min_corr_lines + m_current_corr ) / ( m_min_corr_lines + 1 );
|
|
}
|
|
/// Debugging purpose only.
|
|
if( ( m_corr_calls_nb % 10000 ) == 0 ) {
|
|
LOG_INFO( "m_current_corr=%lf m_curr_corr_avg=%lf m_corr_calls_nb=%d",
|
|
m_current_corr, m_curr_corr_avg, m_corr_calls_nb );
|
|
}
|
|
double metric = m_curr_corr_avg * 100.0 ;
|
|
m_ptr_wefax->display_metric(metric);
|
|
}
|
|
|
|
/// This is called quite often. It estimates, based on the mobile
|
|
/// average of statistical correlation between consecutive lines, whether this is a wefax
|
|
/// signal or not.
|
|
fax_state correlation_state(void) const {
|
|
|
|
static fax_state stable_state = IDLE ;
|
|
|
|
/// If the mobile abverage is not computed on enough lines, returns IDLE
|
|
/// which means "Do not know" in this context.
|
|
if( m_corr_calls_nb >= m_min_corr_lines ) {
|
|
/// The threshold are very approximate.
|
|
if( m_curr_corr_avg < 0.05 ) {
|
|
stable_state = RXAPTSTOP ;
|
|
} else if( m_curr_corr_avg > 0.20 ) {
|
|
stable_state = RXIMAGE ;
|
|
} else {
|
|
stable_state = IDLE ;
|
|
}
|
|
}
|
|
|
|
/// Message for first detection.
|
|
if( (stable_state == IDLE) && (m_corr_calls_nb == m_min_corr_lines) ) {
|
|
LOG_INFO("Correlation average %lf: Detected %s", m_curr_corr_avg, state_to_str(stable_state) );
|
|
}
|
|
|
|
return stable_state;
|
|
}
|
|
|
|
/// We compute about the same when plotting a pixel.
|
|
void correlation_update( int the_sample ) {
|
|
size_t corr_samples_line = lpm_to_samples( LPM_DEFAULT );
|
|
size_t corr_buff_sz = 2 * corr_samples_line ;
|
|
|
|
static size_t cnt_upd = 0 ;
|
|
|
|
if( cnt_upd == 0 ) {
|
|
if( m_correlation_buffer.size() != corr_buff_sz ) {
|
|
m_correlation_buffer.resize( corr_buff_sz, 0 );
|
|
m_current_corr = 0.0 ;
|
|
m_curr_corr_avg = 0.0 ;
|
|
return ;
|
|
}
|
|
}
|
|
|
|
if( ( cnt_upd % corr_samples_line ) == 0 ) {
|
|
correlation_calc();
|
|
}
|
|
++cnt_upd ;
|
|
|
|
m_correlation_buffer[ m_img_sample % corr_buff_sz ] = the_sample ;
|
|
}
|
|
}; // class fax_implementation
|
|
|
|
/// Narrow, middle etc... input filters. Constructed at program startup. Readonly.
|
|
fir_filter_pair_set fax_implementation::m_rx_filters ;
|
|
|
|
// http://www.newearth.demon.co.uk/radio/hfwefax1.htm
|
|
// "...the total offset is 1900Hz, so to tune a fax station on 4610kHz, tune to 4608.1kHz USB."
|
|
static const double wefax_default_carrier = 1900;
|
|
|
|
fax_implementation::fax_implementation( int fax_mode, wefax * ptr_wefax )
|
|
: m_ptr_wefax( ptr_wefax )
|
|
, m_dbl_sine(8192),m_dbl_cosine(8192),m_dbl_arc_sine(256)
|
|
, m_short_sine(8192)
|
|
{
|
|
m_freq_mod = true;
|
|
m_apt_stop_freq = 450 ;
|
|
m_phase_inverted = false ;
|
|
m_img_color = false ;
|
|
m_tx_phasing_lin = 20 ;
|
|
m_carrier = wefax_default_carrier ;
|
|
m_start_duration = 5 ;
|
|
m_stop_duration = 5 ;
|
|
m_manual_mode = false ;
|
|
m_rx_state = IDLE ;
|
|
m_tx_state = IDLE ;
|
|
m_sample_rate = 0 ;
|
|
|
|
int index_of_correlation ;
|
|
/// http://en.wikipedia.org/wiki/Radiofax
|
|
switch( fax_mode )
|
|
{
|
|
case MODE_WEFAX_576:
|
|
m_apt_start_freq = 300 ;
|
|
index_of_correlation = IOC_576 ;
|
|
break;
|
|
case MODE_WEFAX_288:
|
|
m_apt_start_freq = 675 ;
|
|
index_of_correlation = IOC_288 ;
|
|
break;
|
|
default:
|
|
LOG_ERROR(_("Invalid fax mode:%d"), fax_mode);
|
|
abort();
|
|
}
|
|
|
|
m_img_width = ioc_to_width( index_of_correlation );
|
|
|
|
for(size_t i=0; i<m_dbl_sine.size(); i++) {
|
|
m_dbl_sine[i]=32768.0 * std::sin(2.0*M_PI*i/m_dbl_sine.size());
|
|
}
|
|
for(size_t i=0; i<m_dbl_cosine.size(); i++) {
|
|
m_dbl_cosine[i]=32768.0 * std::cos(2.0*M_PI*i/m_dbl_cosine.size());
|
|
}
|
|
for(size_t i=0; i<m_dbl_arc_sine.size(); i++) {
|
|
m_dbl_arc_sine[i]=std::asin(2.0*i/m_dbl_arc_sine.size()-1.0)/2.0/M_PI;
|
|
}
|
|
for(size_t i=0; i<m_short_sine.size(); i++) {
|
|
m_short_sine[i]=static_cast<short>(32767*std::sin(2.0*M_PI*i/m_short_sine.size()));
|
|
}
|
|
};
|
|
|
|
void fax_implementation::init_rx(int the_smpl_rat)
|
|
{
|
|
m_sample_rate=the_smpl_rat;
|
|
m_rx_state=RXAPTSTART;
|
|
m_apt_count=m_apt_trans=0;
|
|
m_apt_high=false;
|
|
/// Centers the carriers on the GUI and reinits the trigonometric tables.
|
|
m_ptr_wefax->set_freq(wefax_default_carrier);
|
|
|
|
/// No weather fax can have such a huge number of rows.
|
|
m_max_fax_rows = 5000 ;
|
|
|
|
/// Default value, can be the with the GUI.
|
|
m_ix_filt = 0 ; // 0=narrow, 1=middle, 2=wide.
|
|
|
|
reset_increments();
|
|
m_i_fir_old=m_q_fir_old=0;
|
|
m_corr_calls_nb = 0;
|
|
}
|
|
|
|
/// Values are between 0 and 255.
|
|
void fax_implementation::decode(const int* buf, int nb_samples)
|
|
{
|
|
if(nb_samples==0)
|
|
{
|
|
LOG_WARN(_("Empty buffer."));
|
|
end_rx();
|
|
}
|
|
fax_signal my_signal(this);
|
|
for(int i=0; i<nb_samples; i++) {
|
|
int crr_val = buf[i];
|
|
my_signal.refresh();
|
|
m_current_value = crr_val;
|
|
|
|
{
|
|
static int n = 0 ;
|
|
|
|
/// TODO: It could be displayed less often.
|
|
if( 0 == ( n++ % 10000 ) ) {
|
|
/// Must create a temp string otherwise the char pointer may be freed.
|
|
std::string tmp_str = my_signal.to_string();
|
|
LOG_VERBOSE( "%s state=%s", tmp_str.c_str(), state_to_str(m_rx_state) );
|
|
my_signal.display();
|
|
}
|
|
}
|
|
|
|
correlation_update(crr_val);
|
|
|
|
if( m_manual_mode ) {
|
|
m_rx_state = RXIMAGE;
|
|
bool is_max_lines_reached = decode_image(crr_val);
|
|
if( is_max_lines_reached ) {
|
|
skip_apt_rx();
|
|
skip_phasing_rx(false);
|
|
LOG_INFO(_("Max lines reached in manual mode: Resuming reception."));
|
|
if( m_manual_mode == false ) {
|
|
LOG_ERROR(_("Inconsistent manual mode."));
|
|
}
|
|
}
|
|
} else {
|
|
decode_apt(crr_val,my_signal);
|
|
if( m_rx_state==RXPHASING ) {
|
|
decode_phasing(crr_val,my_signal);
|
|
}
|
|
if( ( m_rx_state==RXPHASING || m_rx_state==RXIMAGE ) && m_lpm_img > 0 ) {
|
|
/// If the maximum number of lines is reached, we stop the reception.
|
|
decode_image(crr_val);
|
|
}
|
|
}
|
|
m_img_sample++;
|
|
}
|
|
}
|
|
|
|
// The number of transitions between black and white is counted. After 1/2
|
|
// second, the frequency is calculated. If it matches the APT start frequency,
|
|
// the state skips to the detection of phasing lines, if it matches the apt
|
|
// stop frequency two times, the reception is ended.
|
|
void fax_implementation::decode_apt(int x, const fax_signal & the_signal )
|
|
{
|
|
if(x>229 && !m_apt_high) {
|
|
m_apt_high=true;
|
|
++m_apt_trans;
|
|
} else if(x<25 && m_apt_high) {
|
|
m_apt_high=false;
|
|
}
|
|
++m_apt_count ;
|
|
if( m_apt_count >= m_sample_rate/2 ) {
|
|
int curr_freq=m_sample_rate*m_apt_trans/m_apt_count;
|
|
|
|
/// This writes the S/R level on the status bar.
|
|
double tmp_snr = the_signal.image_noise_ratio();
|
|
char snr_buffer[64];
|
|
snprintf(snr_buffer, sizeof(snr_buffer), "s/n %3.0f dB", 20.0 * log10(tmp_snr));
|
|
put_Status1(snr_buffer);
|
|
|
|
m_apt_count=m_apt_trans=0;
|
|
|
|
if(m_rx_state==RXAPTSTART) {
|
|
if( is_near_freq(curr_freq,m_apt_start_freq, 8 ) ) {
|
|
skip_apt_rx();
|
|
PUT_STATUS( state_rx_str() << ", " << _("frequency") << ": "
|
|
<< curr_freq << " Hz. " << _("Skipping.") );
|
|
return ;
|
|
}
|
|
if( is_near_freq(curr_freq,m_apt_stop_freq, 2 ) ) {
|
|
LOG_INFO(_("Spurious APT stop frequency=%d Hz as waiting for APT start. SNR=%f"),
|
|
curr_freq, tmp_snr );
|
|
return ;
|
|
}
|
|
|
|
PUT_STATUS( state_rx_str() << ", " << _("frequency") << ": " << curr_freq << " Hz." );
|
|
}
|
|
|
|
if( is_near_freq(curr_freq,m_apt_stop_freq, 12 ) ) {
|
|
PUT_STATUS( state_rx_str() << " " << _("Apt stop frequency") << ": "
|
|
<< curr_freq << " Hz. " << _("Stopping.") );
|
|
save_automatic("ok");
|
|
end_rx();
|
|
return ;
|
|
}
|
|
|
|
switch( the_signal.signal_state() ) {
|
|
case RXAPTSTART :
|
|
switch( m_rx_state ) {
|
|
case RXAPTSTART :
|
|
skip_apt_rx();
|
|
LOG_INFO( the_signal.signal_text() );
|
|
break ;
|
|
default : break ;
|
|
}
|
|
break ;
|
|
case RXPHASING :
|
|
switch( m_rx_state ) {
|
|
case RXAPTSTART :
|
|
skip_apt_rx();
|
|
LOG_INFO( the_signal.signal_text() );
|
|
break ;
|
|
default : break ;
|
|
}
|
|
break ;
|
|
case RXIMAGE :
|
|
switch( m_rx_state ) {
|
|
case RXAPTSTART :
|
|
skip_apt_rx();
|
|
/// The phasing step will start receiving the image later. First we try
|
|
/// to phase the image correctly.
|
|
LOG_INFO( the_signal.signal_text() );
|
|
break ;
|
|
default : break ;
|
|
}
|
|
break ;
|
|
case RXAPTSTOP :
|
|
switch( m_rx_state ) {
|
|
case RXIMAGE :
|
|
LOG_INFO( the_signal.signal_text() );
|
|
save_automatic("stop");
|
|
end_rx();
|
|
break;
|
|
default : break ;
|
|
}
|
|
break ;
|
|
default : break ;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This generates a file name with the reception time and the frequency.
|
|
std::string fax_implementation::generate_filename( const char *extra_msg ) const
|
|
{
|
|
time_t tmp_time = time(NULL);
|
|
struct tm tmp_tm ;
|
|
localtime_r( &tmp_time, &tmp_tm );
|
|
|
|
char buf_fil_nam[256] ;
|
|
long long tmp_fl = wf->rfcarrier() ;
|
|
snprintf( buf_fil_nam, sizeof(buf_fil_nam),
|
|
"wefax_%04d%02d%02d_%02d%02d%02d_%lld_%s.png",
|
|
1900 + tmp_tm.tm_year,
|
|
1 + tmp_tm.tm_mon,
|
|
tmp_tm.tm_mday,
|
|
tmp_tm.tm_hour,
|
|
tmp_tm.tm_min,
|
|
tmp_tm.tm_sec,
|
|
tmp_fl,
|
|
extra_msg );
|
|
|
|
return buf_fil_nam ;
|
|
}
|
|
|
|
/// This saves an image and adds the right comments.
|
|
void fax_implementation::save_automatic(const char * extra_msg)
|
|
{
|
|
/// Minimum pixel numbers for a valid image.
|
|
static const int max_fax_pix_num = 200000 ;
|
|
if( m_fax_pix_num < max_fax_pix_num )
|
|
{
|
|
LOG_INFO( _("Do not save small image (%d bytes). Manual=%d"), m_fax_pix_num, m_manual_mode );
|
|
return ;
|
|
}
|
|
|
|
std::string new_filnam = generate_filename(extra_msg);
|
|
|
|
LOG_INFO( "Saving %d bytes in %s.", m_fax_pix_num, new_filnam.c_str() );
|
|
|
|
std::stringstream extra_comments ;
|
|
extra_comments << "ControlMode:" << ( m_manual_mode ? "Manual" : "APT control" ) << "\n" ;
|
|
extra_comments << "LPM:" << m_lpm_img << "\n" ;
|
|
extra_comments << "FrequencyMode:" << ( m_freq_mod ? "FM" : "AM" ) << "\n" ;
|
|
extra_comments << "Carrier:" << m_carrier << "\n" ;
|
|
extra_comments << "Inversion:" << ( m_phase_inverted ? "Inverted" : "Normal" ) << "\n" ;
|
|
extra_comments << "Color:" << ( m_img_color ? "Color" : "BW" ) << "\n" ;
|
|
extra_comments << "SampleRate:" << m_sample_rate << "\n" ;
|
|
wefax_pic::save_image( new_filnam, extra_comments.str() );
|
|
};
|
|
|
|
// Phasing lines consist of 2.5% white at the beginning, 95% black and again
|
|
// 2.5% white at the end (or inverted). In normal phasing lines we try to
|
|
// count the length between the white-black transitions. If the line has
|
|
// a reasonable amount of black (4.8%--5.2%) and the length fits in the
|
|
// range of 60--360lpm (plus some tolerance) it is considered a valid
|
|
// phasing line. Then the start of a line and the lpm is calculated.
|
|
void fax_implementation::decode_phasing(int x, const fax_signal & the_signal )
|
|
{
|
|
m_curr_phase_len++;
|
|
++m_phasing_calls_nb ;
|
|
if(x>128) {
|
|
m_curr_phase_high++;
|
|
}
|
|
if((!m_phase_inverted && x>229 && !m_phase_high) ||
|
|
( m_phase_inverted && x<25 && m_phase_high)) {
|
|
m_phase_high=m_phase_inverted?false:true;
|
|
} else if((!m_phase_inverted && x<25 && m_phase_high) ||
|
|
( m_phase_inverted && x>229 && !m_phase_high)) {
|
|
m_phase_high=m_phase_inverted?true:false;
|
|
if(m_curr_phase_high>=(m_phase_inverted?0.948:0.048)*m_curr_phase_len &&
|
|
m_curr_phase_high<=(m_phase_inverted?0.952:0.052)*m_curr_phase_len &&
|
|
m_curr_phase_len <= 1.1 * m_sample_rate &&
|
|
m_curr_phase_len >= 0.15 * m_sample_rate) {
|
|
double tmp_lpm=60.0*m_sample_rate/m_curr_phase_len;
|
|
|
|
m_lpm_sum_rx += tmp_lpm;
|
|
++m_phase_lines;
|
|
|
|
PUT_STATUS( state_rx_str()
|
|
<< ". " << _("Decoding phasing line") << ", lpm = " << tmp_lpm
|
|
<< " " << _("count") << "=" << m_phase_lines );
|
|
|
|
/// The precision cannot really increase because there cannot
|
|
// be more than a couple of loops. This is used for guessing
|
|
// whether the LPM is around 120 or 60.
|
|
lpm_set( m_lpm_sum_rx / m_phase_lines );
|
|
|
|
double smpl_per_lin = m_smpl_per_lin;
|
|
/// Half of the band of the phasing line.
|
|
m_img_sample=static_cast<int>(1.025 * smpl_per_lin );
|
|
|
|
double tmp_pos=std::fmod(m_img_sample,smpl_per_lin) / smpl_per_lin;
|
|
m_last_col=static_cast<int>(tmp_pos*m_img_width);
|
|
|
|
/// Now the image will start at the right column offset.
|
|
m_fax_pix_num = m_last_col * bytes_per_pixel ;
|
|
|
|
m_num_phase_lines=0;
|
|
/// NOTE: If five or more, blacks stripes appear on the image ???
|
|
if( m_phase_lines >= 4 ) {
|
|
LOG_INFO( _("Skipping to reception: m_phase_lines=%d m_num_phase_lines=%d. LPM=%f"),
|
|
m_phase_lines, m_num_phase_lines, m_lpm_img );
|
|
skip_phasing_to_image(false);
|
|
}
|
|
} else if(m_phase_lines>0 && ++m_num_phase_lines>=5) {
|
|
/// TODO: Compare with m_tx_phasing_lin which indicates the number of phasing
|
|
/// lines sent when transmitting an image.
|
|
LOG_INFO( _("Missed last phasing line m_phase_lines=%d m_num_phase_lines=%d. LPM=%f"),
|
|
m_phase_lines, m_num_phase_lines, m_lpm_img );
|
|
/// Phasing header is finished but could not get the center.
|
|
skip_phasing_to_image(true);
|
|
} else if(m_curr_phase_len>5*m_sample_rate) {
|
|
m_curr_phase_len=0;
|
|
PUT_STATUS( state_rx_str() << ". " << _("Decoding phasing line, resetting.") );
|
|
} else {
|
|
/// Here, if no phasing is detected. Must be very fast.
|
|
}
|
|
PUT_STATUS( state_rx_str() << ". " << _("Decoding phasing line, reset.") );
|
|
m_curr_phase_len=m_curr_phase_high=0;
|
|
}
|
|
else
|
|
{
|
|
/// We do not the LPM so we assume the default.
|
|
/// TODO: We could take the one given by the GUI.
|
|
double smpl_per_lin = lpm_to_samples( LPM_DEFAULT );
|
|
int smpl_per_lin_int = smpl_per_lin ;
|
|
int nb_tested_phasing_lines = m_phasing_calls_nb / smpl_per_lin ;
|
|
|
|
if(
|
|
(m_phase_lines == 0) &&
|
|
(m_num_phase_lines == 0) &&
|
|
(nb_tested_phasing_lines >= 30) &&
|
|
( (m_phasing_calls_nb % smpl_per_lin_int) == 0 ) ) {
|
|
switch( the_signal.signal_state() ) {
|
|
case RXIMAGE :
|
|
LOG_INFO( _("Starting reception when phasing:%s"), the_signal.signal_text() );
|
|
skip_phasing_to_image(true);
|
|
break ;
|
|
/// If RXPHASING, we stay in phasing mode.
|
|
case RXAPTSTOP :
|
|
LOG_INFO( _("Strong APT stop when phasing:%s"), the_signal.signal_text() );
|
|
end_rx();
|
|
skip_apt_rx();
|
|
default : break ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool fax_implementation::decode_image(int x)
|
|
{
|
|
double current_row_dbl = m_img_sample / m_smpl_per_lin ;
|
|
int current_row = current_row_dbl ;
|
|
int curr_col= m_img_width * (current_row_dbl - current_row) ;
|
|
|
|
if(curr_col==m_last_col) {
|
|
m_pixel_val+=x;
|
|
m_pix_samples_nb++;
|
|
} else {
|
|
if(m_pix_samples_nb>0) {
|
|
m_pixel_val/=m_pix_samples_nb;
|
|
|
|
/// TODO: Put m_fax_pix_num in wefax_pic so that the saving of an image
|
|
/// will only be the number of added pixels. And it will hide
|
|
/// the storage of B/W pixels in three contiguous ones.
|
|
//
|
|
// TODO: If the machine is heavily loaded, it loses a couple of pixel.
|
|
// The consequence is that the image is shifted.
|
|
// We could use the local clock to deduce where the pixel must be written:
|
|
// - Store the first pixel reception time.
|
|
// - Compute m_fax_pix_num = (reception_time * LPM * image_width)
|
|
REQ( wefax_pic::update_rx_pic_bw, m_pixel_val, m_fax_pix_num );
|
|
m_fax_pix_num += bytes_per_pixel ;
|
|
}
|
|
m_last_col=curr_col;
|
|
m_pixel_val=x;
|
|
m_pix_samples_nb=1;
|
|
}
|
|
|
|
/// Prints the status from time to time.
|
|
if( (m_img_sample % 10000) == 0 ) {
|
|
PUT_STATUS( state_rx_str()
|
|
<< ". " << _("Image reception")
|
|
<< ", " << _("sample") << "=" << m_img_sample );
|
|
}
|
|
|
|
/// Hard-limit to the number of rows.
|
|
if( current_row >= m_max_fax_rows ) {
|
|
LOG_INFO( _("Maximum number of rows reached:%d. Manual=%d"), current_row, m_manual_mode );
|
|
save_automatic("max");
|
|
end_rx();
|
|
return true ;
|
|
} else {
|
|
return false ;
|
|
}
|
|
}
|
|
|
|
/// Called automatically or by the GUI, when clicking "Skip APT"
|
|
void fax_implementation::skip_apt_rx(void)
|
|
{
|
|
wefax_pic::skip_rx_apt();
|
|
if(m_rx_state!=RXAPTSTART) {
|
|
LOG_ERROR( _("Should be in APT state. State=%s. Manual=%d"), state_rx_str().c_str(), m_manual_mode );
|
|
}
|
|
lpm_set( 0 );
|
|
m_lpm_sum_rx = 0;
|
|
m_rx_state=RXPHASING;
|
|
m_phasing_calls_nb = 0 ;
|
|
m_phase_high = m_current_value>=128 ? true : false;
|
|
m_curr_phase_len=m_curr_phase_high=0;
|
|
m_phase_lines=m_num_phase_lines=0;
|
|
m_img_sample=0; /// Used for correlation between consecutive lines.
|
|
}
|
|
|
|
/// Called by the user when skipping phasing,
|
|
/// or automatically when phasing is detected.
|
|
void fax_implementation::skip_phasing_to_image(bool auto_center)
|
|
{
|
|
m_ptr_wefax->qso_rec_init();
|
|
m_ptr_wefax->qso_rec().putField( TX_PWR, "0");
|
|
|
|
REQ( wefax_pic::skip_rx_phasing, auto_center );
|
|
if(m_rx_state!=RXPHASING) {
|
|
LOG_ERROR( _("Should be in phasing state. State=%s"), state_rx_str().c_str() );
|
|
}
|
|
m_rx_state=RXIMAGE;
|
|
|
|
/// For monochrome, LPM=60, 90, 100, 120, 180, 240. For colour, LPM =120, 240
|
|
/// So we round to the nearest integer to avoid slanting.
|
|
int lpm_integer = wefax_pic::normalize_lpm( m_lpm_img );
|
|
if( m_lpm_img != lpm_integer )
|
|
{
|
|
LOG_INFO( _("LPM rounded from %f to %d. Manual=%d"), m_lpm_img, lpm_integer, m_manual_mode );
|
|
}
|
|
|
|
reset_counters();
|
|
|
|
/// From now on, m_lpm_img will never change and has a normalized value.
|
|
REQ( wefax_pic::update_rx_lpm, lpm_integer);
|
|
PUT_STATUS( state_rx_str() << ". " << _("Decoding phasing line LPM=") << lpm_integer );
|
|
lpm_set( lpm_integer );
|
|
}
|
|
|
|
/// Called by the user when clicking button. Never called automatically.
|
|
void fax_implementation::skip_phasing_rx(bool auto_center)
|
|
{
|
|
if(m_rx_state!=RXPHASING) {
|
|
LOG_ERROR( _("Should be in phasing state. State=%s"), state_rx_str().c_str() );
|
|
}
|
|
skip_phasing_to_image(auto_center);
|
|
|
|
/// We force these two values because these could not be detected automatically.
|
|
if( m_lpm_img != LPM_DEFAULT ) {
|
|
lpm_set( LPM_DEFAULT );
|
|
LOG_INFO( _("Forcing m_lpm_img=%f. Manual=%d"), m_lpm_img, m_manual_mode );
|
|
}
|
|
m_img_sample=0; /// The image start may not be what the phasing would have told.
|
|
}
|
|
|
|
// Here we want to remove the last detected phasing line and the following
|
|
// non phasing line from the beginning of the image and one second of apt stop
|
|
// from the end
|
|
void fax_implementation::end_rx(void)
|
|
{
|
|
/// Synchronized otherwise there might be a crash if something tries to access the data.
|
|
REQ(wefax_pic::abort_rx_viewer );
|
|
m_rx_state=RXAPTSTART;
|
|
reset_counters();
|
|
}
|
|
|
|
/// Receives data from the soundcard.
|
|
void fax_implementation::rx_new_samples(const double* audio_ptr, int audio_sz)
|
|
{
|
|
int demod[audio_sz];
|
|
static const double half_255 = 255.0 * 0.5 ;
|
|
const double ratio_sam_devi = half_255 * static_cast<double>(m_sample_rate)/fm_deviation;
|
|
|
|
/// The reception filter may have been changed by the GUI.
|
|
C_FIR_filter & ref_fir_filt_pair = m_rx_filters[ m_ix_filt ];
|
|
|
|
const double half_arc_sine_size = m_dbl_arc_sine.size() / 2.0 ;
|
|
|
|
for(int i=0; i<audio_sz; i++) {
|
|
double idx_aux = audio_ptr[i] ;
|
|
|
|
complex firin( idx_aux*m_dbl_cosine.next_value(), idx_aux*m_dbl_sine.next_value() );
|
|
complex firout ;
|
|
|
|
/// This returns zero if the filter is not yet stable.
|
|
/* int run_status = */ ref_fir_filt_pair.run( firin, firout );
|
|
|
|
double ifirout = firout.real();
|
|
double qfirout = firout.imag();
|
|
|
|
if(m_freq_mod ) {
|
|
/// Normalize values.
|
|
double abs=std::sqrt(qfirout*qfirout+ifirout*ifirout);
|
|
/// cosine(a)
|
|
ifirout/=abs;
|
|
/// sine(a)
|
|
qfirout/=abs;
|
|
|
|
/// Does it mean the signal should not be too strong ? Max is 32767.
|
|
if(abs>10000) {
|
|
/// Real part of the product of current vector,
|
|
/// by previous vector rotated of 90 degrees.
|
|
/// It makes something like sine(a-b).
|
|
/// Maybe the derivative of the phase, that is,
|
|
/// the instantaneous frequency ?
|
|
double y = m_q_fir_old * ifirout - m_i_fir_old * qfirout ;
|
|
|
|
/// Mapped to the interval [0 .. size]
|
|
/// m_dbl_arc_sine[i] = asin(2*i/m_dbl_arc_sine.size-1)/2/Pi.
|
|
/// TODO: Therefore it could be simplified ?
|
|
y = ( y + 1.0 ) * half_arc_sine_size;
|
|
|
|
/// TODO: y might be rounded with more accuracy: (int)(Y+0.5)
|
|
double x = ratio_sam_devi * m_dbl_arc_sine[static_cast<size_t>(y)];
|
|
|
|
int scaled_x = x + half_255 ;
|
|
if(scaled_x < 0) {
|
|
scaled_x=0;
|
|
} else if(scaled_x > 255) {
|
|
scaled_x=255;
|
|
}
|
|
demod[i]=scaled_x;
|
|
} else {
|
|
demod[i]=0;
|
|
}
|
|
} else {
|
|
/// Why 96000 ? bytes_per_pixel * 32000 because color ?
|
|
ifirout/=96000;
|
|
qfirout/=96000;
|
|
demod[i]=static_cast<int>
|
|
(std::sqrt(ifirout*ifirout+qfirout*qfirout));
|
|
}
|
|
|
|
m_i_fir_old=ifirout;
|
|
m_q_fir_old=qfirout;
|
|
}
|
|
decode( demod, audio_sz);
|
|
|
|
#ifdef WEFAX_DISPLAY_SCOPE
|
|
/// Nothing really meaningful to display.
|
|
/// Beware that some pixels are lost if too many things are displayed
|
|
double scope_demod[ audio_sz ];
|
|
/// TODO: Do this in the loop, it will avoid conversions.
|
|
for( int i = 0 ; i < audio_sz ; ++i ) {
|
|
scope_demod[ i ] = demod[i];
|
|
}
|
|
set_scope( (double *)scope_demod, audio_sz , true );
|
|
#endif // WEFAX_DISPLAY_SCOPE
|
|
}
|
|
|
|
// Init transmission. Called once only.
|
|
void fax_implementation::init_tx(int the_smpl_rat)
|
|
{
|
|
m_sample_rate=the_smpl_rat;
|
|
set_carrier(wefax_default_carrier);
|
|
/// These reset increments of trigo tables.
|
|
m_ptr_wefax->set_freq(wefax_default_carrier);
|
|
m_tx_state=TXAPTSTART;
|
|
}
|
|
|
|
/// Elements of buffer are between 0.0 and 1.0
|
|
void fax_implementation::modulate(const double* buffer, int number)
|
|
{
|
|
/// TODO: This should be in m_short_sine
|
|
static const double dbl_max_short_invert = 1.0 / 32768.0 ;
|
|
|
|
double stack_xmt_buf[number] ;
|
|
if( m_freq_mod ) {
|
|
for(int i = 0; i < number; i++) {
|
|
double tmp_freq = m_carrier + 2. * ( buffer[i] - 0.5 ) * fm_deviation ;
|
|
m_short_sine.set_increment(m_short_sine.size() * tmp_freq / m_sample_rate);
|
|
stack_xmt_buf[i] = m_short_sine.next_value() * dbl_max_short_invert ;
|
|
}
|
|
} else {
|
|
/// Beware: Not tested !
|
|
for(int i = 0; i < number; i++) {
|
|
stack_xmt_buf[i] = m_short_sine.next_value() * buffer[i] * dbl_max_short_invert ;
|
|
}
|
|
}
|
|
m_ptr_wefax->ModulateXmtr( stack_xmt_buf, number );
|
|
}
|
|
|
|
/// Returns true if succesful
|
|
bool fax_implementation::trx_do_next(void)
|
|
{
|
|
LOG_DEBUG("m_xmt_bytes=%d m_lpm_img=%f", m_xmt_bytes, m_lpm_img );
|
|
|
|
/// The number of samples sent for one line. The LPM is given by the GUI.
|
|
const int smpl_per_lin= m_smpl_per_lin;
|
|
|
|
/// Should not be too big because it is allocated on the stack, gcc feature.
|
|
static const int block_len = 256 ;
|
|
double buf[block_len];
|
|
|
|
bool end_of_loop = false ;
|
|
bool tx_completed = true ;
|
|
int curr_sample_idx = 0 , nb_samples_to_send = 0 ;
|
|
|
|
for(int num_bytes_to_write=0; ; ++num_bytes_to_write ) {
|
|
bool disp_msg = ( ( num_bytes_to_write % block_len ) == 0 ) && ( num_bytes_to_write > 0 );
|
|
|
|
if( disp_msg ) {
|
|
modulate( buf, num_bytes_to_write);
|
|
|
|
const char * curr_status_msg = state_to_str( m_tx_state );
|
|
/// TODO: Should be multiplied by 3 when sending in BW ?
|
|
if( m_ptr_wefax->is_tx_finished(curr_sample_idx, nb_samples_to_send, curr_status_msg ) ) {
|
|
end_of_loop = true ;
|
|
tx_completed = false;
|
|
continue ;
|
|
};
|
|
num_bytes_to_write = 0 ;
|
|
};
|
|
if( end_of_loop == true ) {
|
|
break ;
|
|
}
|
|
if(m_tx_state==TXAPTSTART) {
|
|
nb_samples_to_send = m_sample_rate * m_start_duration ;
|
|
if( curr_sample_idx < nb_samples_to_send ) {
|
|
buf[num_bytes_to_write]=(curr_sample_idx*2*m_apt_start_freq/m_sample_rate)%2;
|
|
curr_sample_idx++;
|
|
} else {
|
|
m_tx_state=TXPHASING;
|
|
curr_sample_idx=0;
|
|
}
|
|
}
|
|
if(m_tx_state==TXPHASING) {
|
|
nb_samples_to_send = smpl_per_lin * m_tx_phasing_lin ;
|
|
if( curr_sample_idx < nb_samples_to_send ) {
|
|
double pos= (double)(curr_sample_idx % smpl_per_lin) / (double)smpl_per_lin;
|
|
buf[num_bytes_to_write] = (pos<0.025||pos>=0.975 )
|
|
? (m_phase_inverted?0.0:1.0)
|
|
: (m_phase_inverted?1.0:0.0);
|
|
curr_sample_idx++;
|
|
} else {
|
|
m_tx_state=ENDPHASING;
|
|
curr_sample_idx=0;
|
|
}
|
|
}
|
|
if(m_tx_state==ENDPHASING) {
|
|
nb_samples_to_send = smpl_per_lin ;
|
|
if( curr_sample_idx < nb_samples_to_send ) {
|
|
buf[num_bytes_to_write]= m_phase_inverted?0.0:1.0;
|
|
curr_sample_idx++;
|
|
} else {
|
|
m_tx_state=TXIMAGE;
|
|
curr_sample_idx=0;
|
|
}
|
|
}
|
|
if(m_tx_state==TXIMAGE) {
|
|
/// The image must be stretched so that its width matches the fax width.
|
|
/// Accordingly the height is stretched. This could be computed in advance.
|
|
double ratio_img_to_fax = (double)smpl_per_lin / m_img_tx_cols ;
|
|
|
|
int fax_rows_nb = m_img_tx_rows * ratio_img_to_fax ;
|
|
|
|
/// The number of samples for the whole image.
|
|
/// TODO: It could be computed once and for all.
|
|
/// NOT SURE AT ALL.
|
|
// nb_samples_to_send = (m_img_color?bytes_per_pixel:1) * smpl_per_lin * fax_rows_nb ;
|
|
nb_samples_to_send = smpl_per_lin * fax_rows_nb ;
|
|
|
|
/// TODO: Maybe we are oversampling the image and losing definition.
|
|
if( curr_sample_idx < nb_samples_to_send ) {
|
|
int tmp_col = ( ( curr_sample_idx / bytes_per_pixel ) % smpl_per_lin ) / ratio_img_to_fax ;
|
|
if( tmp_col >= m_img_tx_cols ) {
|
|
LOG_ERROR(
|
|
"Inconsistent tmp_col=%d m_img_tx_cols=%d ratio_img_to_fax=%f "
|
|
"curr_sample_idx=%d smpl_per_lin=%d fax_rows_nb=%d m_img_color=%d",
|
|
tmp_col, m_img_tx_cols, ratio_img_to_fax,
|
|
curr_sample_idx, smpl_per_lin, fax_rows_nb, (int)m_img_color );
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
int tmp_row= curr_sample_idx / ( smpl_per_lin * ratio_img_to_fax );
|
|
|
|
if( tmp_row >= m_img_tx_rows ) {
|
|
LOG_ERROR( "Inconsistent tmp_row=%d m_img_tx_rows=%d "
|
|
"curr_sample_idx=%d smpl_per_lin=%d fax_rows_nb=%d "
|
|
"ratio_img_to_fax=%f nb_samples_to_send=%d",
|
|
tmp_row, m_img_tx_rows,
|
|
curr_sample_idx, smpl_per_lin, fax_rows_nb,
|
|
ratio_img_to_fax, nb_samples_to_send );
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
int image_offset = tmp_row * m_img_tx_cols + tmp_col ;
|
|
if( image_offset >= m_xmt_bytes )
|
|
{
|
|
LOG_ERROR( "Inconsistent image_offset=%d m_xmt_bytes=%d "
|
|
"tmp_row=%d m_img_tx_rows=%d "
|
|
"curr_sample_idx=%d smpl_per_lin=%d fax_rows_nb=%d",
|
|
image_offset, m_xmt_bytes,
|
|
tmp_row, m_img_tx_rows,
|
|
curr_sample_idx, smpl_per_lin, fax_rows_nb );
|
|
exit(EXIT_FAILURE);
|
|
};
|
|
|
|
/// TODO: NOT SURE AT ALL ...
|
|
/*
|
|
if( m_img_color ) {
|
|
image_offset /= bytes_per_pixel ;
|
|
}
|
|
unsigned char temp_pix = m_xmt_pic_buf[ image_offset ];
|
|
REQ(wefax_pic::set_tx_pic, temp_pix, tmp_col, tmp_row, m_img_tx_cols, m_img_color );
|
|
buf[num_bytes_to_write]= (double)temp_pix / 256.0 ;
|
|
curr_sample_idx++;
|
|
*/
|
|
unsigned char temp_pix = m_xmt_pic_buf[ image_offset ];
|
|
wefax_pic::set_tx_pic( temp_pix, tmp_col, tmp_row, m_img_tx_cols, m_img_color );
|
|
buf[num_bytes_to_write]= (double)temp_pix / 256.0 ;
|
|
if( m_img_color ) {
|
|
curr_sample_idx++;
|
|
} else {
|
|
/// Takes one byte out of three because they have the same value
|
|
/// for a monochrome image.
|
|
curr_sample_idx += bytes_per_pixel ;
|
|
}
|
|
} else {
|
|
m_tx_state=TXAPTSTOP;
|
|
curr_sample_idx=0;
|
|
}
|
|
}
|
|
if(m_tx_state==TXAPTSTOP) {
|
|
nb_samples_to_send = m_sample_rate * m_stop_duration ;
|
|
if( curr_sample_idx < nb_samples_to_send ) {
|
|
buf[num_bytes_to_write]=curr_sample_idx*2*m_apt_stop_freq/m_sample_rate%2;
|
|
curr_sample_idx++;
|
|
} else {
|
|
m_tx_state=IDLE;
|
|
end_of_loop = true ;
|
|
continue ;
|
|
}
|
|
}
|
|
} // loop
|
|
return tx_completed ;
|
|
}
|
|
|
|
void fax_implementation::tx_params_set(
|
|
int the_lpm,
|
|
const unsigned char * xmtpic_buffer,
|
|
bool is_color,
|
|
int img_w,
|
|
int img_h,
|
|
int xmt_bytes )
|
|
{
|
|
LOG_DEBUG("img_w=%d img_h=%d xmt_bytes=%d the_lpm=%d is_color=%d",
|
|
img_w, img_h, xmt_bytes, the_lpm, (int)is_color);
|
|
m_img_tx_rows=img_h;
|
|
m_img_tx_cols=img_w;
|
|
m_xmt_bytes = xmt_bytes ;
|
|
m_img_color = is_color ;
|
|
lpm_set( the_lpm );
|
|
m_xmt_pic_buf = xmtpic_buffer ;
|
|
|
|
PUT_STATUS( _("Sending picture.") << " " << _("Bytes") << "=" << m_xmt_bytes
|
|
<< _(" rows=") << m_img_tx_rows
|
|
<< _(" cols=") << m_img_tx_cols );
|
|
}
|
|
|
|
void fax_implementation::tx_apt_stop(void)
|
|
{
|
|
m_tx_state=TXAPTSTOP;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//=============================================================================
|
|
|
|
/// Called by trx_trx_transmit_loop
|
|
void wefax::tx_init(SoundBase *sc)
|
|
{
|
|
modem::scard = sc; // SoundBase
|
|
|
|
videoText(); // In trx/modem.cxx
|
|
m_impl->init_tx(modem::samplerate) ;
|
|
}
|
|
|
|
/// This updates the window label according to the state.
|
|
void wefax::update_rx_label(void) const
|
|
{
|
|
std::string tmp_label( _("Reception ") );
|
|
tmp_label += mode_info[modem::mode].name ;
|
|
|
|
if( m_impl->manual_mode_get() ) {
|
|
tmp_label += _(" - Manual") ;
|
|
} else {
|
|
tmp_label += _(" - APT control") ;
|
|
}
|
|
|
|
REQ( wefax_pic::set_rx_label, tmp_label );
|
|
}
|
|
|
|
void wefax::rx_init()
|
|
{
|
|
put_MODEstatus(modem::mode);
|
|
m_impl->init_rx(modem::samplerate) ;
|
|
|
|
/// This updates the window label.
|
|
set_rx_manual_mode(false);
|
|
|
|
REQ( wefax_pic::resize_rx_viewer, m_impl->fax_width() );
|
|
update_rx_label();
|
|
}
|
|
|
|
void wefax::init()
|
|
{
|
|
modem::init();
|
|
|
|
/// TODO: Maybe remove, probably not necessary because called by trx_trx_loop
|
|
rx_init();
|
|
|
|
/// TODO: Display scope.
|
|
set_scope_mode(Digiscope::SCOPE);
|
|
}
|
|
|
|
void wefax::shutdown()
|
|
{
|
|
}
|
|
|
|
wefax::~wefax()
|
|
{
|
|
modem::stopflag = true;
|
|
|
|
REQ( wefax_pic::rx_hide );
|
|
|
|
/// Maybe we are receiving an image, this must be stopped.
|
|
end_reception();
|
|
|
|
/// Maybe we are sending an image.
|
|
REQ( wefax_pic::abort_tx_viewer );
|
|
|
|
activate_wefax_image_item(false);
|
|
|
|
delete m_impl ;
|
|
|
|
/// Not really useful, just to help debugging.
|
|
m_impl = 0 ;
|
|
}
|
|
|
|
wefax::wefax(trx_mode wefax_mode) : modem()
|
|
{
|
|
/// Beware that it is already set by modem::modem
|
|
modem::cap |= CAP_AFC | CAP_REV | CAP_IMG ;
|
|
LOG_DEBUG("wefax_mode=%d", (int)wefax_mode);
|
|
|
|
wefax::mode = wefax_mode;
|
|
|
|
modem::samplerate = WEFAXSampleRate ;
|
|
|
|
m_impl = new fax_implementation(wefax_mode, this);
|
|
|
|
/// Now this object is usable by wefax_pic.
|
|
wefax_pic::setpicture_link(this);
|
|
|
|
modem::bandwidth = fm_deviation * 2 ;
|
|
|
|
m_abortxmt = false;
|
|
modem::stopflag = false;
|
|
|
|
/// By default, images are not logged to adif file.
|
|
m_adif_log = false ;
|
|
|
|
// There is only one instance of the reception and transmission
|
|
// windows, therefore only static methods.
|
|
wefax_pic::create_tx_viewer();
|
|
|
|
/// TODO: Temp only, later remove sizing info, do it once only in resize.
|
|
wefax_pic::create_rx_viewer();
|
|
update_rx_label();
|
|
|
|
activate_wefax_image_item(true);
|
|
|
|
init();
|
|
}
|
|
|
|
|
|
//=====================================================================
|
|
// receive processing
|
|
//=====================================================================
|
|
|
|
#ifdef __linux__
|
|
|
|
#include <sys/timeb.h>
|
|
/// This must return the current time in seconds with high precision.
|
|
static double current_time(void)
|
|
{
|
|
struct timeb tmp_timb ;
|
|
ftime( &tmp_timb );
|
|
|
|
return (double)tmp_timb.time + tmp_timb.millitm / 1000.0 ;
|
|
}
|
|
|
|
#else
|
|
#include <ctime>
|
|
/// This is much less accurate.
|
|
static double current_time(void)
|
|
{
|
|
clock_t clks = clock();
|
|
|
|
return clks * 1.0 / CLOCKS_PER_SEC;
|
|
}
|
|
#endif
|
|
|
|
|
|
/// Callback continuously called by fldigi modem class.
|
|
int wefax::rx_process(const double *buf, int len)
|
|
{
|
|
if( len == 0 )
|
|
{
|
|
return 0 ;
|
|
}
|
|
|
|
static const int avg_buf_size = 256 ;
|
|
|
|
static int idx = 0 ;
|
|
|
|
static double buf_tim[avg_buf_size];
|
|
static int buf_len[avg_buf_size];
|
|
|
|
int idx_mod = idx % avg_buf_size ;
|
|
|
|
/// Here we estimate the average number of pixels per second.
|
|
buf_tim[idx_mod] = current_time();
|
|
buf_len[idx_mod] = len ;
|
|
|
|
++idx ;
|
|
|
|
/// Wait some seconds otherwise not significant.
|
|
if( idx >= avg_buf_size ) {
|
|
if( idx == avg_buf_size ) {
|
|
LOG_INFO( _("Starting samples loss control avg_buf_size=%d"), avg_buf_size);
|
|
}
|
|
int idx_mod_first = idx % avg_buf_size ;
|
|
double total_tim = buf_tim[idx_mod] - buf_tim[idx_mod_first];
|
|
int total_len = 0 ;
|
|
for( int ix = 0 ; ix < avg_buf_size ; ++ix ) {
|
|
total_len += buf_len[ix] ;
|
|
}
|
|
|
|
/// Estimate the real sample rate.
|
|
double estim_smpl_rate = (double)total_len / total_tim ;
|
|
|
|
/// If too far from what it should be, it means that pixels were lost.
|
|
if( estim_smpl_rate < 0.95 * modem::samplerate ) {
|
|
int expected_samples = (int)( modem::samplerate * total_tim + 0.5 );
|
|
int missing_samples = expected_samples - total_len ;
|
|
|
|
LOG_INFO(_("Lost %d samples idx=%d estim_smpl_rate=%f total_tim=%f total_len=%d"),
|
|
missing_samples,
|
|
idx,
|
|
estim_smpl_rate,
|
|
total_tim,
|
|
total_len );
|
|
|
|
if( missing_samples <= 0 ) {
|
|
/// This should practically never happen.
|
|
LOG_WARN(_("Cannot compensate"));
|
|
} else {
|
|
/// Adjust the number of received pixels,
|
|
/// so the lost frames are signaled once only.
|
|
buf_len[idx_mod] += missing_samples ;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Back to normal processing.
|
|
m_impl->rx_new_samples( buf, len );
|
|
return 0;
|
|
}
|
|
|
|
//=====================================================================
|
|
// transmit processing
|
|
//=====================================================================
|
|
|
|
/// This is called by wefax-pix.cxx before entering transmission loop.
|
|
void wefax::set_tx_parameters(
|
|
int the_lpm,
|
|
const unsigned char * xmtpic_buffer,
|
|
bool is_color,
|
|
int img_w,
|
|
int img_h,
|
|
int xmt_bytes )
|
|
{
|
|
m_impl->tx_params_set(
|
|
the_lpm,
|
|
xmtpic_buffer,
|
|
is_color,
|
|
img_w,
|
|
img_h,
|
|
xmt_bytes );
|
|
}
|
|
|
|
/// Callback continuously called by fldigi modem class.
|
|
int wefax::tx_process()
|
|
{
|
|
bool tx_was_completed = m_impl->trx_do_next();
|
|
std::string status ;
|
|
if( false == tx_was_completed ) {
|
|
status = _("Transmission cancelled");
|
|
LOG_INFO("Sending cancelled" );
|
|
m_qso_rec.putField(NOTES, status.c_str() );
|
|
}
|
|
qso_rec_save();
|
|
|
|
REQ_FLUSH(GET_THREAD_ID());
|
|
|
|
FL_LOCK_E();
|
|
wefax_pic::restart_tx_viewer();
|
|
transmit_lock_release(status);
|
|
m_abortxmt = false;
|
|
FL_UNLOCK_E();
|
|
m_impl->tx_apt_stop();
|
|
return -1;
|
|
}
|
|
|
|
void wefax::skip_apt(void)
|
|
{
|
|
m_impl->skip_apt_rx();
|
|
}
|
|
|
|
/// auto_center indicates whether we try to find the margin of the image
|
|
/// automatically. This is the fact when skipping to image reception
|
|
/// is triggered manually or based on the signal power.
|
|
void wefax::skip_phasing(bool auto_center)
|
|
{
|
|
m_impl->skip_phasing_rx(auto_center);
|
|
}
|
|
|
|
void wefax::end_reception(void)
|
|
{
|
|
m_impl->end_rx();
|
|
}
|
|
|
|
// Continuous reception or APT control.
|
|
void wefax::set_rx_manual_mode( bool manual_flag )
|
|
{
|
|
m_impl->manual_mode_set( manual_flag );
|
|
update_rx_label();
|
|
}
|
|
|
|
// Maximum admissible number of lines for a fax, adjustable by the user.
|
|
void wefax::set_max_lines( int max_lines )
|
|
{
|
|
m_impl->max_lines_set( max_lines );
|
|
}
|
|
|
|
int wefax::get_max_lines(void) const
|
|
{
|
|
return m_impl->max_lines_get();
|
|
}
|
|
|
|
void wefax::set_lpm( int the_lpm )
|
|
{
|
|
return m_impl->lpm_set( the_lpm );
|
|
}
|
|
|
|
/// Transmission time in seconds. Factor 3 if b/w image.
|
|
int wefax::tx_time( int nb_bytes ) const
|
|
{
|
|
return (double)nb_bytes / ( modem::samplerate * 3.0 );
|
|
}
|
|
|
|
/// This prints a message about the progress of image sending,
|
|
/// then tells whether the user has requested the end.
|
|
bool wefax::is_tx_finished( int ix_sample, int nb_sample, const char * msg ) const
|
|
{
|
|
static char wefaxmsg[256];
|
|
double fraction_done = nb_sample ? 100.0 * (double)ix_sample / nb_sample : 0.0 ;
|
|
int tm_left = tx_time( nb_sample - ix_sample );
|
|
snprintf(
|
|
wefaxmsg, sizeof(wefaxmsg),
|
|
"%s : %04.1f%% done. Time left: %dm %ds",
|
|
msg,
|
|
fraction_done,
|
|
tm_left / 60,
|
|
tm_left % 60 );
|
|
put_status(wefaxmsg);
|
|
|
|
bool is_finished = modem::stopflag || m_abortxmt ;
|
|
if( is_finished ) {
|
|
LOG_INFO("Transmit finished");
|
|
}
|
|
return is_finished ;
|
|
}
|
|
|
|
/// This returns the names of the possible reception filters.
|
|
const char ** wefax::rx_filters(void)
|
|
{
|
|
return fir_filter_pair_set::filters_list();
|
|
}
|
|
|
|
/// Allows to choose the reception filter.
|
|
void wefax::set_rx_filter( int idx_filter )
|
|
{
|
|
m_impl->set_filter_rx( idx_filter );
|
|
}
|
|
|
|
std::string wefax::suggested_filename(void) const
|
|
{
|
|
return m_impl->generate_filename( "gui" );
|
|
};
|
|
|
|
/// This creates a QSO record to be written to an adif file.
|
|
void wefax::qso_rec_init(void)
|
|
{
|
|
if( m_adif_log == false ) {
|
|
return ;
|
|
}
|
|
|
|
/// Ideally we should find out the name of the fax station.
|
|
m_qso_rec.putField(CALL, "Wefax");
|
|
m_qso_rec.putField(NAME, "Weather fax");
|
|
|
|
time_t tmp_time = time(NULL);
|
|
struct tm tmp_tm ;
|
|
localtime_r( &tmp_time, &tmp_tm );
|
|
|
|
char buf_date[64] ;
|
|
snprintf( buf_date, sizeof(buf_date),
|
|
"%04d%02d%02d",
|
|
1900 + tmp_tm.tm_year,
|
|
1 + tmp_tm.tm_mon,
|
|
tmp_tm.tm_mday );
|
|
m_qso_rec.putField(QSO_DATE, buf_date);
|
|
|
|
char buf_timeon[64] ;
|
|
snprintf( buf_timeon, sizeof(buf_timeon),
|
|
"%02d%02d",
|
|
tmp_tm.tm_hour,
|
|
tmp_tm.tm_min );
|
|
m_qso_rec.putField(TIME_ON, buf_timeon);
|
|
|
|
long long tmp_fl = wf->rfcarrier() ;
|
|
double freq_dbl = tmp_fl / 1000000.0 ;
|
|
char buf_freq[64];
|
|
snprintf( buf_freq, sizeof(buf_freq), "%lf", freq_dbl );
|
|
m_qso_rec.putField(FREQ, buf_freq );
|
|
|
|
/// The method get_mode should be const.
|
|
m_qso_rec.putField(MODE, mode_info[ const_cast<wefax*>(this)->get_mode()].adif_name );
|
|
|
|
// m_qso_rec.putField(QTH, inpQth_log->value());
|
|
// m_qso_rec.putField(STATE, inpState_log->value());
|
|
// m_qso_rec.putField(VE_PROV, inpVE_Prov_log->value());
|
|
// m_qso_rec.putField(COUNTRY, inpCountry_log->value());
|
|
// m_qso_rec.putField(GRIDSQUARE, inpLoc_log->value());
|
|
// m_qso_rec.putField(QSLRDATE, inpQSLrcvddate_log->value());
|
|
// m_qso_rec.putField(QSLSDATE, inpQSLsentdate_log->value());
|
|
// m_qso_rec.putField(RST_RCVD, inpRstR_log->value ());
|
|
// m_qso_rec.putField(RST_SENT, inpRstS_log->value ());
|
|
// m_qso_rec.putField(SRX, inpSerNoIn_log->value());
|
|
// m_qso_rec.putField(STX, inpSerNoOut_log->value());
|
|
// m_qso_rec.putField(XCHG1, inpXchgIn_log->value());
|
|
// m_qso_rec.putField(MYXCHG, inpMyXchg_log->value());
|
|
// m_qso_rec.putField(IOTA, inpIOTA_log->value());
|
|
// m_qso_rec.putField(DXCC, inpDXCC_log->value());
|
|
// m_qso_rec.putField(CONT, inpCONT_log->value());
|
|
// m_qso_rec.putField(CQZ, inpCQZ_log->value());
|
|
// m_qso_rec.putField(ITUZ, inpITUZ_log->value());
|
|
// m_qso_rec.putField(TX_PWR, inpTX_pwr_log->value());
|
|
}
|
|
|
|
/// Called once a QSO rec has been filled with information. Saved to adif file.
|
|
void wefax::qso_rec_save(void)
|
|
{
|
|
if( m_adif_log == false ) {
|
|
return ;
|
|
}
|
|
|
|
time_t tmp_time = time(NULL);
|
|
struct tm tmp_tm ;
|
|
localtime_r( &tmp_time, &tmp_tm );
|
|
|
|
char buf_timeoff[64] ;
|
|
snprintf( buf_timeoff, sizeof(buf_timeoff),
|
|
"%02d%02d",
|
|
tmp_tm.tm_hour,
|
|
tmp_tm.tm_min );
|
|
m_qso_rec.putField(TIME_OFF, buf_timeoff);
|
|
|
|
adifFile.writeLog (logbook_filename.c_str(), &qsodb);
|
|
qsodb.isdirty(0);
|
|
|
|
qsodb.qsoNewRec (&m_qso_rec);
|
|
// dxcc_entity_cache_add(&rec);
|
|
LOG_INFO( _("Updating log book %s"), logbook_filename.c_str() );
|
|
}
|
|
|
|
/// Called when changing the carrier in the GUI, and by class modem with 1000Hz, when initializing.
|
|
void wefax::set_freq(double freq)
|
|
{
|
|
modem::set_freq(freq);
|
|
/// This must recompute the increments of the trigonometric tables.
|
|
m_impl->set_carrier( freq );
|
|
}
|
|
|
|
// String telling the tx and rx internal state.
|
|
std::string wefax::state_string(void) const
|
|
{
|
|
return m_impl->state_string();
|
|
}
|
|
|
|
/// Called when a file is saved, so XML-RPC calls can get the filename.
|
|
void wefax::put_received_file( const std::string &filnam )
|
|
{
|
|
m_impl->put_received_file( filnam );
|
|
}
|
|
|
|
/// Returns a received file name, by chronological order.
|
|
std::string wefax::get_received_file( double max_seconds )
|
|
{
|
|
return m_impl->get_received_file( max_seconds );
|
|
}
|
|
|
|
/// This is not thread-safe, at all.
|
|
std::string wefax::send_file( const std::string & filnam, double max_seconds )
|
|
{
|
|
return m_impl->send_file( filnam, max_seconds );
|
|
}
|
|
|
|
/// Transmitting files is done in exclusive mode.
|
|
bool wefax::transmit_lock_acquire(const std::string & filnam, double max_seconds )
|
|
{
|
|
return m_impl->transmit_lock_acquire( filnam, max_seconds );
|
|
}
|
|
|
|
/// Called after a file is sent.
|
|
void wefax::transmit_lock_release( const std::string & err_msg )
|
|
{
|
|
m_impl->transmit_lock_release( err_msg );
|
|
}
|
|
|