mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
revert: remove fldigi CW decoder migration, keep Morse Expert
This commit is contained in:
1 parent
16dce5df2a
commit
f40a67e1b7
23 files changed
+23
-8021
No files matched your search
@@ -1,30 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project(fldigi_cw)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# 禁用 ANR 检测器等不必要的 Android 特性
|
||||
set(CMAKE_ANDROID_EXCEPTIONS ON)
|
||||
|
||||
add_library(fldigi_cw SHARED
|
||||
fldigi_cw_jni.cpp
|
||||
fldigi/cw.cxx
|
||||
fldigi/morse.cxx
|
||||
fldigi/fftfilt.cxx
|
||||
fldigi/filters.cxx
|
||||
)
|
||||
|
||||
target_include_directories(fldigi_cw PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fldigi
|
||||
)
|
||||
|
||||
target_compile_definitions(fldigi_cw PRIVATE
|
||||
FLDIGI_ANDROID
|
||||
)
|
||||
|
||||
target_link_libraries(fldigi_cw
|
||||
log
|
||||
android
|
||||
)
|
||||
@@ -1,115 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// android_compat.h -- fldigi desktop compatibility stubs for Android
|
||||
// Replaces global variables that fldigi defines in fl_digi.h, configuration.h, etc.
|
||||
// ----------------------------------------------------------------------------
|
||||
#ifndef ANDROID_COMPAT_H
|
||||
#define ANDROID_COMPAT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
// Waterfall stub
|
||||
struct waterfall_stub {
|
||||
double Carrier() { return 700.0; }
|
||||
bool Reverse() { return false; }
|
||||
bool USB() { return true; }
|
||||
void Bandwidth(int) {}
|
||||
};
|
||||
extern waterfall_stub* wf;
|
||||
|
||||
// progdefaults - fldigi global configuration
|
||||
struct AndroidProgDefaults {
|
||||
int CWspeed = 18;
|
||||
double CWbandwidth = 200.0;
|
||||
double CWfarnsworth = 18;
|
||||
double CWupper = 0.8;
|
||||
double CWlower = 0.2;
|
||||
bool CWmfilt = true;
|
||||
bool CWtrack = true;
|
||||
int CWsweetspot = 700;
|
||||
int CWnoise = '*';
|
||||
int CWrisetime = 4;
|
||||
int QSKshape = 0;
|
||||
int CWdash2dot = 3;
|
||||
int CW_cal_speed = 18;
|
||||
bool CWusefarnsworth = false;
|
||||
bool use_KNWDkeying = false;
|
||||
bool use_ELCTkeying = false;
|
||||
bool use_ICOMkeying = false;
|
||||
bool use_YAESUkeying = false;
|
||||
int CATkeying_compensation = 0;
|
||||
bool StartAtSweetSpot = false;
|
||||
bool CW_use_paren = false;
|
||||
std::string CW_prosigns;
|
||||
bool pretone = false;
|
||||
bool use_nanoIO = false;
|
||||
bool rx_lowercase = false;
|
||||
bool CWuseSOMdecoding = true;
|
||||
int CW_bandwidth = 200;
|
||||
int CW_upper = 80;
|
||||
int CW_lower = 20;
|
||||
|
||||
// Additional fields referenced by cw.cxx
|
||||
int CWrange = 10;
|
||||
int CWlowerlimit = 5;
|
||||
int CWupperlimit = 60;
|
||||
int CWpre = 0;
|
||||
int CWpost = 0;
|
||||
int CWkeycomp = 0;
|
||||
int cwrx_attack = 0;
|
||||
int cwrx_decay = 0;
|
||||
int defCWspeed = 18;
|
||||
int QSK = 0;
|
||||
int QSKamp = 0;
|
||||
int QSKfrequency = 0;
|
||||
int QSKrisetime = 0;
|
||||
bool CW_KEYLINE = false;
|
||||
bool CW_KEYLINE_on_cat_port = false;
|
||||
bool CW_KEYLINE_on_ptt_port = false;
|
||||
bool PTT_KEYLINE = false;
|
||||
bool use_FLRIGkeying = false;
|
||||
int BaudRate = 0;
|
||||
std::string CW_KEYLINE_serial_port_name;
|
||||
};
|
||||
extern AndroidProgDefaults progdefaults;
|
||||
|
||||
// NanoIO globals
|
||||
extern bool use_nanoIO;
|
||||
void set_nanoWPM(int wpm);
|
||||
|
||||
// UI/status stubs (fldigi desktop helpers, no-op on Android)
|
||||
void put_cwRcvWPM(int);
|
||||
void put_MODEstatus(const char*, ...);
|
||||
void set_scope_xaxis_1(double);
|
||||
void set_scope_xaxis(double);
|
||||
template<typename T, int N, int M>
|
||||
void set_scope(mbuffer<T,N,M>&, int, bool) {}
|
||||
void set_nanoCW();
|
||||
void update_Status();
|
||||
|
||||
// Fldigi math helpers
|
||||
#define TWOPI (2.0 * M_PI)
|
||||
inline double decayavg(double average, double input, int weight) {
|
||||
if (weight <= 1) return input;
|
||||
return ((input - average) / (double)weight) + average;
|
||||
}
|
||||
|
||||
// progStatus
|
||||
struct AndroidProgStatus {
|
||||
int carrier = 0;
|
||||
bool WK_online = false;
|
||||
bool sqlonoff = false;
|
||||
double sldrSquelchValue = 0.0;
|
||||
bool show_channels = false;
|
||||
};
|
||||
extern AndroidProgStatus progStatus;
|
||||
|
||||
// Misc helpers that fldigi provides via misc.h / status.h / etc.
|
||||
void set_scope_mode(int);
|
||||
void put_rx_char(int c);
|
||||
void put_echo_char(int c);
|
||||
|
||||
// Time helpers
|
||||
double zmsec();
|
||||
void MilliSleep(int);
|
||||
|
||||
#endif
|
||||
@@ -1,43 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// complex.h -- Complex arithmetic
|
||||
//
|
||||
// Copyright (C) 2006-2008
|
||||
// Dave Freese, W1HKJ
|
||||
// Copyright (C) 2008
|
||||
// Stelios Bounanos, M0GLD
|
||||
//
|
||||
// This file is part of fldigi.
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _COMPLEX_H
|
||||
#define _COMPLEX_H
|
||||
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
|
||||
typedef std::complex<double> cmplx;
|
||||
|
||||
inline cmplx cmac (const cmplx *a, const cmplx *b, int ptr, int len) {
|
||||
cmplx z;
|
||||
ptr %= len;
|
||||
for (int i = 0; i < len; i++) {
|
||||
z += a[i] * b[ptr];
|
||||
ptr = (ptr + 1) % len;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,197 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// cw.h -- morse code modem (Android adaptation)
|
||||
// Copyright (C) 2006-2009 Dave Freese, W1HKJ
|
||||
// Adapted from fldigi/src/include/cw.h (GPL v3).
|
||||
// ----------------------------------------------------------------------------
|
||||
#ifndef _CW_H
|
||||
#define _CW_H
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "modem.h"
|
||||
#include "filters.h"
|
||||
#include "fftfilt.h"
|
||||
#include "mbuffer.h"
|
||||
#include "view_cw.h"
|
||||
|
||||
#define CW_SAMPLERATE 8000
|
||||
#define CWMaxSymLen 4096
|
||||
#define MAX_MORSE_ELEMENTS 6
|
||||
#define CW_SUCCESS 0
|
||||
#define CW_ERROR -1
|
||||
#define ASC_NUL '\0'
|
||||
#define ASC_SPACE ' '
|
||||
#define KWPM (12 * CW_SAMPLERATE / 10)
|
||||
#define CWKNUM ((KWPM) / 10)
|
||||
#define TONE_SILENT 0
|
||||
#define USECS_PER_SEC 1000000
|
||||
#define INITIAL_SEND_SPEED 18
|
||||
#define INITIAL_RECEIVE_SPEED 18
|
||||
#define INITIAL_THRESHOLD (((KWPM) / INITIAL_RECEIVE_SPEED) * 2)
|
||||
#define INITIAL_NOISE_THRESHOLD (((KWPM) / CW_MAX_SPEED) / 2)
|
||||
#define TRACKING_FILTER_SIZE 16
|
||||
#define MAX_PIPE_SIZE (22 * CW_SAMPLERATE * 12 / 800)
|
||||
#define CW_MAX_SPEED 100
|
||||
|
||||
enum CW_RX_STATE {
|
||||
RS_IDLE = 0,
|
||||
RS_IN_TONE,
|
||||
RS_AFTER_TONE
|
||||
};
|
||||
|
||||
enum CW_EVENT {
|
||||
CW_RESET_EVENT,
|
||||
CW_KEYDOWN_EVENT,
|
||||
CW_KEYUP_EVENT,
|
||||
CW_QUERY_EVENT
|
||||
};
|
||||
|
||||
class cw : public modem {
|
||||
public:
|
||||
#define CLRCOUNT 16
|
||||
#define DEC_RATIO 16
|
||||
#define WGT_SIZE 7
|
||||
|
||||
struct SOM_TABLE {
|
||||
std::string rpr;
|
||||
float wgt[WGT_SIZE];
|
||||
};
|
||||
|
||||
protected:
|
||||
int symbollen;
|
||||
int fsymlen;
|
||||
double phaseacc;
|
||||
double FFTphase;
|
||||
double FFTvalue;
|
||||
unsigned int smpl_ctr;
|
||||
double agc_peak;
|
||||
bool use_matched_filter;
|
||||
double upper_threshold;
|
||||
double lower_threshold;
|
||||
fftfilt *cw_FFT_filter;
|
||||
Cmovavg *bitfilter;
|
||||
Cmovavg *trackingfilter;
|
||||
int bitfilterlen;
|
||||
CW_RX_STATE cw_receive_state;
|
||||
CW_RX_STATE old_cw_receive_state;
|
||||
CW_EVENT cw_event;
|
||||
double pipe[MAX_PIPE_SIZE + 1];
|
||||
double clearpipe[MAX_PIPE_SIZE + 1];
|
||||
mbuffer<double, MAX_PIPE_SIZE + 1, 4> scopedata;
|
||||
int pipeptr;
|
||||
int pipesize;
|
||||
bool scope_clear;
|
||||
|
||||
// Config (from progdefaults, replaced for Android)
|
||||
int cw_speed;
|
||||
int cw_bandwidth;
|
||||
int cw_squelch;
|
||||
int cw_send_speed;
|
||||
int cw_receive_speed;
|
||||
bool usedefaultWPM;
|
||||
int cw_upper_limit;
|
||||
int cw_lower_limit;
|
||||
long int cw_noise_spike_threshold;
|
||||
int cw_in_sync;
|
||||
long int cw_send_dot_length;
|
||||
long int cw_send_dash_length;
|
||||
int lastsym;
|
||||
double risetime;
|
||||
int knum;
|
||||
int qnum;
|
||||
int QSKshape;
|
||||
double qskbuf[OUTBUFSIZE];
|
||||
double qskphase;
|
||||
bool firstelement;
|
||||
bool lastelement;
|
||||
double maxval;
|
||||
long int cw_receive_dot_length;
|
||||
long int cw_receive_dash_length;
|
||||
std::string rx_rep_buf;
|
||||
int cw_rr_current;
|
||||
unsigned int cw_rr_start_timestamp;
|
||||
unsigned int cw_rr_end_timestamp;
|
||||
long int two_dots;
|
||||
int in_replay;
|
||||
double dot_tracking;
|
||||
double dash_tracking;
|
||||
|
||||
// Android additions
|
||||
std::string rx_text_buffer; // decoded text for JNI retrieval
|
||||
|
||||
inline double nco(double freq);
|
||||
inline double qsknco();
|
||||
void update_syncscope();
|
||||
void clear_syncscope();
|
||||
void update_Status();
|
||||
void sync_parameters();
|
||||
void reset_rx_filter();
|
||||
int handle_event(int cw_event, std::string &sc);
|
||||
inline int usec_diff(unsigned int earlier, unsigned int later);
|
||||
void send_symbol(int symbol, int len, int state);
|
||||
void send_ch(int c);
|
||||
bool tables_init();
|
||||
unsigned int tokenize_representation(char *representation);
|
||||
void update_tracking(int dot, int dash);
|
||||
|
||||
static const SOM_TABLE som_table[];
|
||||
float cw_buffer[512];
|
||||
int cw_ptr;
|
||||
int clrcount;
|
||||
double lowerwpm;
|
||||
double upperwpm;
|
||||
int synchscope;
|
||||
double noise_floor;
|
||||
double sig_avg;
|
||||
double siglevel;
|
||||
bool use_paren;
|
||||
std::string prosigns;
|
||||
cmplx mixer(cmplx in);
|
||||
|
||||
int nusymbollen;
|
||||
int nufsymlen;
|
||||
int kpre;
|
||||
int kpost;
|
||||
double wpm;
|
||||
double fwpm;
|
||||
double cal_wpm;
|
||||
void create_edges();
|
||||
void sync_transmit_parameters();
|
||||
void flush_audio();
|
||||
void send_CW(int);
|
||||
view_cw viewcw;
|
||||
|
||||
public:
|
||||
cw();
|
||||
~cw();
|
||||
void init();
|
||||
void rx_init();
|
||||
void tx_init();
|
||||
void restart() {}
|
||||
|
||||
int rx_process(const double *buf, int len);
|
||||
void rx_FFTprocess(const double *buf, int len);
|
||||
void rx_FIRprocess(const double *buf, int len);
|
||||
void decode_stream(double);
|
||||
|
||||
int tx_process();
|
||||
void incWPM();
|
||||
void decWPM();
|
||||
void toggleWPM();
|
||||
double calWPM() { return cal_wpm; }
|
||||
void calWPM(double val) { cal_wpm = val; }
|
||||
|
||||
int normalize(float *v, int n, int twodots);
|
||||
std::string find_winner(float *inbuf, int twodots);
|
||||
|
||||
// Android: get and clear decoded text buffer
|
||||
std::string get_rx_text() {
|
||||
std::string result = rx_text_buffer;
|
||||
rx_text_buffer.clear();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,315 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// fftfilt.cxx -- Fast convolution Overlap-Add filter
|
||||
//
|
||||
// Filter implemented using overlap-add FFT convolution method
|
||||
// h(t) characterized by Windowed-Sinc impulse response
|
||||
//
|
||||
// Reference:
|
||||
// "The Scientist and Engineer's Guide to Digital Signal Processing"
|
||||
// by Dr. Steven W. Smith, http://www.dspguide.com
|
||||
// Chapters 16, 18 and 21
|
||||
//
|
||||
// Copyright (C) 2006-2008 Dave Freese, W1HKJ
|
||||
//
|
||||
// This file is part of fldigi.
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <memory.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <typeinfo>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include "misc.h"
|
||||
#include "fftfilt.h"
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// initialize the filter
|
||||
// create forward and reverse FFTs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// probably only need a single instance of g_fft !!
|
||||
// use for both forward and reverse
|
||||
|
||||
void fftfilt::clear_filter()
|
||||
{
|
||||
for (int i = 0; i < flen; i++) {
|
||||
filter[i] = 0;
|
||||
timedata[i] = 0;
|
||||
freqdata[i] = 0;
|
||||
output[i] = 0;
|
||||
ht[i] = 0;
|
||||
}
|
||||
for (int i = 0; i < flen2; i++)
|
||||
ovlbuf[i] = 0;
|
||||
inptr = 0;
|
||||
}
|
||||
|
||||
void fftfilt::init_filter()
|
||||
{
|
||||
flen2 = flen >> 1;
|
||||
fft = new g_fft<double>(flen);
|
||||
|
||||
filter = new cmplx[flen];
|
||||
timedata = new cmplx[flen];
|
||||
freqdata = new cmplx[flen];
|
||||
output = new cmplx[flen];
|
||||
ovlbuf = new cmplx[flen2];
|
||||
ht = new cmplx[flen];
|
||||
}
|
||||
|
||||
// number of samples needed to completely flush the filter
|
||||
int fftfilt::flush_size()
|
||||
{
|
||||
return flen - inptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// fft filter
|
||||
// f1 < f2 ==> band pass filter
|
||||
// f1 > f2 ==> band reject filter
|
||||
// f1 == 0 ==> low pass filter
|
||||
// f2 == 0 ==> high pass filter
|
||||
//------------------------------------------------------------------------------
|
||||
fftfilt::fftfilt(double f1, double f2, int len)
|
||||
{
|
||||
flen = len;
|
||||
init_filter();
|
||||
create_filter(f1, f2);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// low pass filter
|
||||
//------------------------------------------------------------------------------
|
||||
fftfilt::fftfilt(double f, int len)
|
||||
{
|
||||
flen = len;
|
||||
init_filter();
|
||||
create_lpf(f);
|
||||
}
|
||||
|
||||
fftfilt::~fftfilt()
|
||||
{
|
||||
if (fft) delete fft;
|
||||
|
||||
if (filter) delete [] filter;
|
||||
if (timedata) delete [] timedata;
|
||||
if (freqdata) delete [] freqdata;
|
||||
if (output) delete [] output;
|
||||
if (ovlbuf) delete [] ovlbuf;
|
||||
if (ht) delete [] ht;
|
||||
}
|
||||
|
||||
void fftfilt::create_filter(double f1, double f2)
|
||||
{
|
||||
clear_filter();
|
||||
// initialize the filter to zero
|
||||
for (int i = 0; i < flen; i++) ht[i] = 0;
|
||||
|
||||
// create the filter shape coefficients by fft
|
||||
// filter values initialized to the ht response h(t)
|
||||
bool b_lowpass, b_highpass;//, window;
|
||||
b_lowpass = (f2 != 0);
|
||||
b_highpass = (f1 != 0);
|
||||
|
||||
for (int i = 0; i < flen2; i++) {
|
||||
ht[i] = 0;
|
||||
//combine lowpass / highpass
|
||||
// lowpass @ f2
|
||||
if (b_lowpass) ht[i] += fsinc(f2, i, flen2);
|
||||
// highighpass @ f1
|
||||
if (b_highpass) ht[i] -= fsinc(f1, i, flen2);
|
||||
}
|
||||
// highpass is delta[flen2/2] - h(t)
|
||||
if (b_highpass && f2 < f1) ht[flen2 / 2] += 1;
|
||||
|
||||
for (int i = 0; i < flen2; i++)
|
||||
ht[i] *= _blackman(i, flen2);
|
||||
|
||||
// this may change since green fft is in place fft
|
||||
memcpy(filter, ht, flen * sizeof(cmplx));
|
||||
|
||||
// ht is flen complex points with imaginary all zero
|
||||
// first half describes h(t), second half all zeros
|
||||
// perform the cmplx forward fft to obtain H(w)
|
||||
// filter is flen/2 complex values
|
||||
|
||||
fft->ComplexFFT(filter);
|
||||
// fft->transform(ht, filter);
|
||||
|
||||
// normalize the output filter for unity gain
|
||||
double scale = 0, mag;
|
||||
for (int i = 0; i < flen2; i++) {
|
||||
mag = abs(filter[i]);
|
||||
if (mag > scale) scale = mag;
|
||||
}
|
||||
if (scale != 0) {
|
||||
for (int i = 0; i < flen; i++)
|
||||
filter[i] /= scale;
|
||||
}
|
||||
|
||||
// perform the reverse fft to obtain h(t)
|
||||
// for testing
|
||||
// uncomment to obtain filter characteristics
|
||||
/*
|
||||
cmplx *revht = new cmplx[flen];
|
||||
memcpy(revht, filter, flen * sizeof(cmplx));
|
||||
|
||||
fft->InverseComplexFFT(revht);
|
||||
|
||||
std::fstream fspec;
|
||||
fspec.open("fspec.csv", std::ios::out);
|
||||
fspec << "i,imp.re,imp.im,filt.re,filt.im,filt.abs,revimp.re,revimp.im\n";
|
||||
for (int i = 0; i < flen2; i++)
|
||||
fspec
|
||||
<< i << "," << ht[i].real() << "," << ht[i].imag() << ","
|
||||
<< filter[i].real() << "," << filter[i].imag() << ","
|
||||
<< abs(filter[i]) << ","
|
||||
<< revht[i].real() << "," << revht[i].imag() << ","
|
||||
<< std::endl;
|
||||
fspec.close();
|
||||
delete [] revht;
|
||||
*/
|
||||
// start output after 2 full passes are complete
|
||||
pass = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Filter with fast convolution (overlap-add algorithm).
|
||||
*/
|
||||
|
||||
int fftfilt::run(const cmplx & in, cmplx **out)
|
||||
{
|
||||
// collect flen/2 input samples
|
||||
timedata[inptr++] = in;
|
||||
|
||||
if (inptr < flen2)
|
||||
return 0;
|
||||
if (pass) --pass; // filter output is not stable until 2 passes
|
||||
|
||||
// FFT transpose to the frequency domain
|
||||
memcpy(freqdata, timedata, flen * sizeof(cmplx));
|
||||
fft->ComplexFFT(freqdata);
|
||||
|
||||
// multiply with the filter shape
|
||||
for (int i = 0; i < flen; i++)
|
||||
freqdata[i] *= filter[i];
|
||||
|
||||
// transform back to time domain
|
||||
fft->InverseComplexFFT(freqdata);
|
||||
|
||||
// overlap and add
|
||||
// save the second half for overlapping next inverse FFT
|
||||
for (int i = 0; i < flen2; i++) {
|
||||
output[i] = ovlbuf[i] + freqdata[i];
|
||||
ovlbuf[i] = freqdata[i+flen2];
|
||||
}
|
||||
|
||||
// clear inbuf pointer
|
||||
inptr = 0;
|
||||
|
||||
// signal the caller there is flen/2 samples ready
|
||||
if (pass) return 0;
|
||||
|
||||
*out = output;
|
||||
return flen2;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// rtty filter
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//bool print_filter = true; // flag to inhibit printing multiple copies
|
||||
|
||||
void fftfilt::rtty_filter(double f)
|
||||
{
|
||||
// Raised cosine filter designed iaw Section 1.2.6 of
|
||||
// Telecommunications Measurements, Analysis, and Instrumentation
|
||||
// by Dr. Kamilo Feher / Engineers of Hewlett-Packard
|
||||
//
|
||||
// Frequency scaling factor determined hueristically by testing various values
|
||||
// and measuring resulting decoder CER with input s/n = - 9 dB
|
||||
//
|
||||
// K CER
|
||||
// 1.0 .0244
|
||||
// 1.1 .0117
|
||||
// 1.2 .0081
|
||||
// 1.3 .0062
|
||||
// 1.4 .0054
|
||||
// 1.5 .0062
|
||||
// 1.6 .0076
|
||||
|
||||
f *= 1.4;
|
||||
|
||||
double dht;
|
||||
for( int i = 0; i < flen2; ++i ) {
|
||||
double x = (double)i/(double)(flen2);
|
||||
|
||||
// raised cosine response (changed for -1.0...+1.0 times Nyquist-f
|
||||
// instead of books versions ranging from -1..+1 times samplerate)
|
||||
|
||||
dht =
|
||||
x <= 0 ? 1.0 :
|
||||
x > 2.0 * f ? 0.0 :
|
||||
cos((M_PI * x) / (f * 4.0));
|
||||
|
||||
dht *= dht; // cos^2
|
||||
|
||||
// amplitude equalized nyquist-channel response
|
||||
dht /= sinc(2.0 * i * f);
|
||||
|
||||
filter[i] =
|
||||
cmplx( dht*cos((double)i* - 0.5*M_PI),
|
||||
dht*sin((double)i* - 0.5*M_PI) );
|
||||
|
||||
filter[(flen-i)%flen] =
|
||||
cmplx( dht*cos((double)i*+0.5*M_PI),
|
||||
dht*sin((double)i*+0.5*M_PI) );
|
||||
}
|
||||
|
||||
// perform the reverse fft to obtain h(t)
|
||||
// for testing
|
||||
// uncomment to obtain filter characteristics
|
||||
/*
|
||||
cmplx *revht = new cmplx[flen];
|
||||
memcpy(revht, filter, flen * sizeof(cmplx));
|
||||
|
||||
fft->InverseComplexFFT(revht);
|
||||
|
||||
std::fstream fspec;
|
||||
fspec.open("rtty_filter.csv", std::ios::out);
|
||||
fspec << "i,filt.re,filt.im,filt.abs,,revimp.re,revimp.im\n";
|
||||
for (int i = 0; i < flen; i++)
|
||||
fspec
|
||||
<< i << ","
|
||||
<< filter[i].real() << "," << filter[i].imag() << "," << abs(filter[i])
|
||||
<< ",," << revht[i].real() << "," << revht[i].imag()
|
||||
<< std::endl;
|
||||
fspec.close();
|
||||
delete [] revht;
|
||||
*/
|
||||
// start output after 2 full passes are complete
|
||||
pass = 1;
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright (C) 2014
|
||||
// David Freese, W1HKJ
|
||||
//
|
||||
// This file is part of fldigi
|
||||
//
|
||||
// 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _FFTFILT_H
|
||||
#define _FFTFILT_H
|
||||
|
||||
#include "complex.h"
|
||||
#include "gfft.h"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
class fftfilt {
|
||||
enum {NONE, BLACKMAN, HAMMING, HANNING};
|
||||
|
||||
protected:
|
||||
int flen;
|
||||
int flen2;
|
||||
g_fft<double> *fft;
|
||||
g_fft<double> *ift;
|
||||
cmplx *ht;
|
||||
cmplx *filter;
|
||||
cmplx *timedata;
|
||||
cmplx *freqdata;
|
||||
cmplx *ovlbuf;
|
||||
cmplx *output;
|
||||
int inptr;
|
||||
int pass;
|
||||
int window;
|
||||
|
||||
inline double fsinc(double fc, int i, int len) {
|
||||
return (i == len/2) ? 2.0 * fc:
|
||||
sin(2 * M_PI * fc * (i - len/2)) / (M_PI * (i - len/2));
|
||||
}
|
||||
inline double _blackman(int i, int len) {
|
||||
return (0.42 -
|
||||
0.50 * cos(2.0 * M_PI * i / len) +
|
||||
0.08 * cos(4.0 * M_PI * i / len));
|
||||
}
|
||||
void init_filter();
|
||||
void clear_filter();
|
||||
|
||||
public:
|
||||
fftfilt(double f1, double f2, int len);
|
||||
fftfilt(double f, int len);
|
||||
~fftfilt();
|
||||
// f1 < f2 ==> bandpass
|
||||
// f1 > f2 ==> band reject
|
||||
void create_filter(double f1, double f2);
|
||||
void create_lpf(double f) {
|
||||
create_filter(0, f);
|
||||
}
|
||||
void create_hpf(double f) {
|
||||
create_filter(f, 0);
|
||||
}
|
||||
void rtty_filter(double);
|
||||
|
||||
int run(const cmplx& in, cmplx **out);
|
||||
int flush_size();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,551 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// filters.cxx -- Several Digital Filter classes used in fldigi
|
||||
//
|
||||
// Copyright (C) 2006-2008 Dave Freese, W1HKJ
|
||||
//
|
||||
// These filters are based on the gmfsk design and the design notes given in
|
||||
// "Digital Signal Processing, A Practical Guid for Engineers and Scientists"
|
||||
// by Steven W. Smith.
|
||||
//
|
||||
// This file is part of fldigi.
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "filters.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// C_FIR_filter
|
||||
//
|
||||
// a class of Finite Impulse Response (FIR) filters with
|
||||
// decimate in time capability
|
||||
//
|
||||
//=====================================================================
|
||||
|
||||
C_FIR_filter::C_FIR_filter () {
|
||||
pointer = counter = length = 0;
|
||||
decimateratio = 1;
|
||||
ifilter = qfilter = (double *)0;
|
||||
ffreq = 0.0;
|
||||
}
|
||||
|
||||
C_FIR_filter::~C_FIR_filter() {
|
||||
if (ifilter) delete [] ifilter;
|
||||
if (qfilter) delete [] qfilter;
|
||||
}
|
||||
|
||||
void C_FIR_filter::init(int len, int dec, double *itaps, double *qtaps) {
|
||||
length = len;
|
||||
decimateratio = dec;
|
||||
if (ifilter) {
|
||||
delete [] ifilter;
|
||||
ifilter = (double *)0;
|
||||
}
|
||||
if (qfilter) {
|
||||
delete [] qfilter;
|
||||
qfilter = (double *)0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < FIRBufferLen; i++)
|
||||
ibuffer[i] = qbuffer[i] = 0.0;
|
||||
|
||||
if (itaps) {
|
||||
ifilter = new double[len];
|
||||
for (int i = 0; i < len; i++) ifilter[i] = itaps[i];
|
||||
}
|
||||
if (qtaps) {
|
||||
qfilter = new double[len];
|
||||
for (int i = 0; i < len; i++) qfilter[i] = qtaps[i];
|
||||
}
|
||||
|
||||
pointer = len;
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// Create a band pass FIR filter with 6 dB corner frequencies
|
||||
// of 'f1' and 'f2'. (0 <= f1 < f2 <= 0.5)
|
||||
//=====================================================================
|
||||
|
||||
double * C_FIR_filter::bp_FIR(int len, int hilbert, double f1, double f2)
|
||||
{
|
||||
double *fir;
|
||||
double t, h, x;
|
||||
|
||||
fir = new double[len];
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
t = i - (len - 1.0) / 2.0;
|
||||
h = i * (1.0 / (len - 1.0));
|
||||
|
||||
if (!hilbert) {
|
||||
x = (2 * f2 * sinc(2 * f2 * t) -
|
||||
2 * f1 * sinc(2 * f1 * t)) * hamming(h);
|
||||
} else {
|
||||
x = (2 * f2 * cosc(2 * f2 * t) -
|
||||
2 * f1 * cosc(2 * f1 * t)) * hamming(h);
|
||||
// The actual filter code assumes the impulse response
|
||||
// is in time reversed order. This will be anti-
|
||||
// symmetric so the minus sign handles that for us.
|
||||
x = -x;
|
||||
}
|
||||
|
||||
fir[i] = x;
|
||||
}
|
||||
|
||||
return fir;
|
||||
}
|
||||
|
||||
//=====================================================================
|
||||
// Filter will be a lowpass with
|
||||
// length = len
|
||||
// decimation = dec
|
||||
// 0.5 frequency point = freq
|
||||
//=====================================================================
|
||||
|
||||
void C_FIR_filter::init_lowpass (int len, int dec, double freq) {
|
||||
double *fi = bp_FIR(len, 0, 0.0, freq);
|
||||
ffreq = freq;
|
||||
init (len, dec, fi, fi);
|
||||
delete [] fi;
|
||||
}
|
||||
|
||||
//=====================================================================
|
||||
// Filter will be a bandpass with
|
||||
// length = len
|
||||
// decimation = dec
|
||||
// 0.5 frequency points of f1 (low) and f2 (high)
|
||||
//=====================================================================
|
||||
|
||||
void C_FIR_filter::init_bandpass (int len, int dec, double f1, double f2) {
|
||||
double *fi = bp_FIR (len, 0, f1, f2);
|
||||
init (len, dec, fi, fi);
|
||||
delete [] fi;
|
||||
}
|
||||
|
||||
//=====================================================================
|
||||
// Filter will the Hilbert form
|
||||
//=====================================================================
|
||||
|
||||
void C_FIR_filter::init_hilbert (int len, int dec) {
|
||||
double *fi = bp_FIR(len, 0, 0.05, 0.45);
|
||||
double *fq = bp_FIR(len, 1, 0.05, 0.45);
|
||||
init (len, dec, fi, fq);
|
||||
delete [] fi;
|
||||
delete [] fq;
|
||||
}
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// Run
|
||||
// passes a cmplx value (in) and receives the cmplx value (out)
|
||||
// function returns 0 if the filter is not yet stable
|
||||
// returns 1 when stable and decimated cmplx output value is valid
|
||||
//=====================================================================
|
||||
|
||||
int C_FIR_filter::run (const cmplx &in, cmplx &out) {
|
||||
ibuffer[pointer] = in.real();
|
||||
qbuffer[pointer] = in.imag();
|
||||
counter++;
|
||||
if (counter == decimateratio)
|
||||
out = cmplx ( mac(&ibuffer[pointer - length], ifilter, length),
|
||||
mac(&qbuffer[pointer - length], qfilter, length) );
|
||||
pointer++;
|
||||
if (pointer == FIRBufferLen) {
|
||||
/// memmove is necessary if length >= FIRBufferLen/2 , theoretically possible.
|
||||
memmove (ibuffer, ibuffer + FIRBufferLen - length, length * sizeof (double) );
|
||||
memmove (qbuffer, qbuffer + FIRBufferLen - length, length * sizeof (double) );
|
||||
pointer = length;
|
||||
}
|
||||
if (counter == decimateratio) {
|
||||
counter = 0;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//=====================================================================
|
||||
// Run the filter for the Real part of the cmplx variable
|
||||
//=====================================================================
|
||||
|
||||
int C_FIR_filter::Irun (const double &in, double &out) {
|
||||
double *iptr = ibuffer + pointer;
|
||||
|
||||
pointer++;
|
||||
counter++;
|
||||
|
||||
*iptr = in;
|
||||
|
||||
if (counter == decimateratio) {
|
||||
out = mac(iptr - length, ifilter, length);
|
||||
}
|
||||
|
||||
if (pointer == FIRBufferLen) {
|
||||
iptr = ibuffer + FIRBufferLen - length;
|
||||
memcpy(ibuffer, iptr, length * sizeof(double));
|
||||
pointer = length;
|
||||
}
|
||||
|
||||
if (counter == decimateratio) {
|
||||
counter = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//=====================================================================
|
||||
// Run the filter for the Imaginary part of the cmplx variable
|
||||
//=====================================================================
|
||||
|
||||
int C_FIR_filter::Qrun (const double &in, double &out) {
|
||||
double *qptr = ibuffer + pointer;
|
||||
|
||||
pointer++;
|
||||
counter++;
|
||||
|
||||
*qptr = in;
|
||||
|
||||
if (counter == decimateratio) {
|
||||
out = mac(qptr - length, qfilter, length);
|
||||
}
|
||||
|
||||
if (pointer == FIRBufferLen) {
|
||||
qptr = qbuffer + FIRBufferLen - length;
|
||||
memcpy(qbuffer, qptr, length * sizeof(double));
|
||||
pointer = length;
|
||||
}
|
||||
|
||||
if (counter == decimateratio) {
|
||||
counter = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// Moving average filter
|
||||
//
|
||||
// Simple in concept, sublime in implementation ... the fastest filter
|
||||
// in the west. Also optimal for the processing of time domain signals
|
||||
// characterized by a transition edge. The is the perfect signal filter
|
||||
// for CW, RTTY and other signals of that type. For a given filter size
|
||||
// it provides the greatest s/n improvement while retaining the sharpest
|
||||
// leading edge on the filtered signal.
|
||||
//=====================================================================
|
||||
|
||||
Cmovavg::Cmovavg (int filtlen)
|
||||
{
|
||||
len = filtlen;
|
||||
in = new double[len];
|
||||
empty = true;
|
||||
}
|
||||
|
||||
Cmovavg::~Cmovavg()
|
||||
{
|
||||
if (in) delete [] in;
|
||||
}
|
||||
|
||||
double Cmovavg::run(double a)
|
||||
{
|
||||
if (!in) {
|
||||
return a;
|
||||
}
|
||||
if (empty) {
|
||||
empty = false;
|
||||
out = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
in[i] = a;
|
||||
out += a;
|
||||
}
|
||||
pint = 0;
|
||||
return a;
|
||||
}
|
||||
out = out - in[pint] + a;
|
||||
in[pint] = a;
|
||||
if (++pint >= len) pint = 0;
|
||||
return out / len;
|
||||
}
|
||||
|
||||
void Cmovavg::setLength(int filtlen)
|
||||
{
|
||||
if (filtlen > len) {
|
||||
if (in) delete [] in;
|
||||
in = new double[filtlen];
|
||||
}
|
||||
len = filtlen;
|
||||
empty = true;
|
||||
}
|
||||
|
||||
void Cmovavg::reset()
|
||||
{
|
||||
empty = true;
|
||||
}
|
||||
|
||||
//=====================================================================
|
||||
// Sliding FFT filter
|
||||
// Sliding Fast Fourier Transform
|
||||
//
|
||||
// The sliding FFT ingeniously exploits the properties of a time-delayed
|
||||
// input and the property of linearity for its derivation.
|
||||
//
|
||||
// First of all, the N-point transform of a sequence x(n) is equal to the
|
||||
// summation of the transforms of N separate transforms where each transform
|
||||
// has just one of the original samples at it's original sample time.
|
||||
//
|
||||
//i.e.
|
||||
// transform of [x0, x1, x2, x3, x4, x5,...xN-1]
|
||||
// is equal to
|
||||
// transform of [x0, 0, 0, 0, 0, 0,...0]
|
||||
// + transform of [0, x1, 0, 0, 0, 0,...0]
|
||||
// + transform of [0, 0, x2, 0, 0, 0,...0]
|
||||
// + transform of [0, 0, 0, x3, 0, 0,...0]
|
||||
// .
|
||||
// .
|
||||
// .
|
||||
// + transform of [0, 0, 0, 0, 0, 0,...xN-1]
|
||||
//
|
||||
// Secondly, the transform of a time-delayed sequence is a phase-rotated
|
||||
// version of the transform of the original sequence. i.e.
|
||||
//
|
||||
// If x(n) transforms to X(k),
|
||||
// Then x(n-m) transforms to X(k)(Wn)^(-mk)
|
||||
//
|
||||
// where N is the FFT size, m is the delay in sample periods, and WN is the
|
||||
// familiar phase-rotating coefficient or twiddle factor e^(-j2p/N)
|
||||
//
|
||||
// Therefore, if the N-point transform X(k) of an individual sample is considered,
|
||||
// and then the sample is moved back in time by one sample period, all frequency
|
||||
// bins of X(k) are phase-rotated by 2pk/N radians.
|
||||
//
|
||||
// The important thing here is that the transform is not performed again because
|
||||
// the previous frequency results can be used by simply application of the correct
|
||||
// coefficients.
|
||||
//
|
||||
// This is the technique that is applied when the rectangular sampling window
|
||||
// slides along by one sample. The contributions of all samples that are
|
||||
// included in both the original and the new windows are simply phase rotated.
|
||||
// The end effects are that the transform of the new sample must be added, and
|
||||
// the transform of the oldest sample that disappeared off the end must be
|
||||
// subtracted. These end-effects are easy to perform if we treat the new sample
|
||||
// as occurring at time t = 0, because the transform of a single sample at t = 0,
|
||||
// say (a + bj), simply has all frequency bins equal to (a + bj). Similarly, the
|
||||
// oldest sample that has just disappeared off the end of the window is exactly N
|
||||
// samples old. I.e. it occurred at t = -N. The transform of this sample,
|
||||
// say (c + dj), is also straightforward since every frequency bin has now been
|
||||
// phase-rotated an integer number of times from when the sample was at t = 0.
|
||||
// (The kth frequency bin has been rotated by 2pk radians). The transform of the
|
||||
// sample at t = -N is therefore the same as if it was still at t = 0. I.e. it
|
||||
// has all frequency bins equal to (c + dj).
|
||||
//
|
||||
// All that is needed therefore is to
|
||||
// phase rotate each frequency bin in F(k) by WN^(k) and then
|
||||
// add [(a + bj) + (c + dj)] to each frequency bin.
|
||||
//
|
||||
// One cmplx multiplication and two cmplx additions per frequency bin are
|
||||
// therefore required, per sample period, regardless of the size of the transform.
|
||||
//
|
||||
// For example, a traditional 1024-point FFT needs 5120 cmplx multiplies
|
||||
// and 10240 cmplx additions to calculate all 1024 frequency bins. A 1024-point
|
||||
// Sliding FFT however needs 1024 cmplx multiplies and 2048 cmplx additions
|
||||
// for all 1024 frequency bins, and as each frequency bin is calculated separately,
|
||||
// it is only necessary to calculate the ones that are of interest.
|
||||
//
|
||||
// One drawback of the Sliding FFT is that in using feedback from previous
|
||||
// frequency bins, there is potential for instability if the coefficients are not
|
||||
// infinitely precise. Without infinite precision, stability can be guaranteed by
|
||||
// making each phase-rotation coefficient have a magnitude of slightly less than
|
||||
// unity. E.g. 0.9999.
|
||||
//
|
||||
// This then has to taken into account when the Nth sample is subtracted, because
|
||||
// the factor 0.9999 has been applied N times to the transform of this sample.
|
||||
// The sample cannot therefore be directly subtracted, it must first be multiplied
|
||||
// by the factor of 0.9999^N. This unfortunately means there is another multipli-
|
||||
// cation to perform per frequency bin. Another drawback is that a circular buffer
|
||||
// is needed in which to keep N samples, so that the oldest sample, (from t= -N),
|
||||
// can be subtracted each time.
|
||||
//
|
||||
// This filter is ideal for extracting a finite number of frequency bins
|
||||
// with a very long kernel length. The filter only needs to calculate the
|
||||
// values for the bins of interest and not the entire spectrum. It does
|
||||
// require the store of the history associated with those bins over the
|
||||
// kernel length.
|
||||
//
|
||||
// Use in the MFSK / DOMINO modem for extraction of the frequency spectra
|
||||
//
|
||||
//=====================================================================
|
||||
|
||||
struct sfft::vrot_bins_pair {
|
||||
cmplx vrot;
|
||||
cmplx bins;
|
||||
} ;
|
||||
|
||||
sfft::sfft(int len, int _first, int _last)
|
||||
{
|
||||
vrot_bins = new vrot_bins_pair[len];
|
||||
delay = new cmplx[len];
|
||||
fftlen = len;
|
||||
first = _first;
|
||||
last = _last;
|
||||
ptr = 0;
|
||||
double phi = 0.0, tau = 2.0 * M_PI/ len;
|
||||
k2 = 1.0;
|
||||
for (int i = 0; i < fftlen; i++) {
|
||||
vrot_bins[i].vrot = cmplx( K1 * cos (phi), K1 * sin (phi) );
|
||||
phi += tau;
|
||||
delay[i] = vrot_bins[i].bins = 0.0;
|
||||
k2 *= K1;
|
||||
}
|
||||
count = 0;
|
||||
}
|
||||
|
||||
sfft::~sfft()
|
||||
{
|
||||
delete [] vrot_bins;
|
||||
delete [] delay;
|
||||
}
|
||||
|
||||
void sfft::reset()
|
||||
{
|
||||
for (int i = 0; i < fftlen; i++) delay[i] = vrot_bins[i].bins = 0.0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
bool sfft::is_stable()
|
||||
{
|
||||
return (count >= fftlen);
|
||||
}
|
||||
|
||||
// Sliding FFT, cmplx input, cmplx output
|
||||
// FFT is computed for each value from first to last
|
||||
// Values are not stable until more than "len" samples have been processed.
|
||||
// Copies the frequencies to a pointer with a given stride.
|
||||
void sfft::run(const cmplx& input, cmplx * __restrict__ result, int stride )
|
||||
{
|
||||
cmplx & de = delay[ptr];
|
||||
const cmplx z( input.real() - k2 * de.real(), input.imag() - k2 * de.imag());
|
||||
de = input;
|
||||
|
||||
++ptr ;
|
||||
if( ptr >= fftlen ) ptr = 0 ;
|
||||
|
||||
// It is more efficient to have vrot and bins very close to each other.
|
||||
for( vrot_bins_pair
|
||||
* __restrict__ itr = vrot_bins + first,
|
||||
* __restrict__ end = vrot_bins + last ;
|
||||
itr != end ;
|
||||
++itr, result += stride ) {
|
||||
*result = itr->bins = itr->bins * itr->vrot + z * itr->vrot;
|
||||
}
|
||||
if (count < fftlen) count++;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Goertzel filter
|
||||
// Optimized implementation of a DFT for a single frequency of interest
|
||||
// SR = sample rate
|
||||
// N = Block size (does not need to be a factor of 2!)
|
||||
// bin size = SR / N
|
||||
// K = frequency bin of interest = (N * freq / SR)
|
||||
// N should be selected to make K an integer if possible
|
||||
//
|
||||
// Q0 = current sample
|
||||
// Q1 = previous sample (1 delay)
|
||||
// Q2 = previous sample (2 delay)
|
||||
// w = (2 * pi * K / N)
|
||||
// k1 = cos(w)
|
||||
// k2 = sin(w)
|
||||
// k3 = 2.0 * k1
|
||||
|
||||
// Q0, Q1, Q2 are initialized to zero
|
||||
// Iterate N times:
|
||||
// Q0 = k3*Q1 - Q2 + sample
|
||||
// Q2 = Q1
|
||||
// Q1 = Q0
|
||||
//
|
||||
// After N interations:
|
||||
// real = (Q1 - Q2 * k1)
|
||||
// imag = Q2 * k2
|
||||
// or
|
||||
// mag = Q1*Q1 + Q2*Q2 - Q1*Q2*k1
|
||||
// ============================================================================
|
||||
|
||||
goertzel::goertzel(int n, double freq, double sr)
|
||||
{
|
||||
double w;
|
||||
w = 2 * M_PI * freq / sr;
|
||||
k1 = cos(w);
|
||||
k2 = sin(w);
|
||||
k3 = 2.0 * k1;
|
||||
Q0 = Q1 = Q2 = 0.0;
|
||||
count = N = n;
|
||||
}
|
||||
|
||||
goertzel::~goertzel()
|
||||
{
|
||||
}
|
||||
|
||||
void goertzel::reset()
|
||||
{
|
||||
Q0 = Q1 = Q2 = 0.0;
|
||||
count = N;
|
||||
}
|
||||
|
||||
void goertzel::reset(int n, double freq, double sr)
|
||||
{
|
||||
double w;
|
||||
w = 2 * M_PI * freq / sr;
|
||||
k1 = cos(w);
|
||||
k2 = sin(w);
|
||||
k3 = 2.0 * k1;
|
||||
Q0 = Q1 = Q2 = 0.0;
|
||||
count = N = n;
|
||||
}
|
||||
|
||||
bool goertzel::run(double sample)
|
||||
{
|
||||
Q0 = sample + k3*Q1 - Q2;
|
||||
Q2 = Q1;
|
||||
Q1 = Q0;
|
||||
if (count) { --count; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
double goertzel::real()
|
||||
{
|
||||
return ((0.5*k3*Q1 - Q2)/N);
|
||||
}
|
||||
|
||||
double goertzel::imag()
|
||||
{
|
||||
return ((k2*Q1)/N);
|
||||
}
|
||||
|
||||
double goertzel::mag()
|
||||
{
|
||||
return (Q2*Q2 + Q1*Q1 - k3*Q2*Q1);
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// filters.h -- Several Digital Filter classes used in fldigi
|
||||
//
|
||||
// Copyright (C) 2006-2008
|
||||
// Dave Freese, W1HKJ
|
||||
//
|
||||
// This file is part of fldigi. These filters are based on the
|
||||
// gmfsk design and the design notes given in
|
||||
// "Digital Signal Processing", A Practical Guid for Engineers and Scientists
|
||||
// by Steven W. Smith.
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#ifndef _FILTER_H
|
||||
#define _FILTER_H
|
||||
|
||||
#include "complex.h"
|
||||
#ifndef M_PI
|
||||
#define M_PI (3.1415926535897932385)
|
||||
#endif
|
||||
|
||||
//=====================================================================
|
||||
// FIR filters
|
||||
//=====================================================================
|
||||
|
||||
class C_FIR_filter {
|
||||
#define FIRBufferLen 4096
|
||||
private:
|
||||
int length;
|
||||
int decimateratio;
|
||||
|
||||
double *ifilter;
|
||||
double *qfilter;
|
||||
|
||||
double ffreq;
|
||||
|
||||
double ibuffer[FIRBufferLen];
|
||||
double qbuffer[FIRBufferLen];
|
||||
|
||||
int pointer;
|
||||
int counter;
|
||||
|
||||
cmplx fu;
|
||||
|
||||
inline double sinc(double x) {
|
||||
if (fabs(x) < 1e-10)
|
||||
return 1.0;
|
||||
else
|
||||
return sin(M_PI * x) / (M_PI * x);
|
||||
}
|
||||
inline double cosc(double x) {
|
||||
if (fabs(x) < 1e-10)
|
||||
return 0.0;
|
||||
else
|
||||
return (1.0 - cos(M_PI * x)) / (M_PI * x);
|
||||
}
|
||||
inline double hamming(double x) {
|
||||
return 0.54 - 0.46 * cos(2 * M_PI * x);
|
||||
}
|
||||
inline double mac(const double *a, const double *b, unsigned int size) {
|
||||
double sum = 0.0;
|
||||
double sum2 = 0.0;
|
||||
double sum3 = 0.0;
|
||||
double sum4 = 0.0;
|
||||
// Reduces read-after-write dependencies : Each subsum does not wait for the others.
|
||||
// The CPU can therefore schedule each line independently.
|
||||
for (; size > 3; size -= 4, a += 4, b+=4)
|
||||
{
|
||||
sum += a[0] * b[0];
|
||||
sum2 += a[1] * b[1];
|
||||
sum3 += a[2] * b[2];
|
||||
sum4 += a[3] * b[3];
|
||||
}
|
||||
for (; size; --size)
|
||||
sum += (*a++) * (*b++);
|
||||
return sum + sum2 + sum3 + sum4 ;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
public:
|
||||
C_FIR_filter ();
|
||||
~C_FIR_filter ();
|
||||
|
||||
void init (int len, int dec, double *ifil, double *qfil);
|
||||
void init_lowpass (int len, int dec, double freq );
|
||||
void init_bandpass (int len, int dec, double freq1, double freq2);
|
||||
void init_hilbert (int len, int dec);
|
||||
double *bp_FIR(int len, int hilbert, double f1, double f2);
|
||||
|
||||
void clear();
|
||||
int run (const cmplx &in, cmplx &out);
|
||||
int Irun (const double &in, double &out);
|
||||
int Qrun (const double &in, double &out);
|
||||
};
|
||||
|
||||
//=====================================================================
|
||||
// Moving average filter
|
||||
//=====================================================================
|
||||
|
||||
class Cmovavg {
|
||||
#define MAXMOVAVG 2048
|
||||
private:
|
||||
double *in;
|
||||
double out;
|
||||
int len, pint;
|
||||
bool empty;
|
||||
public:
|
||||
Cmovavg(int filtlen = 64);
|
||||
~Cmovavg();
|
||||
double run(double a);
|
||||
void setLength(int filtlen);
|
||||
void reset();
|
||||
double value() { return out / (len > 0 ? len : 1); }
|
||||
};
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// Sliding FFT
|
||||
//=====================================================================
|
||||
|
||||
class sfft {
|
||||
#define K1 0.99999999999L
|
||||
private:
|
||||
int fftlen;
|
||||
int first;
|
||||
int last;
|
||||
int ptr;
|
||||
struct vrot_bins_pair ;
|
||||
vrot_bins_pair * __restrict__ vrot_bins ;
|
||||
cmplx * __restrict__ delay;
|
||||
double k2;
|
||||
int count;
|
||||
public:
|
||||
sfft(int len, int first, int last);
|
||||
~sfft();
|
||||
bool is_stable();
|
||||
void reset();
|
||||
void run(const cmplx& input, cmplx * __restrict__ result, int stride );
|
||||
};
|
||||
|
||||
|
||||
|
||||
//=============================================================================
|
||||
// Goertzel DFT
|
||||
//=============================================================================
|
||||
|
||||
class goertzel {
|
||||
private:
|
||||
int N;
|
||||
int count;
|
||||
double Q0;
|
||||
double Q1;
|
||||
double Q2;
|
||||
double k1;
|
||||
double k2;
|
||||
double k3;
|
||||
public:
|
||||
goertzel(int n, double freq, double sr);
|
||||
~goertzel();
|
||||
void reset();
|
||||
void reset(int n, double freq, double sr);
|
||||
bool run(double v);
|
||||
double real();
|
||||
double imag();
|
||||
double mag();
|
||||
};
|
||||
|
||||
#endif /* _FILTER_H */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,258 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// mbuffer.h
|
||||
//
|
||||
// Copyright (C) 2007
|
||||
// Stelios Bounanos, M0GLD
|
||||
//
|
||||
// This file is part of fldigi.
|
||||
//
|
||||
// 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
// A simple vector wrapper for fldigi's double-buffering needs.
|
||||
// Most vector operations are provided for mbuffers by redirecting them to the
|
||||
// current vector.
|
||||
|
||||
// The template arguments are
|
||||
// 1) T - the type
|
||||
// 2) S - the apparent mbuffer size, i.e., the size of each vector. Defaults to 0.
|
||||
// A mbuffer instantiated with S == 0 is not very useful until resized
|
||||
// with alloc().
|
||||
// 3) N - the number of vectors that we can cycle between. Defaults to 1.
|
||||
|
||||
// Things to note:
|
||||
// 1) There is a T* conversion operator
|
||||
// 2) Operations that modify the length of the container are not provided
|
||||
// 3) Comparison operators are not implemented (but see (1)!)
|
||||
// 4) mbuffer<T, 0, N> is meant to be used when we don't know the size at
|
||||
// compile time, in which case we resize with alloc. The compiler will treat
|
||||
// mbuffers resized to different lengths in this way as objects of the same type.
|
||||
|
||||
#ifndef MBUFFER_H
|
||||
#define MBUFFER_H
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include <iosfwd>
|
||||
#include <iterator>
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
template <typename T, std::size_t S, std::size_t N>
|
||||
class mbuffer;
|
||||
template <typename T, std::size_t S, std::size_t N>
|
||||
std::ostream& operator<<(std::ostream& o, const mbuffer<T, S, N>& b);
|
||||
#endif // NDEBUG
|
||||
|
||||
template <typename T, std::size_t S = 0, std::size_t N = 1>
|
||||
class mbuffer
|
||||
{
|
||||
protected:
|
||||
std::vector<T> data[N];
|
||||
mutable std::size_t cur;
|
||||
|
||||
public:
|
||||
explicit mbuffer(void)
|
||||
{
|
||||
alloc(S);
|
||||
}
|
||||
explicit mbuffer(std::size_t n)
|
||||
{
|
||||
alloc(n);
|
||||
}
|
||||
mbuffer(const std::vector<T>& v)
|
||||
{
|
||||
data[0] = v;
|
||||
// resize 1 to N only
|
||||
alloc(data[0].size(), 1);
|
||||
}
|
||||
mbuffer(const T* a, std::size_t n)
|
||||
{
|
||||
data[0].assign(a, a + n);
|
||||
// resize 1 to N only
|
||||
alloc(n, 1);
|
||||
}
|
||||
void alloc(std::size_t n, std::size_t start = 0)
|
||||
{
|
||||
for (size_t i = start; i < N; ++i)
|
||||
data[i].resize(n);
|
||||
cur = 0;
|
||||
}
|
||||
|
||||
typedef typename std::vector<T>::iterator iterator;
|
||||
typedef typename std::vector<T>::const_iterator const_iterator;
|
||||
|
||||
iterator begin(void) { return data[cur].begin(); }
|
||||
const_iterator begin(void) const { return data[cur].begin(); }
|
||||
iterator end(void) { return data[cur].end(); }
|
||||
const_iterator end(void) const { return data[cur].end(); }
|
||||
|
||||
|
||||
typedef typename std::vector<T>::reverse_iterator reverse_iterator;
|
||||
typedef typename std::vector<T>::const_reverse_iterator const_reverse_iterator;
|
||||
|
||||
reverse_iterator rbegin(void) { return reverse_iterator(end()); }
|
||||
const_reverse_iterator rbegin(void) const { return const_reverse_iterator(end()); }
|
||||
reverse_iterator rend(void) { return reverse_iterator(begin()); }
|
||||
const_reverse_iterator rend(void) const { return const_reverse_iterator(begin()); }
|
||||
|
||||
|
||||
typedef typename std::vector<T>::value_type value_type;
|
||||
typedef typename std::vector<T>::reference reference;
|
||||
typedef typename std::vector<T>::const_reference const_reference;
|
||||
typedef typename std::vector<T>::size_type size_type;
|
||||
typedef typename std::vector<T>::difference_type difference_type;
|
||||
|
||||
// These should be the same for all vectors in data[]
|
||||
size_type size(void) { return data[0].size(); }
|
||||
size_type max_size(void) { return data[0].max_size(); }
|
||||
size_type capacity(void) { return data[0].capacity(); }
|
||||
bool empty(void) { return data[0].empty(); }
|
||||
|
||||
// Instead of these, we provide a conversion operator for T*
|
||||
// reference operator[](size_type i) { return data[cur][i]; }
|
||||
// const_reference operator[](size_type i) const { return data[cur][i]; }
|
||||
reference at(size_type i) { return data[cur].at(i); }
|
||||
const_reference at(size_type i) const { return data[cur].at(i); }
|
||||
reference front(void) { return data[cur].front(); }
|
||||
const_reference front(void) const { return data[cur].front(); }
|
||||
reference back(void) { return data[cur].back(); }
|
||||
const_reference back(void) const { return data[cur].back(); }
|
||||
|
||||
|
||||
// Operations that modify the size of data[cur] might invalidate
|
||||
// pointers to internal buffers. The rest of the data vectors would need
|
||||
// to be resized, e.g. with check_size below. For this reason these
|
||||
// operations are not provided, but are included here for completeness
|
||||
|
||||
// void check_size(void)
|
||||
// {
|
||||
// for (size_t i = 0; i < N; ++i)
|
||||
// if (data[i].size() != data[cur].size())
|
||||
// data[i].resize(data[cur].size());
|
||||
// }
|
||||
|
||||
// mbuffer<T, S, N>& operator=(const mbuffer<T, S, N>& o)
|
||||
// {
|
||||
// for (int i = 0; i < N; ++i)
|
||||
// std::copy(o.data[i].begin(), o.data[i].end(), data[i].begin());
|
||||
// return *this;
|
||||
// }
|
||||
// std::vector<T>& operator=(const std::vector<T>& o)
|
||||
// {
|
||||
// std::copy(o.begin(), o.end(), data[cur].begin());
|
||||
// check_size();
|
||||
// return data[cur];
|
||||
// }
|
||||
|
||||
// The methods below would modify the length of the vector.
|
||||
// We would need to check_size() before returning from them.
|
||||
|
||||
// There is no vector::assign; the one provided here fills the vector
|
||||
// with copies of the same value without causing a resize.
|
||||
void assign(const_reference v) { std::fill_n(begin(), size(), v); }
|
||||
// template <typename input_iterator>
|
||||
// void assign(input_iterator first, input_iterator last)
|
||||
// {
|
||||
// data[cur].assign(first, last);
|
||||
// }
|
||||
|
||||
// void push_back(const_reference v) { data[cur].push_back(v); }
|
||||
// void pop_back(void) { data[cur].pop_back(); }
|
||||
|
||||
// iterator insert(iterator pos, const_reference v)
|
||||
// {
|
||||
// return data[cur].insert(pos, v);
|
||||
// }
|
||||
// iterator insert(iterator pos, size_type n, const_reference v)
|
||||
// {
|
||||
// return data[cur].insert(pos, n, v);
|
||||
// }
|
||||
// template <typename input_iterator>
|
||||
// void insert(iterator pos, input_iterator first, input_iterator last)
|
||||
// {
|
||||
// data[cur].insert(pos, first, last);
|
||||
// }
|
||||
|
||||
// iterator erase(iterator pos) { return data[cur].erase(pos); }
|
||||
// iterator erase(iterator first, iterator last) { return data[cur].erase(first, last); }
|
||||
|
||||
// void clear(void) { data[cur].clear(); }
|
||||
|
||||
void swap(mbuffer<T, S, N>& o)
|
||||
{
|
||||
for (int i = 0; i < N; ++i)
|
||||
std::swap(data[i].begin(), data[i].end(), o.data[i].begin());
|
||||
}
|
||||
// void swap(std::vector<T>& o)
|
||||
// {
|
||||
// std::swap(data[cur].begin(), data[cur].end(), o.begin());
|
||||
// check_size();
|
||||
// o.check_size();
|
||||
// }
|
||||
|
||||
|
||||
// and now for something completely different
|
||||
|
||||
void next(void) const { if (++cur == N) cur = 0; }
|
||||
void prev(void) const { if (cur > 0) --cur; }
|
||||
void reset(void) const { cur = 0; }
|
||||
|
||||
T* c_array(void) { return &data[cur][0]; }
|
||||
operator T*(void) { return c_array(); }
|
||||
const T* c_array(void) const { return &data[cur][0]; }
|
||||
operator const T*(void) const { return c_array(); }
|
||||
|
||||
std::vector<T>& vec(void) { return data[cur]; }
|
||||
const std::vector<T>& vec(void) const { return data[cur]; }
|
||||
// We also do not provide vector<T> conversions
|
||||
// operator std::vector<T>&(void) { return vec(); }
|
||||
// operator const std::vector<T>&(void) const { return vec(); }
|
||||
|
||||
std::size_t idx(void) { return cur; }
|
||||
std::size_t nvec(void) { return N; }
|
||||
std::vector<T>* vecp(std::size_t i) { return &data[i]; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
friend std::ostream& operator<<<>(std::ostream& o, const mbuffer<T, S, N>& b);
|
||||
#endif // NDEBUG
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
template <typename T, std::size_t S, std::size_t N>
|
||||
std::ostream& operator<<(std::ostream& o, const mbuffer<T, S, N>& b)
|
||||
{
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
o << '<' << i << ">\n";
|
||||
copy(b.data[i].begin(), b.data[i].end(),
|
||||
std::ostream_iterator<T>(o, "\n"));
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
template <typename T, std::size_t S, std::size_t N>
|
||||
inline
|
||||
void swap(mbuffer<T, S, N>& a, mbuffer<T, S, N>& b) { a.swap(b); }
|
||||
|
||||
#endif // MBUFFER_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// c-file-style: "linux"
|
||||
// End:
|
||||
@@ -1,160 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// modem.h -- minimal modem base class for fldigi CW decoder (Android)
|
||||
// Extracted from fldigi/src/include/modem.h (GPL v3).
|
||||
// ----------------------------------------------------------------------------
|
||||
#ifndef _MODEM_H
|
||||
#define _MODEM_H
|
||||
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include "morse.h"
|
||||
#include "filters.h"
|
||||
|
||||
#define OUTBUFSIZE 65536
|
||||
|
||||
enum trx_mode {
|
||||
MODE_CW = 0
|
||||
};
|
||||
|
||||
struct mode_info_t {
|
||||
const char *sname;
|
||||
unsigned int iface_io;
|
||||
};
|
||||
|
||||
namespace Digiscope {
|
||||
enum scope_mode { SCOPE, SCOPE2, PHASE, PHASE2, WATERFALL, NONE };
|
||||
}
|
||||
|
||||
class modem {
|
||||
public:
|
||||
static double frequency;
|
||||
static double tx_frequency;
|
||||
static bool freqlock;
|
||||
static unsigned long tx_sample_count;
|
||||
static unsigned int tx_sample_rate;
|
||||
static bool XMLRPC_CPS_TEST;
|
||||
|
||||
protected:
|
||||
cMorse *morse;
|
||||
trx_mode mode;
|
||||
bool stopflag;
|
||||
int fragmentsize;
|
||||
int samplerate;
|
||||
bool reverse;
|
||||
int sigsearch;
|
||||
bool sig_start;
|
||||
bool sig_stop;
|
||||
double bandwidth;
|
||||
double freqerr;
|
||||
double rx_corr;
|
||||
double tx_corr;
|
||||
double PTTphaseacc;
|
||||
double PTTchannel[OUTBUFSIZE];
|
||||
bool cwTrack;
|
||||
bool cwLock;
|
||||
double cwRcvWPM;
|
||||
double cwXmtWPM;
|
||||
double squelch;
|
||||
double metric;
|
||||
double syncpos;
|
||||
int backspaces;
|
||||
unsigned char *txstr;
|
||||
unsigned char *txptr;
|
||||
double outbuf[OUTBUFSIZE];
|
||||
bool historyON;
|
||||
Digiscope::scope_mode scopemode;
|
||||
int scptr;
|
||||
double s2n_ncount, s2n_sum, s2n_sum2, s2n_metric;
|
||||
bool s2n_valid;
|
||||
unsigned cap;
|
||||
std::string audio_filename;
|
||||
bool play_audio;
|
||||
bool CW_EOT;
|
||||
|
||||
public:
|
||||
modem();
|
||||
virtual ~modem() { delete morse; }
|
||||
virtual void init();
|
||||
virtual void tx_init() = 0;
|
||||
virtual void rx_init() = 0;
|
||||
virtual void restart() = 0;
|
||||
virtual void rx_flush() {}
|
||||
virtual int tx_process();
|
||||
virtual int rx_process(const double *, int len) = 0;
|
||||
virtual void Audio_filename(std::string nm) { audio_filename = nm; play_audio = true; }
|
||||
virtual void shutdown() {}
|
||||
virtual void set1(int, int) {}
|
||||
virtual void set2(int, int) {}
|
||||
virtual void makeTxViewer(int W, int H) {}
|
||||
virtual void searchDown() {}
|
||||
virtual void searchUp() {}
|
||||
void HistoryON(bool val) { historyON = val; }
|
||||
bool HistoryON() const { return historyON; }
|
||||
trx_mode get_mode() const { return mode; }
|
||||
const char *get_mode_name() const;
|
||||
unsigned int iface_io() const;
|
||||
virtual void set_freq(double);
|
||||
int get_freq() const { return (int)(frequency + 0.5); }
|
||||
void init_freqlock();
|
||||
void set_freqlock(bool);
|
||||
void set_sigsearch(int n) { sigsearch = n; freqerr = 0.0; }
|
||||
bool freqlocked() const { return freqlock; }
|
||||
double get_txfreq() const;
|
||||
double get_txfreq_woffset() const;
|
||||
void set_metric(double);
|
||||
void display_metric(double);
|
||||
double get_metric() const { return metric; }
|
||||
void set_reverse(bool on);
|
||||
bool get_reverse() const { return reverse; }
|
||||
double get_bandwidth() const { return bandwidth; }
|
||||
void set_bandwidth(double);
|
||||
int get_samplerate() const { return samplerate; }
|
||||
void set_samplerate(int);
|
||||
void init_queues();
|
||||
void ModulateXmtr(double *, int);
|
||||
void ModulateStereo(double *, double *, int, bool sample_flag = true);
|
||||
void ModulateVideo(double *, int);
|
||||
void ModulateVideoStereo(double *, double *, int, bool sample_flag = true);
|
||||
void videoText();
|
||||
void pretone();
|
||||
virtual void send_color_image(std::string) {}
|
||||
virtual void send_Grey_image(std::string) {}
|
||||
virtual void ifkp_send_image(std::string s = "", bool grey = false) {}
|
||||
virtual void ifkp_send_avatar() {}
|
||||
virtual void m_ifkp_send_avatar() {}
|
||||
virtual void thor_send_image(std::string s = "", bool grey = false) {}
|
||||
virtual void thor_send_avatar() {}
|
||||
virtual void m_thor_send_avatar() {}
|
||||
void set_stopflag(bool b) { stopflag = b; }
|
||||
bool get_stopflag() const { return stopflag; }
|
||||
unsigned get_cap() const { return cap; }
|
||||
enum { CAP_AFC = 1 << 0, CAP_AFC_SR = 1 << 1, CAP_REV = 1 << 2,
|
||||
CAP_IMG = 1 << 3, CAP_BW = 1 << 4, CAP_RX = 1 << 5,
|
||||
CAP_TX = 1 << 6 };
|
||||
|
||||
bool get_cwTrack();
|
||||
void set_cwTrack(bool);
|
||||
bool get_cwLock();
|
||||
void set_cwLock(bool);
|
||||
double get_cwXmtWPM();
|
||||
void set_cwXmtWPM(double);
|
||||
double get_cwRcvWPM();
|
||||
virtual void CW_KEYLINE(bool) {}
|
||||
virtual void incWPM() {}
|
||||
virtual void decWPM() {}
|
||||
virtual void toggleWPM() {}
|
||||
virtual void sync_parameters() {}
|
||||
virtual void reset_rx_filter() {}
|
||||
virtual void update_Status() {}
|
||||
virtual void refresh_scope() {}
|
||||
virtual void clear_viewer() {}
|
||||
virtual void clear_ch(int n) {}
|
||||
virtual int viewer_get_freq(int n) { return 0; }
|
||||
double calWPM() { return 20; }
|
||||
void calWPM(double) {}
|
||||
void resetFSK() {}
|
||||
void s2nreport() {}
|
||||
void set_scope_mode(Digiscope::scope_mode sm) { scopemode = sm; }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,314 +0,0 @@
|
||||
/*
|
||||
* morse.c -- morse code tables
|
||||
*
|
||||
* Copyright (C) 2017
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "morse.h"
|
||||
#include "configuration.h"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Morse code characters table. This table allows lookup of the Morse
|
||||
* shape of a given alphanumeric character. Shapes are held as a string,
|
||||
* with "-' representing dash, and ".' representing dot. The table ends
|
||||
* with a NULL entry.
|
||||
*
|
||||
* This is the main table from which the other tables are computed.
|
||||
*
|
||||
* The Prosigns are also defined in the configuration.h file
|
||||
* The user can specify the character which substitutes for the prosign
|
||||
*/
|
||||
|
||||
bool CW_table_changed = false;
|
||||
|
||||
CWstruct cMorse::cw_table[] = {
|
||||
// Prosigns
|
||||
{1, "=", "<BT>", "-...-" }, // 0
|
||||
{0, "~", "<AA>", ".-.-" }, // 1
|
||||
{1, "<", "<AS>", ".-..." }, // 2
|
||||
{1, ">", "<AR>", ".-.-." }, // 3
|
||||
{1, "%", "<SK>", "...-.-" }, // 4
|
||||
{1, "+", "<KN>", "-.--." }, // 5
|
||||
{1, "&", "<INT>", "..-.-" }, // 6
|
||||
{1, "{", "<HM>", "....--" }, // 7
|
||||
{1, "}", "<VE>", "...-." }, // 8
|
||||
// ASCII 7bit letters
|
||||
{1, "A", "A", ".-" },
|
||||
{1, "B", "B", "-..." },
|
||||
{1, "C", "C", "-.-." },
|
||||
{1, "D", "D", "-.." },
|
||||
{1, "E", "E", "." },
|
||||
{1, "F", "F", "..-." },
|
||||
{1, "G", "G", "--." },
|
||||
{1, "H", "H", "...." },
|
||||
{1, "I", "I", ".." },
|
||||
{1, "J", "J", ".---" },
|
||||
{1, "K", "K", "-.-" },
|
||||
{1, "L", "L", ".-.." },
|
||||
{1, "M", "M", "--" },
|
||||
{1, "N", "N", "-." },
|
||||
{1, "O", "O", "---" },
|
||||
{1, "P", "P", ".--." },
|
||||
{1, "Q", "Q", "--.-" },
|
||||
{1, "R", "R", ".-." },
|
||||
{1, "S", "S", "..." },
|
||||
{1, "T", "T", "-" },
|
||||
{1, "U", "U", "..-" },
|
||||
{1, "V", "V", "...-" },
|
||||
{1, "W", "W", ".--" },
|
||||
{1, "X", "X", "-..-" },
|
||||
{1, "Y", "Y", "-.--" },
|
||||
{1, "Z", "Z", "--.." },
|
||||
//
|
||||
{1, "a", "A", ".-" },
|
||||
{1, "b", "B", "-..." },
|
||||
{1, "c", "C", "-.-." },
|
||||
{1, "d", "D", "-.." },
|
||||
{1, "e", "E", "." },
|
||||
{1, "f", "F", "..-." },
|
||||
{1, "g", "G", "--." },
|
||||
{1, "h", "H", "...." },
|
||||
{1, "i", "I", ".." },
|
||||
{1, "j", "J", ".---" },
|
||||
{1, "k", "K", "-.-" },
|
||||
{1, "l", "L", ".-.." },
|
||||
{1, "m", "M", "--" },
|
||||
{1, "n", "N", "-." },
|
||||
{1, "o", "O", "---" },
|
||||
{1, "p", "P", ".--." },
|
||||
{1, "q", "Q", "--.-" },
|
||||
{1, "r", "R", ".-." },
|
||||
{1, "s", "S", "..." },
|
||||
{1, "t", "T", "-" },
|
||||
{1, "u", "U", "..-" },
|
||||
{1, "v", "V", "...-" },
|
||||
{1, "w", "W", ".--" },
|
||||
{1, "x", "X", "-..-" },
|
||||
{1, "y", "Y", "-.--" },
|
||||
{1, "z", "Z", "--.." },
|
||||
// Numerals
|
||||
{1, "0", "0", "-----" },
|
||||
{1, "1", "1", ".----" },
|
||||
{1, "2", "2", "..---" },
|
||||
{1, "3", "3", "...--" },
|
||||
{1, "4", "4", "....-" },
|
||||
{1, "5", "5", "....." },
|
||||
{1, "6", "6", "-...." },
|
||||
{1, "7", "7", "--..." },
|
||||
{1, "8", "8", "---.." },
|
||||
{1, "9", "9", "----." },
|
||||
// Punctuation
|
||||
{1, "\\", "\\", ".-..-." },
|
||||
{1, "\'", "'", ".----." },
|
||||
{1, "$", "$", "...-..-" },
|
||||
{1, "(", "(", "-.--." },
|
||||
{1, ")", ")", "-.--.-" },
|
||||
{1, ",", ",", "--..--" },
|
||||
{1, "-", "-", "-....-" },
|
||||
{1, ".", ".", ".-.-.-" },
|
||||
{1, "/", "/", "-..-." },
|
||||
{1, ":", ":", "---..." },
|
||||
{1, ";", ";", "-.-.-." },
|
||||
{1, "?", "?", "..--.." },
|
||||
{1, "_", "_", "..--.-" },
|
||||
{1, "@", "@", ".--.-." },
|
||||
{1, "!", "!", "-.-.--" },
|
||||
// accented characters
|
||||
{1, "Ä", "Ä", ".-.-" }, // A umlaut
|
||||
{1, "ä", "Ä", ".-.-" }, // A umlaut
|
||||
{0, "Æ", "Æ", ".-.-" }, // A aelig
|
||||
{0, "æ", "Æ", ".-.-" }, // A aelig
|
||||
{0, "Å", "Å", ".--.-" }, // A ring
|
||||
{0, "å", "Å", ".--.-" }, // A ring
|
||||
{1, "Ç", "Ç", "-.-.." }, // C cedilla
|
||||
{1, "ç", "Ç", "-.-.." }, // C cedilla
|
||||
{0, "È", "È", ".-..-" }, // E grave
|
||||
{0, "è", "È", ".-..-" }, // E grave
|
||||
{1, "É", "É", "..-.." }, // E acute
|
||||
{1, "é", "É", "..-.." }, // E acute
|
||||
{0, "Ó", "Ó", "---." }, // O acute
|
||||
{0, "ó", "Ó", "---." }, // O acute
|
||||
{1, "Ö", "Ö", "---." }, // O umlaut
|
||||
{1, "ö", "Ö", "---." }, // O umlaut
|
||||
{0, "Ø", "Ø", "---." }, // O slash
|
||||
{0, "ø", "Ø", "---." }, // O slash
|
||||
{1, "Ñ", "Ñ", "--.--" }, // N tilde
|
||||
{1, "ñ", "Ñ", "--.--" }, // N tilde
|
||||
{1, "Ü", "Ü", "..--" }, // U umlaut
|
||||
{1, "ü", "Ü", "..--" }, // U umlaut
|
||||
{0, "Û", "Û", "..--" }, // U circ
|
||||
{0, "û", "Û", "..--" }, // U circ
|
||||
// array termination
|
||||
{0, "", "", ""}
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
void cMorse::enable(std::string s, bool val)
|
||||
{
|
||||
for (int i = 0; cw_table[i].rpr.length(); i++) {
|
||||
if (cw_table[i].chr == s || cw_table[i].prt == s) {
|
||||
cw_table[i].enabled = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cMorse::init()
|
||||
{
|
||||
// Update the char / prosign relationship
|
||||
if (progdefaults.CW_prosigns.length() == 9) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
cw_table[i].chr = progdefaults.CW_prosigns[i];
|
||||
}
|
||||
}
|
||||
enable("<AA>", 1);
|
||||
enable("Ä", 0); enable("ä", 0);
|
||||
enable("Æ", 0); enable("æ", 0);
|
||||
enable("Å", 0); enable("å", 0);
|
||||
enable("Ç", 0); enable("ç", 0);
|
||||
enable("È", 0); enable("è", 0);
|
||||
enable("É", 0); enable("é", 0);
|
||||
enable("Ó", 0); enable("ó", 0);
|
||||
enable("Ö", 0); enable("ö", 0);
|
||||
enable("Ø", 0); enable("ø", 0);
|
||||
enable("Ñ", 0); enable("ñ", 0);
|
||||
enable("Ü", 0); enable("ü", 0);
|
||||
enable("Û", 0); enable("û", 0);
|
||||
|
||||
if (progdefaults.A_umlaut)
|
||||
{ enable("Ä", 1); enable("ä", 1); enable("<AA>", 0); }
|
||||
if (progdefaults.A_aelig)
|
||||
{ enable("Æ", 1); enable("æ", 1); enable("<AA>", 0); }
|
||||
if (progdefaults.A_ring)
|
||||
{ enable("Å", 1); enable("å", 1); }
|
||||
if (progdefaults.C_cedilla)
|
||||
{ enable("Ç", 1); enable("ç", 1); }
|
||||
if (progdefaults.E_grave)
|
||||
{ enable("È", 1); enable("è", 1); }
|
||||
if (progdefaults.E_acute)
|
||||
{ enable("É", 1); enable("é", 1); }
|
||||
if (progdefaults.O_acute)
|
||||
{ enable("Ó", 1); enable("ó", 1); }
|
||||
if (progdefaults.O_umlaut)
|
||||
{ enable("Ö", 1); enable("ö", 1); }
|
||||
if (progdefaults.O_slash)
|
||||
{ enable("Ø", 1); enable("ø", 1); }
|
||||
if (progdefaults.N_tilde)
|
||||
{ enable("Ñ", 1); enable("ñ", 1); }
|
||||
if (progdefaults.U_umlaut)
|
||||
{ enable("Ü", 1); enable("ü", 1); }
|
||||
if (progdefaults.U_circ)
|
||||
{ enable("Û", 1); enable("û", 1); }
|
||||
|
||||
enable ("\\", progdefaults.CW_backslash);
|
||||
enable ("\'", progdefaults.CW_single_quote);
|
||||
enable ("$", progdefaults.CW_dollar_sign);
|
||||
enable ("(", progdefaults.CW_open_paren);
|
||||
enable (")", progdefaults.CW_close_paren);
|
||||
enable (":", progdefaults.CW_colon);
|
||||
enable (";", progdefaults.CW_semi_colon);
|
||||
enable ("_", progdefaults.CW_underscore);
|
||||
enable ("@", progdefaults.CW_at_symbol);
|
||||
enable ("!", progdefaults.CW_exclamation);
|
||||
|
||||
CW_table_changed = false;
|
||||
utf8.reserve(4);
|
||||
utf8.clear();
|
||||
ptr = 0;
|
||||
toprint.clear();
|
||||
}
|
||||
|
||||
std::string cMorse::rx_lookup(std::string rx)
|
||||
{
|
||||
if (CW_table_changed) init();
|
||||
for (int i = 0; cw_table[i].rpr.length(); i++) {
|
||||
if (rx == cw_table[i].rpr) {
|
||||
if (cw_table[i].enabled) {
|
||||
if (progdefaults.CW_prosign_display)
|
||||
return cw_table[i].chr;
|
||||
return cw_table[i].prt;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string cMorse::tx_lookup(int c)
|
||||
{
|
||||
if (CW_table_changed) init();
|
||||
toprint.clear();
|
||||
|
||||
c &= 0xFF;
|
||||
utf8 += c;
|
||||
// if (ptr < 4) utf8[ptr++] = c;
|
||||
|
||||
// if ( c > 0x7F && ptr == 1 )
|
||||
// return "";
|
||||
if (((utf8[0] & 0xFF) > 0x7F) && (utf8.length() == 1)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
for (int i = 0; cw_table[i].rpr.length(); i++) {
|
||||
if (utf8 == cw_table[i].chr) {
|
||||
if (!cw_table[i].enabled) {
|
||||
utf8.clear();
|
||||
ptr = 0;
|
||||
return "";
|
||||
}
|
||||
toprint = cw_table[i].prt;
|
||||
utf8.clear();
|
||||
ptr = 0;
|
||||
return cw_table[i].rpr;
|
||||
}
|
||||
}
|
||||
utf8.clear();
|
||||
ptr = 0;
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Morse Code timing rules
|
||||
The length of a dot is 1 time unit.
|
||||
A dash is 3 time units.
|
||||
The space between symbols (dots and dashes) of the same letter is 1 time unit.
|
||||
The space between letters is 3 time units.
|
||||
The space between words is 7 time units.
|
||||
*/
|
||||
int cMorse::tx_length(int c)
|
||||
{
|
||||
if (c == ' ') return 4;
|
||||
std::string ms = tx_lookup(c);
|
||||
if (ms.empty()) return 0;
|
||||
int len = 0;
|
||||
for (size_t i = 0; i < ms.length(); i++)
|
||||
if (ms[i] == '.') len += 2;
|
||||
else len += 4;
|
||||
len += 2;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* morse.h -- morse code tables
|
||||
*
|
||||
* Copyright (C) 2017
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MORSE_H
|
||||
#define _MORSE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#define MorseTableSize 256
|
||||
|
||||
#define CW_DOT_REPRESENTATION '.'
|
||||
#define CW_DASH_REPRESENTATION '-'
|
||||
|
||||
struct CWstruct {
|
||||
bool enabled; // true if character is active
|
||||
std::string chr; // utf-8 string representation of character
|
||||
std::string prt; // utf-8 printable representation
|
||||
std::string rpr; // Dot-dash code representation
|
||||
};
|
||||
|
||||
class cMorse {
|
||||
private:
|
||||
static CWstruct cw_table[];
|
||||
std::string utf8;
|
||||
std::string toprint;
|
||||
int ptr;
|
||||
public:
|
||||
cMorse() {
|
||||
init();
|
||||
}
|
||||
~cMorse() {
|
||||
}
|
||||
void init();
|
||||
void enable(std::string, bool);
|
||||
std::string rx_lookup(std::string);
|
||||
std::string tx_lookup(int);
|
||||
std::string tx_print() { return toprint; }
|
||||
int tx_length(int);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,24 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// view_cw.h -- placeholder for fldigi CW waterfall view (Android)
|
||||
// Replaces fldigi/src/include/view_cw.h (FLTK-based). Stub for compilation.
|
||||
// ----------------------------------------------------------------------------
|
||||
#ifndef _VIEW_CW_H
|
||||
#define _VIEW_CW_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
class view_cw {
|
||||
public:
|
||||
view_cw() {}
|
||||
~view_cw() {}
|
||||
|
||||
void restart() {}
|
||||
|
||||
void setFreq(double freq) {}
|
||||
void setSampleRate(int sr) {}
|
||||
|
||||
// Data buffer for waterfall (populated by cw decoder, read by Android UI)
|
||||
std::vector<float> spectrum;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,88 +0,0 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// fldigi_cw_jni.cpp -- JNI wrapper for fldigi CW decoder (Android)
|
||||
// Copyright (C) 2026 atsunatsu
|
||||
// GPL v3
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <jni.h>
|
||||
#include <cstring>
|
||||
#include "fldigi/cw.h"
|
||||
#include "fldigi/android_compat.h"
|
||||
|
||||
// ============================================================================
|
||||
// Global definitions (declared extern in android_compat.h / used by cw.cxx)
|
||||
// ============================================================================
|
||||
|
||||
waterfall_stub* wf = new waterfall_stub();
|
||||
|
||||
AndroidProgDefaults progdefaults;
|
||||
AndroidProgStatus progStatus;
|
||||
|
||||
bool use_nanoIO = false;
|
||||
void set_nanoWPM(int wpm) {}
|
||||
void set_nanoCW() {}
|
||||
|
||||
// UI/status stubs
|
||||
void put_cwRcvWPM(int) {}
|
||||
void put_MODEstatus(const char*, ...) {}
|
||||
void set_scope_xaxis_1(double) {}
|
||||
void set_scope_xaxis(double) {}
|
||||
void update_Status() {}
|
||||
|
||||
// misc helpers
|
||||
void set_scope_mode(int) {}
|
||||
void put_rx_char(int c) {}
|
||||
void put_echo_char(int c) {}
|
||||
|
||||
double zmsec() { return 0.0; }
|
||||
void MilliSleep(int) {}
|
||||
|
||||
// ============================================================================
|
||||
// JNI Exports
|
||||
// ============================================================================
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_rtbishop_look4sat_feature_cw_FldigiNative_create(JNIEnv* env, jobject thiz) {
|
||||
cw* decoder = new cw();
|
||||
decoder->init();
|
||||
return reinterpret_cast<jlong>(decoder);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_rtbishop_look4sat_feature_cw_FldigiNative_destroy(JNIEnv* env, jobject thiz, jlong handle) {
|
||||
cw* decoder = reinterpret_cast<cw*>(handle);
|
||||
if (decoder) {
|
||||
decoder->rx_init();
|
||||
delete decoder;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_rtbishop_look4sat_feature_cw_FldigiNative_process(JNIEnv* env, jobject thiz, jlong handle, jfloatArray audio) {
|
||||
cw* decoder = reinterpret_cast<cw*>(handle);
|
||||
if (!decoder) return;
|
||||
|
||||
jsize len = env->GetArrayLength(audio);
|
||||
jfloat* elements = env->GetFloatArrayElements(audio, nullptr);
|
||||
if (!elements) return;
|
||||
|
||||
// Convert float to double
|
||||
double* buf = new double[len];
|
||||
for (jsize i = 0; i < len; i++) {
|
||||
buf[i] = static_cast<double>(elements[i]);
|
||||
}
|
||||
|
||||
decoder->rx_process(buf, len);
|
||||
|
||||
delete[] buf;
|
||||
env->ReleaseFloatArrayElements(audio, elements, JNI_ABORT);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_com_rtbishop_look4sat_feature_cw_FldigiNative_getDecodedText(JNIEnv* env, jobject thiz, jlong handle) {
|
||||
cw* decoder = reinterpret_cast<cw*>(handle);
|
||||
if (!decoder) return env->NewStringUTF("");
|
||||
|
||||
std::string text = decoder->get_rx_text();
|
||||
return env->NewStringUTF(text.c_str());
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.ve3nea.morse_expert.MainActivity
|
||||
|
||||
@Composable
|
||||
fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
||||
@@ -66,8 +67,8 @@ fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
||||
val activity = remember(context) {
|
||||
context as? Activity ?: error("CwDecodeScreen must be hosted in an Activity")
|
||||
}
|
||||
// fldigi 解码器控制器; 每次进入页面新建实例
|
||||
val controller = remember { FldigiCwController() }
|
||||
// 照搬的控制器(普通类, 非 Activity); 每次进入页面新建实例
|
||||
val controller = remember { MainActivity() }
|
||||
// 提前 inflate 原版布局, 供 AndroidView 与控制器 onCreate 共用同一根视图
|
||||
val rootView = remember(context) {
|
||||
LayoutInflater.from(context).inflate(R.layout.activity_main, null) as ConstraintLayout
|
||||
|
||||
@@ -44,7 +44,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.rtbishop.look4sat.feature.cw.FldigiCwController
|
||||
import com.ve3nea.morse_expert.MainActivity
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/** 键名/默认值照搬原 Morse Expert(root_preferences.xml + SettingsActivity 逻辑)。 */
|
||||
@@ -55,30 +55,6 @@ private const val DEFAULT_TEXT_FONT_SIZE = 18
|
||||
private const val MIN_FONT_SIZE = 7
|
||||
private const val MAX_FONT_SIZE = 99
|
||||
|
||||
/** 颜色键名(照搬原 Morse Expert I2.b.f663b) */
|
||||
private val ColorKeys = arrayOf(
|
||||
"bg_color", "text_color", "text_color_weak",
|
||||
"call_color", "call_color_weak",
|
||||
"cq_color", "cq_color_weak",
|
||||
"rst_color", "rst_color_weak"
|
||||
)
|
||||
|
||||
/** 颜色默认值(照搬原 Morse Expert I2.b.f664d,ARGB int) */
|
||||
private val ColorDefaults = intArrayOf(
|
||||
-3084048, -16777216, -5592406,
|
||||
-65536, -30584,
|
||||
-16776961, -7829249,
|
||||
-65281, -30465
|
||||
)
|
||||
|
||||
/** 颜色名称(照搬原 Morse Expert I2.b.c) */
|
||||
private val ColorNames = arrayOf(
|
||||
"Background color", "Text color", "Text color, weak",
|
||||
"Callsign color", "Callsign color, weak",
|
||||
"CQ color", "CQ color, weak",
|
||||
"RST color", "RST color, weak"
|
||||
)
|
||||
|
||||
/** 预置色板(9 色, 3x3 网格);原 ColorPreferenceCompat 用第三方 colorpicker, 不引入。 */
|
||||
private val PaletteColors = listOf(
|
||||
0xFFD0F0F0.toInt(), 0xFF000000.toInt(), 0xFFFFFFFF.toInt(),
|
||||
@@ -94,7 +70,7 @@ private val PaletteColors = listOf(
|
||||
* 点击 OK 保存并调用 controller.onResume() 立即生效; Cancel/点外部仅关闭不保存。
|
||||
*/
|
||||
@Composable
|
||||
fun CwSettingsDialog(controller: FldigiCwController, onDismiss: () -> Unit) {
|
||||
fun CwSettingsDialog(controller: MainActivity, onDismiss: () -> Unit) {
|
||||
val activity = controller.mActivity ?: return
|
||||
val prefs = remember(activity) {
|
||||
activity.getSharedPreferences(activity.packageName + "_preferences", Context.MODE_PRIVATE)
|
||||
@@ -110,7 +86,7 @@ fun CwSettingsDialog(controller: FldigiCwController, onDismiss: () -> Unit) {
|
||||
)
|
||||
}
|
||||
var colorValues by remember {
|
||||
mutableStateOf(IntArray(9) { i -> prefs.getInt(ColorKeys[i], ColorDefaults[i]) })
|
||||
mutableStateOf(IntArray(9) { i -> prefs.getInt(I2.b.f663b[i], I2.b.f664d[i]) })
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
@@ -148,7 +124,7 @@ fun CwSettingsDialog(controller: FldigiCwController, onDismiss: () -> Unit) {
|
||||
Text("Colors", style = MaterialTheme.typography.titleSmall)
|
||||
for (i in 0 until 9) {
|
||||
ColorSettingRow(
|
||||
title = ColorNames[i],
|
||||
title = I2.b.c[i],
|
||||
value = colorValues[i],
|
||||
onSelect = { selected ->
|
||||
colorValues = colorValues.copyOf().also { it[i] = selected }
|
||||
@@ -163,7 +139,7 @@ fun CwSettingsDialog(controller: FldigiCwController, onDismiss: () -> Unit) {
|
||||
editor.putString(KEY_MESSAGE_TYPE, messageType)
|
||||
editor.putInt(KEY_TEXT_FONT_SIZE, fontSize.roundToInt())
|
||||
for (i in 0 until 9) {
|
||||
editor.putInt(ColorKeys[i], colorValues[i])
|
||||
editor.putInt(I2.b.f663b[i], colorValues[i])
|
||||
}
|
||||
editor.apply()
|
||||
// 原 SettingsActivity 返回时由 MainActivity.onResume() 重新读取全部设置; 照搬逻辑已就位
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
package com.rtbishop.look4sat.feature.cw
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioRecord
|
||||
import android.media.MediaRecorder
|
||||
import android.view.View
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* fldigi CW 解码器控制器。
|
||||
* 替代 com.ve3nea.morse_expert.MainActivity,提供相同生命周期接口:
|
||||
* onCreate / onResume / onPause / onDestroy / onPermissionGranted
|
||||
* 内部使用 AudioRecord 采集麦克风,送入 fldigi 原生解码器。
|
||||
*/
|
||||
class FldigiCwController {
|
||||
|
||||
/** 兼容 CwSettingsDialog 对 controller.mActivity 的引用 */
|
||||
var mActivity: Activity? = null
|
||||
private set
|
||||
|
||||
private var nativeHandle: Long = 0L
|
||||
private var audioRecord: AudioRecord? = null
|
||||
private var isRunning = false
|
||||
private var audioThread: Thread? = null
|
||||
private val scope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
private val _decodedText = MutableStateFlow("")
|
||||
val decodedText: StateFlow<String> = _decodedText.asStateFlow()
|
||||
|
||||
// 轮询解码文本
|
||||
private var pollJob: Job? = null
|
||||
|
||||
/** 创建解码器, 保存 Activity 引用。rootView 暂不处理(UI 由 AndroidView 管理)。 */
|
||||
fun onCreate(activity: Activity, rootView: View?) {
|
||||
mActivity = activity
|
||||
if (nativeHandle == 0L) {
|
||||
nativeHandle = FldigiNative.create()
|
||||
}
|
||||
}
|
||||
|
||||
/** 授予权限后启动录音和解码线程 */
|
||||
fun onPermissionGranted() {
|
||||
startAudioCapture()
|
||||
}
|
||||
|
||||
/** 恢复录音和解码 */
|
||||
fun onResume() {
|
||||
if (hasPermission()) {
|
||||
startAudioCapture()
|
||||
}
|
||||
// 启动轮询
|
||||
pollJob?.cancel()
|
||||
pollJob = scope.launch {
|
||||
while (isActive) {
|
||||
val text = FldigiNative.getDecodedText(nativeHandle)
|
||||
if (text.isNotEmpty()) {
|
||||
_decodedText.value += text
|
||||
}
|
||||
delay(100L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 暂停录音 */
|
||||
fun onPause() {
|
||||
stopAudioCapture()
|
||||
pollJob?.cancel()
|
||||
pollJob = null
|
||||
}
|
||||
|
||||
/** 销毁解码器 */
|
||||
fun onDestroy() {
|
||||
stopAudioCapture()
|
||||
pollJob?.cancel()
|
||||
if (nativeHandle != 0L) {
|
||||
FldigiNative.destroy(nativeHandle)
|
||||
nativeHandle = 0L
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取当前解码文本(供 Compose 读取) */
|
||||
fun getCurrentText(): String = _decodedText.value
|
||||
|
||||
/** 清除解码文本 */
|
||||
fun clearText() {
|
||||
_decodedText.value = ""
|
||||
}
|
||||
|
||||
// ── 兼容 MainActivity 接口(CwDecodeScreen 顶部按钮用)──
|
||||
|
||||
/** 暂停/恢复解码 */
|
||||
fun togglePause() {
|
||||
if (isRunning) {
|
||||
stopAudioCapture()
|
||||
} else {
|
||||
startAudioCapture()
|
||||
}
|
||||
}
|
||||
|
||||
/** 清除解码文本(= clearText) */
|
||||
fun clearDecoded() = clearText()
|
||||
|
||||
/** 保存解码文本到文件。当前简单实现: 不落盘(可选扩展) */
|
||||
fun saveText() {
|
||||
// 可选: 把 _decodedText.value 写入外部存储
|
||||
}
|
||||
|
||||
/** 录音信号保存。占位实现 */
|
||||
fun recordSignals() {
|
||||
// 可选扩展
|
||||
}
|
||||
|
||||
/** 返回是否处理了返回键(占位,直接返回 false 让上层导航) */
|
||||
fun handleBackPress(): Boolean = false
|
||||
|
||||
// ── 内部 ──
|
||||
|
||||
private fun hasPermission(): Boolean {
|
||||
val ctx = mActivity ?: return false
|
||||
return ctx.checkSelfPermission(Manifest.permission.RECORD_AUDIO) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun startAudioCapture() {
|
||||
if (isRunning) return
|
||||
val ctx = mActivity ?: return
|
||||
if (!hasPermission()) return
|
||||
|
||||
val sampleRate = 8000 // fldigi CW 解码器需要的采样率
|
||||
val bufferSize = AudioRecord.getMinBufferSize(
|
||||
sampleRate,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT
|
||||
).coerceAtLeast(4096)
|
||||
|
||||
try {
|
||||
audioRecord = AudioRecord(
|
||||
MediaRecorder.AudioSource.MIC,
|
||||
sampleRate,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
return
|
||||
}
|
||||
|
||||
val record = audioRecord ?: return
|
||||
if (record.state != AudioRecord.STATE_INITIALIZED) {
|
||||
record.release()
|
||||
audioRecord = null
|
||||
return
|
||||
}
|
||||
|
||||
record.startRecording()
|
||||
isRunning = true
|
||||
|
||||
// 16-bit PCM → float 转换送入解码器
|
||||
val shortBuf = ShortArray(4096)
|
||||
val floatBuf = FloatArray(4096)
|
||||
audioThread = Thread {
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO)
|
||||
while (isRunning && nativeHandle != 0L) {
|
||||
val read = record.read(shortBuf, 0, shortBuf.size)
|
||||
if (read > 0) {
|
||||
// 转 float [-1.0, 1.0]
|
||||
for (i in 0 until read) {
|
||||
floatBuf[i] = shortBuf[i].toFloat() / 32768f
|
||||
}
|
||||
FldigiNative.process(nativeHandle, floatBuf.copyOf(read))
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
name = "fldigi-cw-audio"
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopAudioCapture() {
|
||||
isRunning = false
|
||||
audioThread?.join(500)
|
||||
audioThread = null
|
||||
try {
|
||||
audioRecord?.apply {
|
||||
if (recordingState == AudioRecord.RECORDSTATE_RECORDING) {
|
||||
stop()
|
||||
}
|
||||
release()
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
audioRecord = null
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.rtbishop.look4sat.feature.cw
|
||||
|
||||
/**
|
||||
* JNI 绑定: fldigi CW 解码器原生库。
|
||||
* 对应 C++ 文件: fldigi_cw_jni.cpp
|
||||
*/
|
||||
object FldigiNative {
|
||||
init {
|
||||
System.loadLibrary("fldigi_cw")
|
||||
}
|
||||
|
||||
/** 创建解码器实例, 返回 native handle (long) */
|
||||
external fun create(): Long
|
||||
|
||||
/** 销毁解码器 */
|
||||
external fun destroy(handle: Long)
|
||||
|
||||
/** 送入 PCM 音频数据 (float[], mono, 8000Hz), 解码 */
|
||||
external fun process(handle: Long, audio: FloatArray)
|
||||
|
||||
/** 获取并清空积压的解码文本, 返回空字符串表示无新输出 */
|
||||
external fun getDecodedText(handle: Long): String
|
||||
}
|
||||
Reference in new issue
Block a user