kopia lustrzana https://github.com/windytan/slowrx
vis works, sync almost
rodzic
0df5f998a4
commit
c34a49f5db
|
@ -22,7 +22,7 @@ Requirements
|
|||
------------
|
||||
|
||||
* Linux, OSX, ...
|
||||
* Portaudio
|
||||
* PortAudio
|
||||
* gtkmm 3 (`libgtkmm-3.0-dev`)
|
||||
* FFTW 3 (`libfftw3-dev`)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
bin_PROGRAMS = slowrx
|
||||
slowrx_CPPFLAGS = -g $(GTKMM_CFLAGS) @SNDFILE_CFLAGS@ $(PORTAUDIO_CFLAGS)
|
||||
slowrx_LDADD = $(GTKMM_LIBS) @SNDFILE_LIBS@ -lfftw3 $(PORTAUDIO_LIBS)
|
||||
slowrx_SOURCES = slowrx.cc common.cc modespec.cc gui.cc dsp.cc vis.cc video.cc
|
||||
slowrx_SOURCES = slowrx.cc common.cc modespec.cc gui.cc dsp.cc vis.cc video.cc sync.cc tests.cc
|
||||
|
|
|
@ -21,8 +21,7 @@ std::vector<std::thread> threads(2);
|
|||
PicMeta CurrentPic;
|
||||
PcmData pcm;
|
||||
|
||||
short DSPworker::win_lens_[8] = { 47, 63, 95, 127, 255, 511, 1023, 47 };
|
||||
double DSPworker::window_[8][1024];
|
||||
std::vector<std::vector<double> > DSPworker::window_ (16);
|
||||
|
||||
// Clip to [0..255]
|
||||
int clip (double a) {
|
||||
|
|
|
@ -3,13 +3,17 @@
|
|||
|
||||
#define MINSLANT 30
|
||||
#define MAXSLANT 150
|
||||
#define CIRBUF_LEN 4096
|
||||
#define READ_CHUNK_LEN 1024
|
||||
|
||||
// moment length only affects length of delay and maximum window size.
|
||||
#define MOMENT_LEN 1023
|
||||
#define SYNCPIXLEN 1.5e-3
|
||||
|
||||
// moment length only affects length of delay, read interval,
|
||||
// and maximum window size.
|
||||
#define READ_CHUNK_LEN 1024
|
||||
#define MOMENT_LEN 2047
|
||||
#define FFT_LEN_SMALL 1024
|
||||
#define FFT_LEN_BIG 2048
|
||||
#define CIRBUF_LEN_FACTOR 4
|
||||
#define CIRBUF_LEN ((MOMENT_LEN+1)*CIRBUF_LEN_FACTOR)
|
||||
|
||||
#include <iostream>
|
||||
#include "portaudio.h"
|
||||
#include "sndfile.hh"
|
||||
|
@ -17,14 +21,16 @@
|
|||
#include "gtkmm.h"
|
||||
|
||||
enum WindowType {
|
||||
WINDOW_HANN47 = 0,
|
||||
WINDOW_HANN63,
|
||||
WINDOW_CHEB47 = 0,
|
||||
WINDOW_HANN95,
|
||||
WINDOW_HANN127,
|
||||
WINDOW_HANN255,
|
||||
WINDOW_HANN511,
|
||||
WINDOW_HANN1023,
|
||||
WINDOW_CHEB47
|
||||
WINDOW_HANN2047,
|
||||
WINDOW_HANN31,
|
||||
WINDOW_HANN63,
|
||||
WINDOW_SQUARE47
|
||||
};
|
||||
|
||||
enum SSTVMode {
|
||||
|
@ -48,8 +54,12 @@ enum eSubSamp {
|
|||
SUBSAMP_444, SUBSAMP_422_YUV, SUBSAMP_420_YUYV, SUBSAMP_440_YUVY
|
||||
};
|
||||
|
||||
enum {
|
||||
STREAM_FILE, STREAM_PA
|
||||
enum eStreamType {
|
||||
STREAM_TYPE_FILE, STREAM_TYPE_PA, STREAM_TYPE_STDIN
|
||||
};
|
||||
|
||||
enum eVISParity {
|
||||
PARITY_EVEN=0, PARITY_ODD=1
|
||||
};
|
||||
|
||||
extern std::map<int, SSTVMode> vis2mode;
|
||||
|
@ -80,23 +90,22 @@ class DSPworker {
|
|||
|
||||
DSPworker();
|
||||
|
||||
void openAudioFile(std::string);
|
||||
double forward(unsigned);
|
||||
double forward();
|
||||
double forward_ms(double);
|
||||
void getWindowedMoment(WindowType, double *);
|
||||
double getPeakFreq (double, double, WindowType);
|
||||
int getBin (double);
|
||||
double getFourierPower (fftw_complex coeff);
|
||||
bool is_still_listening ();
|
||||
std::vector<double> getBandPowerPerHz(std::vector<std::vector<double> >);
|
||||
WindowType getBestWindowFor(SSTVMode, double);
|
||||
WindowType getBestWindowFor(SSTVMode);
|
||||
void readMore();
|
||||
void openPortAudio();
|
||||
void openAudioFile (std::string);
|
||||
void openPortAudio ();
|
||||
void readMore ();
|
||||
double forward (unsigned);
|
||||
double forward ();
|
||||
double forward_time (double);
|
||||
void forward_to_time (double);
|
||||
|
||||
static short win_lens_[8];
|
||||
static double window_[8][1024];
|
||||
void windowedMoment (WindowType, fftw_complex *);
|
||||
double peakFreq (double, double, WindowType);
|
||||
int freq2bin (double, int);
|
||||
std::vector<double> bandPowerPerHz (std::vector<std::vector<double> >);
|
||||
WindowType bestWindowFor (SSTVMode, double SNR=99);
|
||||
|
||||
bool is_open ();
|
||||
double get_t ();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -107,14 +116,17 @@ class DSPworker {
|
|||
int cirbuf_fill_count_;
|
||||
bool please_stop_;
|
||||
SndfileHandle file_;
|
||||
int fft_len_;
|
||||
double *fft_inbuf_;
|
||||
fftw_complex *fft_inbuf_;
|
||||
fftw_complex *fft_outbuf_;
|
||||
fftw_plan fft_plan_;
|
||||
int samplerate_;
|
||||
bool is_still_listening_;
|
||||
fftw_plan fft_plan_small_;
|
||||
fftw_plan fft_plan_big_;
|
||||
double samplerate_;
|
||||
PaStream *pa_stream_;
|
||||
int stream_type_;
|
||||
eStreamType stream_type_;
|
||||
bool is_open_;
|
||||
double t_;
|
||||
|
||||
static std::vector<std::vector<double> > window_;
|
||||
};
|
||||
|
||||
class SlowGUI {
|
||||
|
@ -196,6 +208,7 @@ typedef struct ModeSpec {
|
|||
eColorEnc ColorEnc;
|
||||
eSyncOrder SyncOrder;
|
||||
eSubSamp SubSampling;
|
||||
eVISParity VISParity;
|
||||
} _ModeSpec;
|
||||
|
||||
extern _ModeSpec ModeSpec[];
|
||||
|
@ -204,7 +217,6 @@ double power (fftw_complex coeff);
|
|||
int clip (double a);
|
||||
void createGUI ();
|
||||
double deg2rad (double Deg);
|
||||
double FindSync (SSTVMode Mode, double Rate, int *Skip);
|
||||
std::string GetFSK ();
|
||||
bool GetVideo (SSTVMode Mode, DSPworker *dsp);
|
||||
SSTVMode GetVIS (DSPworker*);
|
||||
|
@ -215,6 +227,18 @@ void readPcm (int numsamples);
|
|||
void saveCurrentPic();
|
||||
void setVU (double *Power, int FFTLen, int WinIdx, bool ShowWin);
|
||||
int startGui (int, char**);
|
||||
void findSyncRansac (SSTVMode, std::vector<bool>);
|
||||
void findSyncHough (SSTVMode, std::vector<bool>);
|
||||
std::vector<double> upsampleLanczos (std::vector<double>, int);
|
||||
std::vector<double> Hann (std::size_t);
|
||||
std::vector<double> Blackmann (std::size_t);
|
||||
std::vector<double> Rect (std::size_t);
|
||||
std::vector<double> Gauss (std::size_t);
|
||||
|
||||
void runTest(const char*);
|
||||
|
||||
double complexMag (fftw_complex coeff);
|
||||
guint8 freq2lum(double);
|
||||
|
||||
void evt_AbortRx ();
|
||||
void evt_changeDevices ();
|
||||
|
|
490
src/dsp.cc
490
src/dsp.cc
|
@ -3,13 +3,7 @@
|
|||
|
||||
DSPworker::DSPworker() : Mutex(), please_stop_(false) {
|
||||
|
||||
fft_len_ = 2048;
|
||||
|
||||
for (int j = 0; j < 7; j++)
|
||||
for (int i = 0; i < win_lens_[j]; i++)
|
||||
window_[j][i] = 0.5 * (1 - cos( (2 * M_PI * i) / (win_lens_[j] - 1)) );
|
||||
|
||||
std::vector<double> cheb = {
|
||||
std::vector<double> cheb47 = {
|
||||
0.0004272315,0.0013212953,0.0032312239,0.0067664313,0.0127521667,0.0222058684,
|
||||
0.0363037629,0.0563165400,0.0835138389,0.1190416120,0.1637810511,0.2182020094,
|
||||
0.2822270091,0.3551233730,0.4354402894,0.5210045495,0.6089834347,0.6960162864,
|
||||
|
@ -19,117 +13,148 @@ DSPworker::DSPworker() : Mutex(), please_stop_(false) {
|
|||
0.1637810511,0.1190416120,0.0835138389,0.0563165400,0.0363037629,0.0222058684,
|
||||
0.0127521667,0.0067664313,0.0032312239,0.0013212953,0.0004272315
|
||||
};
|
||||
|
||||
for (int i = 0; i < win_lens_[WINDOW_CHEB47]; i++)
|
||||
window_[WINDOW_CHEB47][i] = cheb[i];
|
||||
std::vector<double> sq47 = {
|
||||
1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1
|
||||
};
|
||||
//window_[WINDOW_HANN31] = Hann(31);
|
||||
//window_[WINDOW_HANN47] = Hann(47);
|
||||
//window_[WINDOW_HANN63] = Hann(63);
|
||||
window_[WINDOW_HANN95] = Hann(95);
|
||||
window_[WINDOW_HANN127] = Hann(127);
|
||||
window_[WINDOW_HANN255] = Hann(255);
|
||||
window_[WINDOW_HANN511] = Hann(511);
|
||||
window_[WINDOW_HANN1023] = Hann(1023);
|
||||
window_[WINDOW_HANN2047] = Hann(2047);
|
||||
window_[WINDOW_CHEB47] = cheb47;
|
||||
//window_[WINDOW_SQUARE47] = sq47;
|
||||
|
||||
fft_inbuf_ = (double*) fftw_alloc_real(sizeof(double) * fft_len_);
|
||||
fft_inbuf_ = (fftw_complex*) fftw_alloc_complex(sizeof(fftw_complex) * FFT_LEN_BIG);
|
||||
if (fft_inbuf_ == NULL) {
|
||||
perror("GetVideo: Unable to allocate memory for FFT");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
fft_outbuf_ = (fftw_complex*) fftw_alloc_complex(sizeof(fftw_complex) * (fft_len_));
|
||||
fft_outbuf_ = (fftw_complex*) fftw_alloc_complex(sizeof(fftw_complex) * FFT_LEN_BIG);
|
||||
if (fft_outbuf_ == NULL) {
|
||||
perror("GetVideo: Unable to allocate memory for FFT");
|
||||
fftw_free(fft_inbuf_);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
memset(fft_inbuf_, 0, sizeof(double) * fft_len_);
|
||||
memset(fft_inbuf_, 0, sizeof(fftw_complex) * FFT_LEN_BIG);
|
||||
|
||||
fft_plan_ = fftw_plan_dft_r2c_1d(fft_len_, fft_inbuf_, fft_outbuf_, FFTW_ESTIMATE);
|
||||
fft_plan_small_ = fftw_plan_dft_1d(FFT_LEN_SMALL, fft_inbuf_, fft_outbuf_, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
fft_plan_big_ = fftw_plan_dft_1d(FFT_LEN_BIG, fft_inbuf_, fft_outbuf_, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
cirbuf_tail_ = 0;
|
||||
cirbuf_head_ = 0;
|
||||
cirbuf_fill_count_ = 0;
|
||||
is_still_listening_ = true;
|
||||
|
||||
printf("DSPworker created\n");
|
||||
is_open_ = false;
|
||||
t_ = 0;
|
||||
|
||||
}
|
||||
|
||||
void DSPworker::openAudioFile (std::string fname) {
|
||||
|
||||
file_ = SndfileHandle(fname.c_str()) ;
|
||||
if (!is_open_) {
|
||||
|
||||
printf ("Opened file '%s'\n", fname.c_str()) ;
|
||||
printf (" Sample rate : %d\n", file_.samplerate ()) ;
|
||||
printf (" Channels : %d\n", file_.channels ()) ;
|
||||
fprintf (stderr,"Open '%s'\n", fname.c_str()) ;
|
||||
file_ = SndfileHandle(fname.c_str()) ;
|
||||
|
||||
samplerate_ = file_.samplerate();
|
||||
if (file_.error()) {
|
||||
fprintf(stderr,"(sndfile) %s\n", file_.strError());
|
||||
} else {
|
||||
fprintf (stderr," Sample rate : %d\n", file_.samplerate ()) ;
|
||||
fprintf (stderr," Channels : %d\n", file_.channels ()) ;
|
||||
|
||||
stream_type_ = STREAM_FILE;
|
||||
samplerate_ = file_.samplerate();
|
||||
|
||||
/* RAII takes care of destroying SndfileHandle object. */
|
||||
stream_type_ = STREAM_TYPE_FILE;
|
||||
|
||||
is_open_ = true;
|
||||
readMore();
|
||||
|
||||
/* RAII takes care of destroying SndfileHandle object. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DSPworker::openPortAudio () {
|
||||
|
||||
/* -- initialize PortAudio -- */
|
||||
Pa_Initialize();
|
||||
if (!is_open_) {
|
||||
Pa_Initialize();
|
||||
|
||||
/* -- setup input and output -- */
|
||||
PaStreamParameters inputParameters;
|
||||
inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
|
||||
inputParameters.channelCount = 1;
|
||||
inputParameters.sampleFormat = paInt16;
|
||||
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
|
||||
inputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
PaStreamParameters inputParameters;
|
||||
inputParameters.device = Pa_GetDefaultInputDevice();
|
||||
inputParameters.channelCount = 1;
|
||||
inputParameters.sampleFormat = paInt16;
|
||||
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
|
||||
inputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
const PaDeviceInfo *info;
|
||||
info = Pa_GetDeviceInfo(inputParameters.device);
|
||||
const PaDeviceInfo *devinfo;
|
||||
devinfo = Pa_GetDeviceInfo(inputParameters.device);
|
||||
|
||||
/* -- setup stream -- */
|
||||
int err = Pa_OpenStream(
|
||||
&pa_stream_,
|
||||
&inputParameters,
|
||||
NULL,
|
||||
44100,
|
||||
READ_CHUNK_LEN,
|
||||
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
||||
NULL, /* no callback, use blocking API */
|
||||
NULL ); /* no callback, so no callback userData */
|
||||
int err = Pa_OpenStream(
|
||||
&pa_stream_,
|
||||
&inputParameters,
|
||||
NULL,
|
||||
44100,
|
||||
READ_CHUNK_LEN,
|
||||
paClipOff,
|
||||
NULL, /* no callback, use blocking API */
|
||||
NULL ); /* no callback, so no callback userData */
|
||||
|
||||
if (err == paNoError)
|
||||
printf("opened %s\n",info->name);
|
||||
/* -- start stream -- */
|
||||
err = Pa_StartStream( pa_stream_ );
|
||||
if (err == paNoError)
|
||||
printf("stream started\n");
|
||||
if (err == paNoError)
|
||||
printf("opened %s\n",devinfo->name);
|
||||
err = Pa_StartStream( pa_stream_ );
|
||||
if (err == paNoError)
|
||||
printf("stream started\n");
|
||||
|
||||
samplerate_ = 44100;
|
||||
const PaStreamInfo *streaminfo;
|
||||
streaminfo = Pa_GetStreamInfo(pa_stream_);
|
||||
samplerate_ = streaminfo->sampleRate;
|
||||
printf("%f\n",samplerate_);
|
||||
|
||||
stream_type_ = STREAM_PA;
|
||||
stream_type_ = STREAM_TYPE_PA;
|
||||
|
||||
is_open_ = true;
|
||||
readMore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int DSPworker::getBin (double freq) {
|
||||
return (freq / samplerate_ * fft_len_);
|
||||
int DSPworker::freq2bin (double freq, int fft_len) {
|
||||
return (freq / samplerate_ * fft_len);
|
||||
}
|
||||
|
||||
double DSPworker::getFourierPower (fftw_complex coeff) {
|
||||
return pow(coeff[0],2) + pow(coeff[1],2);
|
||||
bool DSPworker::is_open () {
|
||||
return is_open_;
|
||||
}
|
||||
|
||||
bool DSPworker::is_still_listening () {
|
||||
return is_still_listening_;
|
||||
double DSPworker::get_t() {
|
||||
return t_;
|
||||
}
|
||||
|
||||
void DSPworker::readMore () {
|
||||
short read_buffer[READ_CHUNK_LEN];
|
||||
sf_count_t samplesread = 0;
|
||||
|
||||
sf_count_t samplesread;
|
||||
if (stream_type_ == STREAM_FILE) {
|
||||
if (is_open_) {
|
||||
if (stream_type_ == STREAM_TYPE_FILE) {
|
||||
|
||||
samplesread = file_.read(read_buffer, READ_CHUNK_LEN);
|
||||
if (samplesread < READ_CHUNK_LEN)
|
||||
is_still_listening_ = false;
|
||||
samplesread = file_.read(read_buffer, READ_CHUNK_LEN);
|
||||
if (samplesread < READ_CHUNK_LEN)
|
||||
is_open_ = false;
|
||||
|
||||
} else {
|
||||
} else if (stream_type_ == STREAM_TYPE_PA) {
|
||||
|
||||
samplesread = READ_CHUNK_LEN;
|
||||
int err = Pa_ReadStream( pa_stream_, read_buffer, READ_CHUNK_LEN );
|
||||
if (err != paNoError)
|
||||
is_still_listening_ = false;
|
||||
samplesread = READ_CHUNK_LEN;
|
||||
int err = Pa_ReadStream( pa_stream_, read_buffer, READ_CHUNK_LEN );
|
||||
if (err != paNoError)
|
||||
is_open_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
int cirbuf_fits = std::min(CIRBUF_LEN - cirbuf_head_, (int)samplesread);
|
||||
|
@ -159,74 +184,111 @@ double DSPworker::forward (unsigned nsamples) {
|
|||
readMore();
|
||||
}
|
||||
}
|
||||
return (1.0 * nsamples / samplerate_);
|
||||
double dt = 1.0 * nsamples / samplerate_;
|
||||
t_ += dt;
|
||||
return dt;
|
||||
}
|
||||
double DSPworker::forward() {
|
||||
double DSPworker::forward () {
|
||||
return forward(1);
|
||||
}
|
||||
double DSPworker::forward_ms(double ms) {
|
||||
return forward(ms / 1000 * samplerate_);
|
||||
double DSPworker::forward_time(double sec) {
|
||||
double start_t = t_;
|
||||
while (t_ < start_t + sec)
|
||||
forward();
|
||||
return t_ - start_t;
|
||||
}
|
||||
void DSPworker::forward_to_time(double sec) {
|
||||
while (t_ < sec)
|
||||
forward();
|
||||
}
|
||||
|
||||
|
||||
// the current moment, windowed
|
||||
void DSPworker::getWindowedMoment (WindowType win_type, double *result) {
|
||||
void DSPworker::windowedMoment (WindowType win_type, fftw_complex *result) {
|
||||
|
||||
//double if_phi = 0;
|
||||
for (int i = 0; i < MOMENT_LEN; i++) {
|
||||
|
||||
int win_i = i - MOMENT_LEN/2 + win_lens_[win_type]/2 ;
|
||||
int win_i = i - MOMENT_LEN/2 + window_[win_type].size()/2 ;
|
||||
|
||||
if (win_i >= 0 && win_i < win_lens_[win_type]) {
|
||||
result[win_i] = cirbuf_[cirbuf_tail_ + i] * window_[win_type][win_i];
|
||||
if (win_i >= 0 && win_i < window_[win_type].size()) {
|
||||
double a;
|
||||
//fftw_complex mixed;
|
||||
a = cirbuf_[cirbuf_tail_ + i] * window_[win_type][win_i];
|
||||
|
||||
/*// mix to IF
|
||||
mixed[0] = a * cos(if_phi) - a * sin(if_phi);
|
||||
mixed[1] = a * cos(if_phi) + a * sin(if_phi);
|
||||
if_phi += 2 * M_PI * 10000 / samplerate_;*/
|
||||
|
||||
result[win_i][0] = result[win_i][1] = a;
|
||||
//printf("%f\n",a);
|
||||
//std::cout<<result[win_i]<<",";
|
||||
}
|
||||
}
|
||||
// exit(0);
|
||||
//std::cout<<"\n";
|
||||
|
||||
}
|
||||
|
||||
double DSPworker::getPeakFreq (double minf, double maxf, WindowType wintype) {
|
||||
double DSPworker::peakFreq (double minf, double maxf, WindowType wintype) {
|
||||
|
||||
double windowed[win_lens_[wintype]];
|
||||
double Power[fft_len_];
|
||||
int fft_len = (window_[wintype].size() <= FFT_LEN_SMALL ? FFT_LEN_SMALL : FFT_LEN_BIG);
|
||||
|
||||
getWindowedMoment(wintype, windowed);
|
||||
//for (int i=0;i<win_lens_[wintype];i++)
|
||||
// printf("g %f\n",windowed[i]);
|
||||
memset(fft_inbuf_, 0, fft_len_ * sizeof(double));
|
||||
memcpy(fft_inbuf_, windowed, win_lens_[wintype] * sizeof(double));
|
||||
fftw_execute(fft_plan_);
|
||||
// Find the bin with most power
|
||||
int MaxBin = 0;
|
||||
for (int i = getBin(minf)-1; i <= getBin(maxf)+1; i++) {
|
||||
Power[i] = getFourierPower(fft_outbuf_[i]);
|
||||
if ( (i >= getBin(minf) && i < getBin(maxf)) &&
|
||||
(MaxBin == 0 || Power[i] > Power[MaxBin]))
|
||||
MaxBin = i;
|
||||
fftw_complex windowed[window_[wintype].size()];
|
||||
double Mag[fft_len/2 + 1];
|
||||
|
||||
windowedMoment(wintype, windowed);
|
||||
memset(fft_inbuf_, 0, fft_len * sizeof(windowed[0]));
|
||||
memcpy(fft_inbuf_, windowed, window_[wintype].size() * sizeof(windowed[0]));
|
||||
fftw_execute(fft_len == FFT_LEN_BIG ? fft_plan_big_ : fft_plan_small_);
|
||||
|
||||
int peakBin = 0;
|
||||
int lobin = freq2bin(minf, fft_len);
|
||||
int hibin = freq2bin(maxf, fft_len);
|
||||
for (int i = lobin-1; i <= hibin+1; i++) {
|
||||
Mag[i] = complexMag(fft_outbuf_[i]);
|
||||
if ( (i >= lobin && i < hibin) &&
|
||||
(peakBin == 0 || Mag[i] > Mag[peakBin]))
|
||||
peakBin = i;
|
||||
}
|
||||
|
||||
// Find the peak frequency by Gaussian interpolation
|
||||
double result = MaxBin + (log( Power[MaxBin + 1] / Power[MaxBin - 1] )) /
|
||||
(2 * log( pow(Power[MaxBin], 2) / (Power[MaxBin + 1] * Power[MaxBin - 1])));
|
||||
//double result = MaxBin + (log( Power[MaxBin + 1] / Power[MaxBin - 1] )) /
|
||||
// (2 * log( pow(Power[MaxBin], 2) / (Power[MaxBin + 1] * Power[MaxBin - 1])));
|
||||
|
||||
double y1 = Mag[peakBin-1], y2 = Mag[peakBin], y3 = Mag[peakBin+1];
|
||||
double result = peakBin + (y3 - y1) / (2 * (2*y2 - y3 - y1));
|
||||
|
||||
// In Hertz
|
||||
result = result / fft_len_ * samplerate_;
|
||||
result = result / fft_len * samplerate_;
|
||||
|
||||
// cheb47 @ 44100 can't resolve <1800 Hz
|
||||
if (wintype == WINDOW_CHEB47 && result < 1800)
|
||||
result = peakFreq (minf, maxf, WINDOW_HANN95);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
std::vector<double> DSPworker::getBandPowerPerHz(std::vector<std::vector<double> > bands) {
|
||||
double windowed[win_lens_[WINDOW_HANN1023]];
|
||||
std::vector<double> DSPworker::bandPowerPerHz(std::vector<std::vector<double> > bands) {
|
||||
|
||||
getWindowedMoment(WINDOW_HANN1023, windowed);
|
||||
memset(fft_inbuf_, 0, fft_len_ * sizeof(double));
|
||||
memcpy(fft_inbuf_, windowed, win_lens_[WINDOW_HANN1023] * sizeof(double));
|
||||
fftw_execute(fft_plan_);
|
||||
int fft_len = FFT_LEN_BIG;
|
||||
WindowType wintype = WINDOW_HANN2047;
|
||||
fftw_complex windowed[window_[wintype].size()];
|
||||
|
||||
windowedMoment(wintype, windowed);
|
||||
memset(fft_inbuf_, 0, FFT_LEN_BIG * sizeof(fft_inbuf_[0]));
|
||||
memcpy(fft_inbuf_, windowed, window_[wintype].size() * sizeof(windowed[0]));
|
||||
fftw_execute(fft_len == FFT_LEN_BIG ? fft_plan_big_ : fft_plan_small_);
|
||||
|
||||
std::vector<double> result;
|
||||
for (std::vector<double> band : bands) {
|
||||
double P = 0;
|
||||
double binwidth = 1.0 * samplerate_ / fft_len_;
|
||||
double binwidth = 1.0 * samplerate_ / fft_len;
|
||||
int nbins = 0;
|
||||
for (int i = getBin(band[0]); i <= getBin(band[1]); i++) {
|
||||
P += getFourierPower(fft_outbuf_[i]);
|
||||
for (int i = freq2bin(band[0], fft_len); i <= freq2bin(band[1], fft_len); i++) {
|
||||
P += pow(complexMag(fft_outbuf_[i]), 2);
|
||||
nbins++;
|
||||
}
|
||||
P = P/(binwidth*nbins);
|
||||
|
@ -235,74 +297,26 @@ std::vector<double> DSPworker::getBandPowerPerHz(std::vector<std::vector<double>
|
|||
return result;
|
||||
}
|
||||
|
||||
WindowType DSPworker::getBestWindowFor(SSTVMode Mode, double SNR) {
|
||||
WindowType DSPworker::bestWindowFor(SSTVMode Mode, double SNR) {
|
||||
WindowType WinType;
|
||||
|
||||
if (SNR >= 20) WinType = WINDOW_CHEB47;
|
||||
else if (SNR >= 10) WinType = WINDOW_HANN63;
|
||||
else if (SNR >= 9) WinType = WINDOW_HANN95;
|
||||
else if (SNR >= 3) WinType = WINDOW_HANN127;
|
||||
else if (SNR >= -5) WinType = WINDOW_HANN255;
|
||||
else if (SNR >= -10) WinType = WINDOW_HANN511;
|
||||
else WinType = WINDOW_HANN1023;
|
||||
double samplesInPixel = 1.0 * samplerate_ * ModeSpec[Mode].tScan / ModeSpec[Mode].ScanPixels;
|
||||
|
||||
// Minimum winlength can be doubled for Scottie DX
|
||||
//if (Mode == MODE_SDX && WinType < WINDOW_HANN511) WinType++;
|
||||
if (SNR >= 23 && Mode != MODE_PD180 && Mode != MODE_SDX) WinType = WINDOW_CHEB47;
|
||||
else if (SNR >= 12) WinType = WINDOW_HANN95;
|
||||
else if (SNR >= 8) WinType = WINDOW_HANN127;
|
||||
else if (SNR >= 5) WinType = WINDOW_HANN255;
|
||||
else if (SNR >= 4) WinType = WINDOW_HANN511;
|
||||
else if (SNR >= -7) WinType = WINDOW_HANN1023;
|
||||
else WinType = WINDOW_HANN2047;
|
||||
|
||||
return WinType;
|
||||
}
|
||||
WindowType DSPworker::getBestWindowFor(SSTVMode Mode) {
|
||||
return getBestWindowFor(Mode, 99);
|
||||
}
|
||||
/*WindowType DSPworker::bestWindowFor(SSTVMode Mode) {
|
||||
return bestWindowFor(Mode, 20);
|
||||
}*/
|
||||
|
||||
/*
|
||||
// Capture fresh PCM data to buffer
|
||||
void readPcm(int numsamples) {
|
||||
|
||||
int samplesread, i;
|
||||
gint32 tmp[BUFLEN]; // Holds one or two 16-bit channels, will be ANDed to single channel
|
||||
|
||||
//samplesread = snd_pcm_readi(pcm.handle, tmp, (pcm.WindowPtr == 0 ? BUFLEN : numsamples));
|
||||
|
||||
if (samplesread < numsamples) {
|
||||
|
||||
if (samplesread == -EPIPE)
|
||||
printf("ALSA: buffer overrun\n");
|
||||
else if (samplesread < 0) {
|
||||
//printf("ALSA error %d (%s)\n", samplesread, snd_strerror(samplesread));
|
||||
gtk_widget_set_tooltip_text(gui.image_devstatus, "ALSA error");
|
||||
Abort = TRUE;
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
else
|
||||
printf("Can't read %d samples\n", numsamples);
|
||||
|
||||
// On first appearance of error, update the status icon
|
||||
if (!pcm.BufferDrop) {
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR);
|
||||
gtk_widget_set_tooltip_text(gui.image_devstatus, "Device is dropping samples");
|
||||
pcm.BufferDrop = TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (pcm.WindowPtr == 0) {
|
||||
// Fill buffer on first run
|
||||
for (i=0; i<BUFLEN; i++)
|
||||
pcm.Buffer[i] = tmp[i] & 0xffff;
|
||||
pcm.WindowPtr = BUFLEN/2;
|
||||
} else {
|
||||
|
||||
// Move buffer and push samples
|
||||
for (i=0; i<BUFLEN-numsamples; i++) pcm.Buffer[i] = pcm.Buffer[i+numsamples];
|
||||
for (i=BUFLEN-numsamples; i<BUFLEN; i++) pcm.Buffer[i] = tmp[i-(BUFLEN-numsamples)] & 0xffff;
|
||||
|
||||
pcm.WindowPtr -= numsamples;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void populateDeviceList() {
|
||||
/*void populateDeviceList() {
|
||||
int card;
|
||||
char *cardname;
|
||||
int numcards, row;
|
||||
|
@ -333,98 +347,72 @@ void populateDeviceList() {
|
|||
|
||||
}*/
|
||||
|
||||
// Initialize sound card
|
||||
// Return value:
|
||||
// 0 = opened ok
|
||||
// -1 = opened, but suboptimal
|
||||
// -2 = couldn't be opened
|
||||
int initPcmDevice(std::string wanted_dev_name) {
|
||||
std::vector<double> Hann (std::size_t winlen) {
|
||||
std::vector<double> result(winlen);
|
||||
for (std::size_t i=0; i < winlen; i++)
|
||||
result[i] = 0.5 * (1 - cos( (2 * M_PI * i) / (winlen)) );
|
||||
return result;
|
||||
}
|
||||
|
||||
//snd_pcm_hw_params_t *hwparams;
|
||||
void *hwparams;
|
||||
char pcm_name[30];
|
||||
unsigned int exact_rate = 44100;
|
||||
int card;
|
||||
bool found;
|
||||
char *cardname;
|
||||
std::vector<double> Blackmann (std::size_t winlen) {
|
||||
std::vector<double> result(winlen);
|
||||
for (std::size_t i=0; i < winlen; i++)
|
||||
result[i] = 0.42 - 0.5*cos(2*M_PI*i/winlen) - 0.08*cos(4*M_PI*i/winlen);
|
||||
|
||||
//pcm.BufferDrop = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
//snd_pcm_hw_params_alloca(&hwparams);
|
||||
std::vector<double> Rect (std::size_t winlen) {
|
||||
std::vector<double> result(winlen);
|
||||
double sigma = 0.4;
|
||||
for (std::size_t i=0; i < winlen; i++)
|
||||
result[i] = exp(-0.5*((i-(winlen-1)/2)/(sigma*(winlen-1)/2)));
|
||||
|
||||
card = -1;
|
||||
found = false;
|
||||
if (wanted_dev_name.compare("default") == 0) {
|
||||
found=true;
|
||||
} else {
|
||||
do {
|
||||
//snd_card_next(&card);
|
||||
if (card != -1) {
|
||||
//snd_card_get_name(card,&cardname);
|
||||
if (strcmp(cardname, wanted_dev_name.c_str()) == 0) {
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<double> Gauss (std::size_t winlen) {
|
||||
std::vector<double> result(winlen);
|
||||
for (std::size_t i=0; i < winlen; i++)
|
||||
result[i] = 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double complexMag (fftw_complex coeff) {
|
||||
return sqrt(pow(coeff[0],2) + pow(coeff[1],2));
|
||||
}
|
||||
|
||||
guint8 freq2lum (double freq) {
|
||||
return clip((freq - 1500.0) / (2300.0-1500.0) * 255 + .5);
|
||||
}
|
||||
|
||||
std::vector<double> upsampleLanczos(std::vector<double> orig, int factor) {
|
||||
std::vector<double> result(orig.size()*factor);
|
||||
int alpha = 4;
|
||||
int sinclen = (factor*alpha % 2 == 0 ? factor*alpha+1 : factor*alpha);
|
||||
double middle = 1500;
|
||||
|
||||
std::vector<double> sinc(sinclen);
|
||||
for (int i=0; i<sinclen; i++) {
|
||||
double x1 = 1.0*i/factor - alpha/2;
|
||||
double x2 = 2.0*i/sinclen - 1;
|
||||
sinc[i] = (x1 == 0 ? 1 : sin(M_PI*x1) / (M_PI*x1)) *
|
||||
(x2 == 0 ? 1 : sin(M_PI*x2) / (M_PI*x2));
|
||||
}
|
||||
|
||||
for (int i=0; i<orig.size(); i++) {
|
||||
if (orig[i] != 0) {
|
||||
for (int j=0; j<sinclen; j++) {
|
||||
int i_new = i*factor + (j-sinclen/2);
|
||||
if (i_new > 0 && i_new < result.size())
|
||||
result[i_new] += (orig[i] - middle) * sinc[j];
|
||||
}
|
||||
} while (card != -1);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
perror("Device disconnected?\n");
|
||||
return(-2);
|
||||
}
|
||||
|
||||
if (wanted_dev_name.compare("default") == 0) {
|
||||
sprintf(pcm_name,"default");
|
||||
} else {
|
||||
sprintf(pcm_name,"hw:%d",card);
|
||||
}
|
||||
|
||||
/*if (snd_pcm_open(&pcm.handle, pcm_name, SND_PCM_STREAM_CAPTURE, 0) < 0) {
|
||||
perror("ALSA: Error opening PCM device");
|
||||
return(-2);
|
||||
}*/
|
||||
|
||||
/* Init hwparams with full configuration space */
|
||||
/*if (snd_pcm_hw_params_any(pcm.handle, hwparams) < 0) {
|
||||
perror("ALSA: Can not configure this PCM device.");
|
||||
return(-2);
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_set_access(pcm.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
|
||||
perror("ALSA: Error setting interleaved access.");
|
||||
return(-2);
|
||||
}
|
||||
if (snd_pcm_hw_params_set_format(pcm.handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
|
||||
perror("ALSA: Error setting format S16_LE.");
|
||||
return(-2);
|
||||
}
|
||||
if (snd_pcm_hw_params_set_rate_near(pcm.handle, hwparams, &exact_rate, 0) < 0) {
|
||||
perror("ALSA: Error setting sample rate.");
|
||||
return(-2);
|
||||
}*/
|
||||
|
||||
// Try stereo first
|
||||
/*if (snd_pcm_hw_params_set_channels(pcm.handle, hwparams, 2) < 0) {
|
||||
// Fall back to mono
|
||||
if (snd_pcm_hw_params_set_channels(pcm.handle, hwparams, 1) < 0) {
|
||||
perror("ALSA: Error setting channels.");
|
||||
return(-2);
|
||||
}
|
||||
}
|
||||
if (snd_pcm_hw_params(pcm.handle, hwparams) < 0) {
|
||||
perror("ALSA: Error setting HW params.");
|
||||
return(-2);
|
||||
}*/
|
||||
|
||||
/*pcm.Buffer = calloc( BUFLEN, sizeof(gint16));
|
||||
memset(pcm.Buffer, 0, BUFLEN);*/
|
||||
for (int i=0; i<result.size(); i++)
|
||||
result[i] += middle;
|
||||
|
||||
if (exact_rate != 44100) {
|
||||
fprintf(stderr, "ALSA: Got %d Hz instead of 44100. Expect artifacts.\n", exact_rate);
|
||||
return(-1);
|
||||
}
|
||||
|
||||
return(0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* SubSampling Chroma subsampling mode for YUV (SUBSAMP_444, SUBSAMP_2121,
|
||||
* SUBSAMP_2112, SUBSAMP_211)
|
||||
* ColorEnc Color format (COLOR_GBR, COLOR_RGB, COLOR_YUV, COLOR_MONO)
|
||||
* VISParity Parity mode in VIS (normally PARITY_EVEN - with one exception)
|
||||
*
|
||||
*
|
||||
* All timings are in seconds.
|
||||
|
@ -50,7 +51,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tLine = 446.446e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_GBR },
|
||||
.ColorEnc = COLOR_GBR,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_M2] = { // N7CXI, 2000
|
||||
.Name = "Martin M2",
|
||||
|
@ -65,7 +67,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0.572e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_GBR },
|
||||
.ColorEnc = COLOR_GBR,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_M3] = { // KB4YZ, 1999
|
||||
.Name = "Martin M3",
|
||||
|
@ -80,7 +83,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0.572e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_GBR },
|
||||
.ColorEnc = COLOR_GBR,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_M4] = { // KB4YZ, 1999
|
||||
.Name = "Martin M4",
|
||||
|
@ -95,7 +99,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0.572e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_GBR },
|
||||
.ColorEnc = COLOR_GBR,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_S1] = { // N7CXI, 2000
|
||||
.Name = "Scottie S1",
|
||||
|
@ -110,7 +115,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tLine = 428.22e-3,
|
||||
.SyncOrder = SYNC_SCOTTIE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_GBR },
|
||||
.ColorEnc = COLOR_GBR,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_S2] = { // N7CXI, 2000
|
||||
.Name = "Scottie S2",
|
||||
|
@ -125,7 +131,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 1.5e-3,
|
||||
.SyncOrder = SYNC_SCOTTIE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_GBR },
|
||||
.ColorEnc = COLOR_GBR,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_SDX] = { // N7CXI, 2000
|
||||
.Name = "Scottie DX",
|
||||
|
@ -140,7 +147,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 1.5e-3,
|
||||
.SyncOrder = SYNC_SCOTTIE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_GBR },
|
||||
.ColorEnc = COLOR_GBR,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_R72] = { // N7CXI, 2000
|
||||
.Name = "Robot 72",
|
||||
|
@ -155,7 +163,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 6e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_422_YUV,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_R36] = { // N7CXI, 2000
|
||||
.Name = "Robot 36",
|
||||
|
@ -170,7 +179,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 6e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_420_YUYV,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_R24] = { // KB4YZ, 1999
|
||||
.Name = "Robot 24",
|
||||
|
@ -185,7 +195,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 6e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_420_YUYV,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_R24BW] = { // KB4YZ, 1999
|
||||
.Name = "Robot 24 B/W",
|
||||
|
@ -200,7 +211,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_MONO },
|
||||
.ColorEnc = COLOR_MONO,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_R12BW] = { // KB4YZ, 1999
|
||||
.Name = "Robot 12 B/W",
|
||||
|
@ -215,7 +227,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_MONO },
|
||||
.ColorEnc = COLOR_MONO,
|
||||
.VISParity = PARITY_ODD },
|
||||
|
||||
[MODE_R8BW] = { // KB4YZ, 1999
|
||||
.Name = "Robot 8 B/W",
|
||||
|
@ -230,7 +243,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_MONO },
|
||||
.ColorEnc = COLOR_MONO,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_W2120] = { // KB4YZ, 1999
|
||||
.Name = "Wraase SC-2 120",
|
||||
|
@ -245,7 +259,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_RGB },
|
||||
.ColorEnc = COLOR_RGB,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_W2180] = { // N7CXI, 2000
|
||||
.Name = "Wraase SC-2 180",
|
||||
|
@ -260,7 +275,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_RGB },
|
||||
.ColorEnc = COLOR_RGB,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_PD50] = { // N7CXI, 2000
|
||||
.Name = "PD-50",
|
||||
|
@ -275,7 +291,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_440_YUVY,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_PD90] = { // N7CXI, 2000
|
||||
.Name = "PD-90",
|
||||
|
@ -290,7 +307,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_440_YUVY,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_PD120] = { // N7CXI, 2000
|
||||
.Name = "PD-120",
|
||||
|
@ -305,7 +323,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_440_YUVY,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_PD160] = { // N7CXI, 2000
|
||||
.Name = "PD-160",
|
||||
|
@ -320,7 +339,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_440_YUVY,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_PD180] = { // N7CXI, 2000
|
||||
.Name = "PD-180",
|
||||
|
@ -335,7 +355,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_440_YUVY,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_PD240] = { // N7CXI, 2000
|
||||
.Name = "PD-240",
|
||||
|
@ -350,7 +371,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_440_YUVY,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_PD290] = { // N7CXI, 2000
|
||||
.Name = "PD-290",
|
||||
|
@ -365,7 +387,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 0,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_440_YUVY,
|
||||
.ColorEnc = COLOR_YUV },
|
||||
.ColorEnc = COLOR_YUV,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_P3] = { // N7CXI, 2000
|
||||
.Name = "Pasokon P3",
|
||||
|
@ -380,7 +403,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 1.042e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_RGB },
|
||||
.ColorEnc = COLOR_RGB,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_P5] = { // N7CXI, 2000
|
||||
.Name = "Pasokon P5",
|
||||
|
@ -395,7 +419,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 1.563e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_RGB },
|
||||
.ColorEnc = COLOR_RGB,
|
||||
.VISParity = PARITY_EVEN },
|
||||
|
||||
[MODE_P7] = { // N7CXI, 2000
|
||||
.Name = "Pasokon P7",
|
||||
|
@ -410,7 +435,8 @@ _ModeSpec ModeSpec[] = {
|
|||
.tSep = 2.083e-3,
|
||||
.SyncOrder = SYNC_SIMPLE,
|
||||
.SubSampling = SUBSAMP_444,
|
||||
.ColorEnc = COLOR_RGB }
|
||||
.ColorEnc = COLOR_RGB,
|
||||
.VISParity = PARITY_EVEN }
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -1,14 +1,32 @@
|
|||
#include <getopt.h>
|
||||
#include "common.hh"
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
std::string confpath(std::string(getenv("HOME")) + "/.config/slowrx/slowrx.ini");
|
||||
config.load_from_file(confpath);
|
||||
|
||||
DSPworker dsp;
|
||||
|
||||
int opt_char;
|
||||
while ((opt_char = getopt (argc, argv, "t:f:")) != EOF)
|
||||
switch (opt_char) {
|
||||
case 't':
|
||||
runTest(optarg);
|
||||
return(0);
|
||||
break;
|
||||
case 'f':
|
||||
dsp.openAudioFile(optarg);
|
||||
break;
|
||||
}
|
||||
|
||||
//dsp.openAudioFile("/Users/windy/Audio/sig/sstv/scottie2-01-noiseonly.wav");
|
||||
dsp.openPortAudio();
|
||||
upsampleLanczos({0},10);
|
||||
|
||||
|
||||
if (!dsp.is_open())
|
||||
dsp.openPortAudio();
|
||||
|
||||
GetVideo(GetVIS(&dsp), &dsp);
|
||||
|
||||
//SlowGUI gui = SlowGUI();
|
||||
|
|
170
src/sync.cc
170
src/sync.cc
|
@ -1,126 +1,52 @@
|
|||
#include "common.hh"
|
||||
|
||||
/* Find the slant angle of the sync singnal and adjust sample rate to cancel it out
|
||||
* Length: number of PCM samples to process
|
||||
* Mode: one of M1, M2, S1, S2, R72, R36 ...
|
||||
* Rate: approximate sampling rate used
|
||||
* Skip: pointer to variable where the skip amount will be returned
|
||||
* returns adjusted sample rate
|
||||
*
|
||||
*/
|
||||
double FindSync (SSTVMode Mode, double Rate, int *Skip) {
|
||||
|
||||
int LineWidth = ModeSpec[Mode].tLine / ModeSpec[Mode].tSync * 4;
|
||||
int x,y;
|
||||
int q, d, qMost, dMost;
|
||||
gushort xAcc[700] = {0};
|
||||
gushort lines[600][(MAXSLANT-MINSLANT)*2];
|
||||
gushort cy, cx, Retries = 0;
|
||||
bool SyncImg[700][630] = {{FALSE}};
|
||||
double t=0, slantAngle, s;
|
||||
double ConvoFilter[8] = { 1,1,1,1,-1,-1,-1,-1 };
|
||||
double convd, maxconvd=0;
|
||||
int xmax=0;
|
||||
|
||||
// Repeat until slant < 0.5° or until we give up
|
||||
while (TRUE) {
|
||||
|
||||
// Draw the 2D sync signal at current rate
|
||||
|
||||
for (y=0; y<ModeSpec[Mode].NumLines; y++) {
|
||||
for (x=0; x<LineWidth; x++) {
|
||||
t = (y + 1.0*x/LineWidth) * ModeSpec[Mode].tLine;
|
||||
SyncImg[x][y] = HasSync[ (int)( t * Rate / 13.0) ];
|
||||
}
|
||||
}
|
||||
|
||||
/** Linear Hough transform **/
|
||||
|
||||
dMost = qMost = 0;
|
||||
memset(lines, 0, sizeof(lines[0][0]) * (MAXSLANT-MINSLANT)*2 * 600);
|
||||
|
||||
// Find white pixels
|
||||
for (cy = 0; cy < ModeSpec[Mode].NumLines; cy++) {
|
||||
for (cx = 0; cx < LineWidth; cx++) {
|
||||
if (SyncImg[cx][cy]) {
|
||||
|
||||
// Slant angles to consider
|
||||
for (q = MINSLANT*2; q < MAXSLANT*2; q ++) {
|
||||
|
||||
// Line accumulator
|
||||
d = LineWidth + round( -cx * sin(deg2rad(q/2.0)) + cy * cos(deg2rad(q/2.0)) );
|
||||
if (d > 0 && d < LineWidth) {
|
||||
lines[d][q-MINSLANT*2] ++;
|
||||
if (lines[d][q-MINSLANT*2] > lines[dMost][qMost-MINSLANT*2]) {
|
||||
dMost = d;
|
||||
qMost = q;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( qMost == 0) {
|
||||
printf(" no sync signal; giving up\n");
|
||||
break;
|
||||
}
|
||||
|
||||
slantAngle = qMost / 2.0;
|
||||
|
||||
printf(" %.1f° (d=%d) @ %.1f Hz", slantAngle, dMost, Rate);
|
||||
|
||||
// Adjust sample rate
|
||||
Rate += tan(deg2rad(90 - slantAngle)) / LineWidth * Rate;
|
||||
|
||||
if (slantAngle > 89 && slantAngle < 91) {
|
||||
printf(" slant OK :)\n");
|
||||
break;
|
||||
} else if (Retries == 3) {
|
||||
printf(" still slanted; giving up\n");
|
||||
Rate = 44100;
|
||||
printf(" -> 44100\n");
|
||||
break;
|
||||
}
|
||||
printf(" -> %.1f recalculating\n", Rate);
|
||||
Retries ++;
|
||||
}
|
||||
|
||||
// accumulate a 1-dim array of the position of the sync pulse
|
||||
memset(xAcc, 0, sizeof(xAcc[0]) * 700);
|
||||
for (y=0; y<ModeSpec[Mode].NumLines; y++) {
|
||||
for (x=0; x<700; x++) {
|
||||
t = y * ModeSpec[Mode].tLine + x/700.0 * ModeSpec[Mode].tLine;
|
||||
xAcc[x] += HasSync[ (int)(t / (13.0/44100) * Rate/44100) ];
|
||||
}
|
||||
}
|
||||
|
||||
// find falling edge of the sync pulse by 8-point convolution
|
||||
for (x=0;x<700-8;x++) {
|
||||
convd = 0;
|
||||
for (int i=0;i<8;i++) convd += xAcc[x+i] * ConvoFilter[i];
|
||||
if (convd > maxconvd) {
|
||||
maxconvd = convd;
|
||||
xmax = x+4;
|
||||
}
|
||||
}
|
||||
|
||||
// If pulse is near the right edge of the image, it just probably slipped
|
||||
// out the left edge
|
||||
if (xmax > 350) xmax -= 350;
|
||||
|
||||
// Skip until the start of the line
|
||||
s = xmax / 700.0 * ModeSpec[Mode].tLine - ModeSpec[Mode].tSync;
|
||||
|
||||
// (Scottie modes don't start lines with sync)
|
||||
if (ModeSpec[Mode].SyncOrder == SYNC_SCOTTIE)
|
||||
s = s - ModeSpec[Mode].tPixel * ModeSpec[Mode].ImgWidth / 2.0
|
||||
+ ModeSpec[Mode].tPorch * 2;
|
||||
|
||||
*Skip = s * Rate;
|
||||
|
||||
printf("will return %.2f\n",Rate);
|
||||
|
||||
return (Rate);
|
||||
double findSyncHough (SSTVMode Mode, std::vector<bool> has_sync) {
|
||||
|
||||
}
|
||||
|
||||
void findSyncRansac(SSTVMode Mode, std::vector<bool> has_sync) {
|
||||
int line_width = ModeSpec[Mode].NumLines;//ModeSpec[Mode].tLine / ModeSpec[Mode].tSync * 4;
|
||||
std::vector<std::pair<int,int> > sync_pixels;
|
||||
for (int y=0; y<ModeSpec[Mode].NumLines; y++) {
|
||||
for (int x=0; x<line_width; x++) {
|
||||
if (y+x>0 && has_sync[y*line_width + x] && !has_sync[y*line_width + x - 1]) {
|
||||
sync_pixels.push_back({x,y});
|
||||
printf("%d,%d\n",x,y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::pair<int,int>, std::pair<int,int> > best_line;
|
||||
double best_dist = -1;
|
||||
int it_num = 0;
|
||||
while (++it_num < 300) {
|
||||
|
||||
std::random_shuffle(sync_pixels.begin(), sync_pixels.end());
|
||||
|
||||
std::pair<std::pair<int,int>, std::pair<int,int> > test_line = {sync_pixels[0], sync_pixels[1]};
|
||||
int x0 = test_line.first.first;
|
||||
int y0 = test_line.first.second;
|
||||
int x1 = test_line.second.first;
|
||||
int y1 = test_line.second.second;
|
||||
double total_sq_dist = 0;
|
||||
int total_good = 0;
|
||||
|
||||
for(std::pair<int,int> pixel : sync_pixels) {
|
||||
int x = pixel.first;
|
||||
int y = pixel.second;
|
||||
// Point distance to line
|
||||
double d = 1.0 * ((y0-y1)*x + (x1-x0)*y + (x0*y1 - x1*y0)) / sqrt(pow(x1-x0,2) + pow(y1-y0,2));
|
||||
|
||||
if (fabs(d) < 6 || fabs(d-line_width) < 6) {
|
||||
total_good ++;//+= sqrt(fabs(d));
|
||||
}
|
||||
|
||||
}
|
||||
if (best_dist < 0 || total_good > best_dist) {
|
||||
best_line = test_line;
|
||||
best_dist = total_good;
|
||||
printf("(it. %d) for (%d,%d) (%d,%d) total_sq_dist=%f, total_good=%d\n",it_num,x0,y0,x1,y1,total_sq_dist,total_good);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
121
src/video.cc
121
src/video.cc
|
@ -169,13 +169,20 @@ bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
|
|||
_ModeSpec s = ModeSpec[Mode];
|
||||
|
||||
guint8 Image[800][800][3];
|
||||
guint8 Imagesnr[800][800][3];
|
||||
|
||||
Glib::RefPtr<Gtk::Application> app = Gtk::Application::create("com.windytan.slowrx");
|
||||
|
||||
// Initialize pixbuffer
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf_rx;
|
||||
pixbuf_rx = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, s.ScanPixels, s.NumLines);
|
||||
pixbuf_rx->fill(0x000000ff);
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf_snr;
|
||||
pixbuf_snr = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, s.ScanPixels, s.NumLines);
|
||||
pixbuf_snr->fill(0x000000ff);
|
||||
|
||||
double next_sync_sample_time = 0;
|
||||
std::vector<bool> has_sync;
|
||||
|
||||
/*g_object_unref(pixbuf_disp);
|
||||
pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500,
|
||||
|
@ -192,119 +199,72 @@ bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
|
|||
double t = 0;
|
||||
for (int PixelIdx = 0; PixelIdx < PixelGrid.size(); PixelIdx++) {
|
||||
|
||||
double Lum = 0;
|
||||
/*printf("expecting %d (%d,%d,%d)\n",PixelIdx,
|
||||
PixelGrid[PixelIdx].X,
|
||||
PixelGrid[PixelIdx].Y,
|
||||
PixelGrid[PixelIdx].Channel
|
||||
);*/
|
||||
|
||||
while (t < PixelGrid[PixelIdx].Time && dsp->is_still_listening()) {
|
||||
while (t < PixelGrid[PixelIdx].Time && dsp->is_open()) {
|
||||
t += dsp->forward();
|
||||
}
|
||||
|
||||
/*if (dsp->is_open()) {
|
||||
printf("got it\n");
|
||||
} else {
|
||||
printf("didn't get it\n");
|
||||
}*/
|
||||
|
||||
/*** Store the sync band for later adjustments ***/
|
||||
|
||||
/*if (SampleNum == NextSyncTime) {
|
||||
if (dsp->get_t() >= next_sync_sample_time) {
|
||||
|
||||
Praw = Psync = 0;
|
||||
int line_width = ModeSpec[Mode].NumLines;
|
||||
|
||||
memset(fft.in, 0, sizeof(double)*FFTLen);
|
||||
|
||||
// Hann window
|
||||
for (i = 0; i < 64; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr+i-32] / 32768.0 * Hann[1][i];
|
||||
|
||||
fftw_execute(fft.Plan1024);
|
||||
|
||||
for (i=GetBin(1500+CurrentPic.HedrShift,FFTLen); i<=GetBin(2300+CurrentPic.HedrShift, FFTLen); i++)
|
||||
Praw += power(fft.out[i]);
|
||||
|
||||
for (i=SyncTargetBin-1; i<=SyncTargetBin+1; i++)
|
||||
Psync += power(fft.out[i]) * (1- .5*abs(SyncTargetBin-i));
|
||||
|
||||
Praw /= (GetBin(2300+CurrentPic.HedrShift, FFTLen) - GetBin(1500+CurrentPic.HedrShift, FFTLen));
|
||||
Psync /= 2.0;
|
||||
std::vector<double> bands = dsp->bandPowerPerHz({{1150,1250}, {1500,2300}});
|
||||
|
||||
// If there is more than twice the amount of power per Hz in the
|
||||
// sync band than in the video band, we have a sync signal here
|
||||
HasSync[SyncSampleNum] = (Psync > 2*Praw);
|
||||
has_sync.push_back(bands[0] > bands[1]);
|
||||
|
||||
NextSyncTime += 13;
|
||||
SyncSampleNum ++;
|
||||
|
||||
}*/
|
||||
next_sync_sample_time += ModeSpec[Mode].tLine / line_width;
|
||||
|
||||
}
|
||||
|
||||
/*** Estimate SNR ***/
|
||||
|
||||
double SNR;
|
||||
bool Adaptive = true;
|
||||
|
||||
if (PixelIdx == 0 || PixelGrid[PixelIdx].X == s.ScanPixels/2) {
|
||||
std::vector<double> bands = dsp->getBandPowerPerHz({{400,800}, {1500,2300}, {2700, 3400}});
|
||||
if (PixelIdx == 0 || (Adaptive && PixelGrid[PixelIdx].X == s.ScanPixels/2)) {
|
||||
std::vector<double> bands = dsp->bandPowerPerHz({{300,1100}, {1500,2300}, {2500, 2700}});
|
||||
double Pvideo_plus_noise = bands[1];
|
||||
double Pnoise_only = (bands[0] + bands[2]) / 2;
|
||||
double Psignal = Pvideo_plus_noise - Pnoise_only;
|
||||
|
||||
SNR = ((Psignal / Pnoise_only < .01) ? -20 : 10 * log10(Psignal / Pnoise_only));
|
||||
SNR = ((Pnoise_only == 0 || Psignal / Pnoise_only < .01) ? -20 : 10 * log10(Psignal / Pnoise_only));
|
||||
|
||||
}
|
||||
|
||||
/*if (SampleNum == NextSNRtime) {
|
||||
|
||||
memset(fft.in, 0, sizeof(double)*FFTLen);
|
||||
|
||||
// Apply Hann window
|
||||
for (i = 0; i < FFTLen; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - FFTLen/2] / 32768.0 * Hann[6][i];
|
||||
|
||||
fftw_execute(fft.Plan1024);
|
||||
|
||||
// Calculate video-plus-noise power (1500-2300 Hz)
|
||||
|
||||
Pvideo_plus_noise = 0;
|
||||
for (n = GetBin(1500+CurrentPic.HedrShift, FFTLen); n <= GetBin(2300+CurrentPic.HedrShift, FFTLen); n++)
|
||||
Pvideo_plus_noise += power(fft.out[n]);
|
||||
|
||||
// Calculate noise-only power (400-800 Hz + 2700-3400 Hz)
|
||||
|
||||
Pnoise_only = 0;
|
||||
for (n = GetBin(400+CurrentPic.HedrShift, FFTLen); n <= GetBin(800+CurrentPic.HedrShift, FFTLen); n++)
|
||||
Pnoise_only += power(fft.out[n]);
|
||||
|
||||
for (n = GetBin(2700+CurrentPic.HedrShift, FFTLen); n <= GetBin(3400+CurrentPic.HedrShift, FFTLen); n++)
|
||||
Pnoise_only += power(fft.out[n]);
|
||||
|
||||
// Bandwidths
|
||||
VideoPlusNoiseBins = GetBin(2300, FFTLen) - GetBin(1500, FFTLen) + 1;
|
||||
|
||||
NoiseOnlyBins = GetBin(800, FFTLen) - GetBin(400, FFTLen) + 1 +
|
||||
GetBin(3400, FFTLen) - GetBin(2700, FFTLen) + 1;
|
||||
|
||||
ReceiverBins = GetBin(3400, FFTLen) - GetBin(400, FFTLen);
|
||||
|
||||
// Eq 15
|
||||
Pnoise = Pnoise_only * (1.0 * ReceiverBins / NoiseOnlyBins);
|
||||
Psignal = Pvideo_plus_noise - Pnoise_only * (1.0 * VideoPlusNoiseBins / NoiseOnlyBins);
|
||||
|
||||
// Lower bound to -20 dB
|
||||
SNR = ((Psignal / Pnoise < .01) ? -20 : 10 * log10(Psignal / Pnoise));
|
||||
|
||||
NextSNRtime += 256;
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
/*** FM demodulation ***/
|
||||
|
||||
//PrevFreq = Freq;
|
||||
|
||||
// Adapt window size to SNR
|
||||
bool Adaptive = true;
|
||||
WindowType WinType;
|
||||
|
||||
if (Adaptive) WinType = dsp->getBestWindowFor(Mode, SNR);
|
||||
else WinType = dsp->getBestWindowFor(Mode);
|
||||
if (Adaptive) WinType = dsp->bestWindowFor(Mode, SNR);
|
||||
else WinType = dsp->bestWindowFor(Mode);
|
||||
|
||||
double Freq = dsp->getPeakFreq(1500, 2300, WinType);
|
||||
double Freq = dsp->peakFreq(1500, 2300, WinType);
|
||||
|
||||
// Linear interpolation of (chronologically) intermediate frequencies, for redrawing
|
||||
//InterpFreq = PrevFreq + (Freq-PrevFreq) * ... // TODO!
|
||||
|
||||
// Calculate luminency & store for later use
|
||||
Lum = clip((Freq - (1500)) / 3.1372549);
|
||||
guint8 Lum = freq2lum(Freq);
|
||||
//measured.push_back({t, Lum});
|
||||
//StoredLum[SampleNum] = clip((Freq - (1500 + CurrentPic.HedrShift)) / 3.1372549);
|
||||
|
||||
|
@ -314,11 +274,15 @@ bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
|
|||
|
||||
// Store pixel
|
||||
Image[x][y][Channel] = Lum;//StoredLum[SampleNum];
|
||||
Imagesnr[x][y][Channel] = WinType;//StoredLum[SampleNum];
|
||||
|
||||
}
|
||||
|
||||
// Calculate and draw pixels to pixbuf on line change
|
||||
/* sync */
|
||||
findSyncRansac(Mode, has_sync);
|
||||
|
||||
toPixbufRGB(Image, pixbuf_rx, Mode);
|
||||
toPixbufRGB(Imagesnr, pixbuf_snr, Mode);
|
||||
/*if (!Redraw || y % 5 == 0 || PixelIdx == PixelGrid.size()-1) {
|
||||
// Scale and update image
|
||||
g_object_unref(pixbuf_disp);
|
||||
|
@ -329,11 +293,8 @@ bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
|
|||
}*/
|
||||
|
||||
|
||||
/*if (!Redraw && SampleNum % 8820 == 0) {
|
||||
setVU(Power, FFTLen, WinIdx, true);
|
||||
}*/
|
||||
|
||||
pixbuf_rx->save("testi.png", "png");
|
||||
pixbuf_snr->save("snr.png", "png");
|
||||
|
||||
return true;
|
||||
|
||||
|
|
175
src/vis.cc
175
src/vis.cc
|
@ -3,135 +3,116 @@
|
|||
|
||||
/*
|
||||
*
|
||||
* Detect VIS & frequency shift
|
||||
*
|
||||
* Each bit lasts 30 ms (1323 samples)
|
||||
* Detect Robot header & VIS
|
||||
*
|
||||
*/
|
||||
|
||||
SSTVMode GetVIS (DSPworker *dsp) {
|
||||
|
||||
double dt = 5e-3;
|
||||
int bitlen = 30e-3 / dt;
|
||||
int upsample_factor = 10;
|
||||
|
||||
int selmode, ptr=0;
|
||||
int vis = 0, Parity = 0, HedrPtr = 0;
|
||||
double HedrBuf[100] = {0}, tone[100] = {0};
|
||||
int vis = 0, Parity = 0, ReadPtr = 0;
|
||||
std::vector<double> HedrCirBuf(bitlen*22);
|
||||
std::vector<double> delta_f(bitlen*22);
|
||||
std::vector<double> tone(bitlen*22);
|
||||
bool gotvis = false;
|
||||
unsigned Bit[8] = {0}, ParityBit = 0;
|
||||
|
||||
//for (i = 0; i < FFTLen; i++) fft.in[i] = 0;
|
||||
|
||||
// Create 20ms Hann window
|
||||
//for (i = 0; i < 882; i++) Hann[i] = 0.5 * (1 - cos( (2 * M_PI * (double)i) / 881 ) );
|
||||
printf("bitlen=%d\n",bitlen);
|
||||
|
||||
//ManualActivated = false;
|
||||
|
||||
printf("Waiting for header\n");
|
||||
|
||||
while ( dsp->is_still_listening() ) {
|
||||
while ( dsp->is_open() ) {
|
||||
|
||||
//if (Abort || ManualResync) return(0);
|
||||
HedrCirBuf[ReadPtr] = dsp->peakFreq(500, 3300, WINDOW_HANN1023);
|
||||
|
||||
HedrBuf[HedrPtr] = dsp->getPeakFreq(500, 3300, WINDOW_HANN1023);
|
||||
dsp->forward_ms(10);
|
||||
//printf("%f,%f\n",dsp->get_t(),HedrCirBuf[ReadPtr]);
|
||||
|
||||
// Header buffer holds 45 * 10 msec = 450 msec
|
||||
HedrPtr = (HedrPtr + 1) % 45;
|
||||
|
||||
// Frequencies in the last 450 msec
|
||||
for (int i = 0; i < 45; i++) tone[i] = HedrBuf[(HedrPtr + i) % 45];
|
||||
delta_f[0] = HedrCirBuf[(ReadPtr+1) % HedrCirBuf.size()];
|
||||
for (int i = 1; i < HedrCirBuf.size(); i++) {
|
||||
delta_f[i] = HedrCirBuf[(ReadPtr + 1 + i) % HedrCirBuf.size()] - delta_f[0];
|
||||
}
|
||||
|
||||
// Is there a pattern that looks like (the end of) a calibration header + VIS?
|
||||
// Tolerance ±25 Hz
|
||||
CurrentPic.HedrShift = 0;
|
||||
gotvis = false;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
//if (CurrentPic.HedrShift != 0) break;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if ( (tone[1*3+i] > tone[0+j] - 25 && tone[1*3+i] < tone[0+j] + 25) && // 1900 Hz leader
|
||||
(tone[2*3+i] > tone[0+j] - 25 && tone[2*3+i] < tone[0+j] + 25) && // 1900 Hz leader
|
||||
(tone[3*3+i] > tone[0+j] - 25 && tone[3*3+i] < tone[0+j] + 25) && // 1900 Hz leader
|
||||
(tone[4*3+i] > tone[0+j] - 25 && tone[4*3+i] < tone[0+j] + 25) && // 1900 Hz leader
|
||||
(tone[5*3+i] > tone[0+j] - 725 && tone[5*3+i] < tone[0+j] - 675) && // 1200 Hz start bit
|
||||
// ...8 VIS bits...
|
||||
(tone[14*3+i] > tone[0+j] - 725 && tone[14*3+i] < tone[0+j] - 675) // 1200 Hz stop bit
|
||||
) {
|
||||
double freq_margin = 25;
|
||||
|
||||
// Attempt to read VIS
|
||||
if ( fabs(delta_f[4*bitlen]) < freq_margin && fabs(delta_f[6*bitlen]) < freq_margin &&
|
||||
fabs(delta_f[8*bitlen]) < freq_margin && fabs(delta_f[10*bitlen]) < freq_margin &&
|
||||
fabs(delta_f[12*bitlen]+(1900-1200)) < freq_margin &&
|
||||
fabs(delta_f[21*bitlen]+(1900-1200)) < freq_margin ) {
|
||||
|
||||
gotvis = true;
|
||||
for (int k = 0; k < 8; k++) {
|
||||
if (tone[6*3+i+3*k] > tone[0+j] - 625 && tone[6*3+i+3*k] < tone[0+j] - 575) Bit[k] = 0;
|
||||
else if (tone[6*3+i+3*k] > tone[0+j] - 825 && tone[6*3+i+3*k] < tone[0+j] - 775) Bit[k] = 1;
|
||||
else { // erroneous bit
|
||||
gotvis = false;
|
||||
break;
|
||||
}
|
||||
double fshift = ((delta_f[0] - 1900) + delta_f[4*bitlen] + delta_f[6*bitlen] + delta_f[8*bitlen] +
|
||||
delta_f[10*bitlen]) / 5.0;
|
||||
|
||||
printf("header! t=%f, fshift=%+.1f Hz\n",dsp->get_t(),fshift);
|
||||
|
||||
// hi-res zero-crossing search
|
||||
std::vector<double> abs_f(delta_f.size());
|
||||
for (int i=1; i<abs_f.size(); i++)
|
||||
abs_f[i] += delta_f[0] + delta_f[i];
|
||||
std::vector<double> header_interp = upsampleLanczos(abs_f, upsample_factor);
|
||||
|
||||
int n_zc = 0;
|
||||
double t_zc1=0, t_zc2=0;
|
||||
double s=0,s0=0;
|
||||
for (int i=0; i<header_interp.size(); i++) {
|
||||
s = header_interp[i] - fshift - (1200.0+(1900.0-1200.0)/2.0);
|
||||
double t = dsp->get_t() - (header_interp.size()-1-i)*dt/upsample_factor;
|
||||
//printf("%f,%f\n",t,s);
|
||||
if (i*dt/upsample_factor>15e-3 && s * s0 < 0) {
|
||||
if (n_zc == 1) {
|
||||
t_zc1 = t - dt + ((-s0)/(s-s0))*dt/upsample_factor;
|
||||
}
|
||||
if (gotvis) {
|
||||
CurrentPic.HedrShift = tone[0+j] - 1900;
|
||||
|
||||
vis = Bit[0] + (Bit[1] << 1) + (Bit[2] << 2) + (Bit[3] << 3) + (Bit[4] << 4) +
|
||||
(Bit[5] << 5) + (Bit[6] << 6);
|
||||
ParityBit = Bit[7];
|
||||
|
||||
printf(" VIS %d (%02Xh) @ %+d Hz\n", vis, vis, CurrentPic.HedrShift);
|
||||
|
||||
Parity = Bit[0] ^ Bit[1] ^ Bit[2] ^ Bit[3] ^ Bit[4] ^ Bit[5] ^ Bit[6];
|
||||
|
||||
if (vis2mode.find(vis) == vis2mode.end()) {
|
||||
printf(" Unknown VIS\n");
|
||||
gotvis = false;
|
||||
} else if (Parity != ParityBit && vis2mode[vis] != MODE_R12BW) {
|
||||
printf(" Parity fail\n");
|
||||
gotvis = false;
|
||||
} else {
|
||||
|
||||
//gtk_combo_box_set_active (GTK_COMBO_BOX(gui.combo_mode), VISmap[VIS]-1);
|
||||
//gtk_spin_button_set_value (GTK_SPIN_BUTTON(gui.spin_shift), CurrentPic.HedrShift);
|
||||
break;
|
||||
}
|
||||
if (n_zc == 2) {
|
||||
t_zc2 = t - dt + ((-s0)/(s-s0))*dt/upsample_factor;
|
||||
break;
|
||||
}
|
||||
n_zc++;
|
||||
|
||||
}
|
||||
s0 = s;
|
||||
}
|
||||
|
||||
/*printf ("n_zc=%d, t_zc1=%f, t_zc2=%fi (dur = %.05f ms, error = %.03f%%)\n",n_zc,t_zc1,t_zc2,
|
||||
(t_zc2-t_zc1)*1e3,
|
||||
(t_zc2 - t_zc1) / 300e-3 * 100 - 100
|
||||
);*/
|
||||
|
||||
int parity_rx=0;
|
||||
for (int k=0; k<8; k++) {
|
||||
if (abs_f[(13+k)*bitlen]+fshift < 1200) {
|
||||
vis |= (1 << k);
|
||||
parity_rx ++;
|
||||
}
|
||||
}
|
||||
if (gotvis) break;
|
||||
}
|
||||
if (gotvis) break;
|
||||
vis &= 0x7f;
|
||||
|
||||
//if (gotvis)
|
||||
//if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_rx))) break;
|
||||
|
||||
// Manual start
|
||||
/*if (ManualActivated) {
|
||||
|
||||
gtk_widget_set_sensitive( gui.frame_manual, false );
|
||||
gtk_widget_set_sensitive( gui.combo_card, false );
|
||||
|
||||
selmode = gtk_combo_box_get_active (GTK_COMBO_BOX(gui.combo_mode)) + 1;
|
||||
CurrentPic.HedrShift = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(gui.spin_shift));
|
||||
VIS = 0;
|
||||
for (i=0; i<0x80; i++) {
|
||||
if (VISmap[i] == selmode) {
|
||||
VIS = i;
|
||||
printf("VIS: %dd (%02Xh) @ %+f Hz\n", vis, vis, fshift);
|
||||
if (vis2mode.find(vis) == vis2mode.end()) {
|
||||
printf("Unknown VIS\n");
|
||||
gotvis = false;
|
||||
} else {
|
||||
if ((parity_rx % 2) != ModeSpec[vis2mode[vis]].VISParity) {
|
||||
printf("Parity fail\n");
|
||||
gotvis = false;
|
||||
} else {
|
||||
printf("%s\n",ModeSpec[vis2mode[vis]].Name.c_str());
|
||||
double start_of_video = t_zc2 + 10 * 30e-3;
|
||||
dsp->forward_to_time(start_of_video);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (++ptr == 10) {
|
||||
setVU(Power, 2048, 6, false);
|
||||
ptr = 0;
|
||||
}*/
|
||||
|
||||
ReadPtr = (ReadPtr+1) % HedrCirBuf.size();
|
||||
dsp->forward_time(dt);
|
||||
}
|
||||
|
||||
// Skip the rest of the stop bit
|
||||
dsp->forward_ms(12); // TODO hack
|
||||
|
||||
if (vis2mode.find(vis) != vis2mode.end()) {
|
||||
return vis2mode[vis];
|
||||
}
|
||||
return vis2mode[vis];
|
||||
|
||||
printf(" No VIS found\n");
|
||||
return MODE_UNKNOWN;
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue