freeswitch/libs/libcodec2/fltk/fl_fdmdv.cxx
2012-12-20 20:17:20 -06:00

1159 lines
29 KiB
C++

/*
fl_fdmdv.cxx
Created 14 June 2012
David Rowe
Fltk 1.3 based GUI program to prototype FDMDV & Codec 2 integration
issues such as:
+ spectrum, waterfall, and other FDMDV GUI displays
+ integration with real time audio I/O using portaudio
+ what we do with audio when out of sync
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Group.H>
#include <FL/names.h>
#include "portaudio.h"
#include "fdmdv.h"
#include "codec2.h"
#define MIN_DB -40.0
#define MAX_DB 0.0
#define BETA 0.1 // constant for time averageing spectrum data
#define MIN_HZ 0
#define MAX_HZ 4000
#define WATERFALL_SECS_Y 5 // number of seconds respresented by y axis of waterfall
#define DT 0.02 // time between samples
#define FS 8000 // FDMDV modem sample rate
#define SCATTER_MEM (FDMDV_NSYM)*50
#define SCATTER_X_MAX 3.0
#define SCATTER_Y_MAX 3.0
// main window params
#define W 1200
#define W3 (W/3)
#define H 600
#define H2 (H/2)
#define SP 20
// sound card
#define SAMPLE_RATE 48000 /* 48 kHz sampling rate rec. as we
can trust accuracy of sound
card */
#define N8 FDMDV_NOM_SAMPLES_PER_FRAME /* processing buffer size at 8 kHz */
#define MEM8 (FDMDV_OS_TAPS/FDMDV_OS)
#define N48 (N8*FDMDV_OS) /* processing buffer size at 48 kHz */
#define NUM_CHANNELS 2 /* I think most sound cards prefer stereo,
we will convert to mono */
#define BITS_PER_CODEC_FRAME (2*FDMDV_BITS_PER_FRAME)
#define BYTES_PER_CODEC_FRAME (BITS_PER_CODEC_FRAME/8)
// forward class declarations
class Spectrum;
class Waterfall;
class Scatter;
class Scalar;
// Globals --------------------------------------
char *fin_name = NULL;
char *fout_name = NULL;
char *sound_dev_name = NULL;
FILE *fin = NULL;
FILE *fout = NULL;
struct FDMDV *fdmdv;
struct CODEC2 *codec2;
float av_mag[FDMDV_NSPEC]; // shared between a few classes
// GUI variables --------------------------------
Fl_Group *agroup;
Fl_Window *window;
Fl_Window *zoomSpectrumWindow = NULL;
Fl_Window *zoomWaterfallWindow = NULL;
Spectrum *aSpectrum;
Spectrum *aZoomedSpectrum;
Waterfall *aWaterfall;
Waterfall *aZoomedWaterfall;
Scatter *aScatter;
Scalar *aTimingEst;
Scalar *aFreqEst;
Scalar *aSNR;
int zoom_spectrum = 0;
// Main processing loop states ------------------
float Ts = 0.0;
short input_buf[2*FDMDV_NOM_SAMPLES_PER_FRAME];
int n_input_buf = 0;
int nin = FDMDV_NOM_SAMPLES_PER_FRAME;
short *output_buf;
int n_output_buf = 0;
int codec_bits[2*FDMDV_BITS_PER_FRAME];
int state = 0;
// Portaudio states -----------------------------
PaStream *stream = NULL;
PaError err;
typedef struct {
float in48k[FDMDV_OS_TAPS + N48];
float in8k[MEM8 + N8];
} paCallBackData;
// Class for each window type ------------------
class Spectrum: public Fl_Box {
protected:
int handle(int event) {
// detect a left mouse down if inside the spectrum window
if ((event == FL_NO_EVENT) && (Fl::event_button() == 1)) {
if ((Fl::event_x() > x()) && (Fl::event_x() < (x() + w())) &&
(Fl::event_y() > y()) && (Fl::event_y() < (y() + h()))) {
// show zoomed spectrum window
zoomSpectrumWindow->show();
}
}
return 0;
}
void draw() {
float x_px_per_point = 0.0;
float y_px_per_dB = 0.0;
int i, x1, y1, x2, y2;
float mag1, mag2;
char label[20];
float px_per_hz;
Fl_Box::draw();
fl_color(FL_BLACK);
fl_rectf(x(),y(),w(),h());
fl_color(FL_GREEN);
fl_line_style(FL_SOLID);
fl_push_clip(x(),y(),w(),h());
//printf("%d %d\n", w(), h());
x_px_per_point = (float)w()/FDMDV_NSPEC;
y_px_per_dB = (float)h()/(MAX_DB - MIN_DB);
// plot spectrum
for(i=0; i<FDMDV_NSPEC-1; i++) {
mag1 = av_mag[i];
mag2 = av_mag[i+1];
x1 = x() + i*x_px_per_point;
y1 = y() + -mag1*y_px_per_dB;
x2 = x() + (i+1)*x_px_per_point;
y2 = y() + -mag2*y_px_per_dB;
fl_line(x1,y1,x2,y2);
}
// y axis graticule
fl_line_style(FL_DOT);
for(i=MIN_DB; i<MAX_DB; i+=10) {
x1 = x();
y1 = y() + -i*y_px_per_dB;
x2 = x() + w();
y2 = y1;
//printf("%d %d %d %d\n", x1, y1, x2, y2);
fl_line(x1,y1,x2,y2);
sprintf(label, "%d", i);
fl_draw(label, x1, y1);
}
// x axis graticule
px_per_hz = (float)w()/(MAX_HZ-MIN_HZ);
fl_line_style(FL_DOT);
for(i=500; i<MAX_HZ; i+=500) {
x1 = x() + i*px_per_hz;
y1 = y();
x2 = x1;
y2 = y() + h();
//printf("i=%d %d %d %d %d\n", i, x1, y1, x2, y2);
fl_line(x1,y1,x2,y2);
sprintf(label, "%d", i);
fl_draw(label, x1, y2);
}
fl_pop_clip();
}
public:
Spectrum(int x, int y, int w, int h): Fl_Box(x, y, w, h, "Spectrum")
{
align(FL_ALIGN_TOP);
labelsize(10);
};
};
/*
Notes:
The height h() pixels represents WATERFALL_SECS_Y of data. Every DT
seconds we get a vector of FDMDV_NSPEC spectrum samples which we use
to update the last row. The height of each row is dy pixels, which
maps to DT seconds. We call each dy high rectangle of pixels a
block.
*/
class Waterfall: public Fl_Box {
protected:
int prev_w, prev_h;
unsigned *pixel_buf;
unsigned heatmap_lut[256];
int greyscale;
void new_pixel_buf(int w, int h) {
int buf_sz, i;
prev_w = w; prev_h = h;
buf_sz = h*w;
pixel_buf = new unsigned[buf_sz];
for(i=0; i<buf_sz; i++)
pixel_buf[i] = 0;
}
int handle(int event) {
// detect a left mouse down if inside the window
if ((event == FL_NO_EVENT) && (Fl::event_button() == 1)) {
if ((Fl::event_x() > x()) && (Fl::event_x() < (x() + w())) &&
(Fl::event_y() > y()) && (Fl::event_y() < (y() + h()))) {
// show zoomed spectrum window
zoomWaterfallWindow->show();
}
}
return 0;
}
// map val to a rgb colour
// from http://eddiema.ca/2011/01/21/c-sharp-heatmaps/
unsigned heatmap(float val, float min, float max) {
unsigned r = 0;
unsigned g = 0;
unsigned b = 0;
val = (val - min) / (max - min);
if(val <= 0.2) {
b = (unsigned)((val / 0.2) * 255);
} else if(val > 0.2 && val <= 0.7) {
b = (unsigned)((1.0 - ((val - 0.2) / 0.5)) * 255);
}
if(val >= 0.2 && val <= 0.6) {
g = (unsigned)(((val - 0.2) / 0.4) * 255);
} else if(val > 0.6 && val <= 0.9) {
g = (unsigned)((1.0 - ((val - 0.6) / 0.3)) * 255);
}
if(val >= 0.5) {
r = (unsigned)(((val - 0.5) / 0.5) * 255);
}
//printf("%f %x %x %x\n", val, r, g, b);
return (b << 16) + (g << 8) + r;
}
void draw() {
float spec_index_per_px, intensity_per_dB;
int px_per_sec;
int index, dy, dy_blocks, bytes_in_row_of_blocks, b;
int px, py, intensity;
unsigned *last_row, *pdest, *psrc;
/* detect resizing of window */
if ((h() != prev_h) || (w() != prev_w)) {
delete pixel_buf;
new_pixel_buf(w(), h());
}
Fl_Box::draw();
// determine dy, the height of one "block"
px_per_sec = (float)h()/WATERFALL_SECS_Y;
dy = DT*px_per_sec;
// number of dy high blocks in spectrogram
dy_blocks = h()/dy;
// shift previous bit map
bytes_in_row_of_blocks = dy*w()*sizeof(unsigned);
for(b=0; b<dy_blocks-1; b++) {
pdest = pixel_buf + b*w()*dy;
psrc = pixel_buf + (b+1)*w()*dy;
memcpy(pdest, psrc, bytes_in_row_of_blocks);
}
// create a new row of blocks at bottom
spec_index_per_px = (float)FDMDV_NSPEC/(float)w();
intensity_per_dB = (float)256/(MAX_DB - MIN_DB);
last_row = pixel_buf + dy*(dy_blocks - 1)*w();
for(px=0; px<w(); px++) {
index = px*spec_index_per_px;
intensity = intensity_per_dB * (av_mag[index] - MIN_DB);
if (intensity > 255) intensity = 255;
if (intensity < 0) intensity = 0;
if (greyscale) {
for(py=0; py<dy; py++)
last_row[px+py*w()] = intensity<<8;
}
else {
for(py=0; py<dy; py++)
last_row[px+py*w()] = heatmap_lut[intensity];
}
}
// update bit map
fl_draw_image((uchar*)pixel_buf, x(), y(), w(), h(), 4, 0);
}
public:
Waterfall(int x, int y, int w, int h): Fl_Box(x, y, w, h, "Waterfall")
{
int i;
for(i=0; i<255; i++) {
heatmap_lut[i] = heatmap((float)i, 0.0, 255.0);
}
greyscale = 0;
align(FL_ALIGN_TOP);
labelsize(10);
new_pixel_buf(w,h);
};
~Waterfall() {
delete pixel_buf;
}
};
class Scatter: public Fl_Box {
protected:
COMP mem[SCATTER_MEM];
COMP new_samples[FDMDV_NSYM];
int prev_w, prev_h, prev_x, prev_y;
void draw() {
float x_scale;
float y_scale;
int i, j, x1, y1;
Fl_Box::draw();
/* detect resizing of window */
if ((h() != prev_h) || (w() != prev_w) || (x() != prev_x) || (y() != prev_y)) {
fl_color(FL_BLACK);
fl_rectf(x(),y(),w(),h());
prev_h = h(); prev_w = w(); prev_x = x(); prev_y = y();
}
fl_push_clip(x(),y(),w(),h());
x_scale = w()/SCATTER_X_MAX;
y_scale = h()/SCATTER_Y_MAX;
// erase last samples
fl_color(FL_BLACK);
for(i=0; i<FDMDV_NSYM; i++) {
x1 = x_scale * mem[i].real + x() + w()/2;
y1 = y_scale * mem[i].imag + y() + h()/2;
fl_point(x1, y1);
mem[i] = mem[i+FDMDV_NSYM];
}
// shift memory
for(i=FDMDV_NSYM; i<SCATTER_MEM-FDMDV_NSYM; i++) {
mem[i] = mem[i+FDMDV_NSYM];
}
// draw new samples
fl_color(FL_GREEN);
for(i=SCATTER_MEM-FDMDV_NSYM, j=0; i<SCATTER_MEM; i++,j++) {
x1 = x_scale * new_samples[j].real + x() + w()/2;
y1 = y_scale * new_samples[j].imag + y() + h()/2;
fl_point(x1, y1);
mem[i] = new_samples[j];
}
fl_pop_clip();
}
public:
Scatter(int x, int y, int w, int h): Fl_Box(x, y, w, h, "Scatter")
{
int i;
align(FL_ALIGN_TOP);
labelsize(10);
for(i=0; i<SCATTER_MEM; i++) {
mem[i].real = 0.0;
mem[i].imag = 0.0;
}
prev_w = 0; prev_h = 0; prev_x = 0; prev_y = 0;
};
void add_new_samples(COMP samples[]) {
int i;
for(i=0; i<FDMDV_NSYM; i++)
new_samples[i] = samples[i];
}
};
// general purpose way of plotting scalar values that are
// updated once per frame
class Scalar: public Fl_Box {
protected:
int x_max, y_max;
float *mem; /* array of x_max samples */
float new_sample;
int index, step;
int prev_w, prev_h, prev_x, prev_y;
int clip(int y1) {
if (y1 > (h()/2 - 10))
y1 = h()/2 - 10;
if (y1 < -(h()/2 - 10))
y1 = -(h()/2 - 10);
return y1;
}
void draw() {
float x_scale;
float y_scale;
int i, x1, y1, x2, y2;
char label[100];
Fl_Box::draw();
/* detect resizing of window */
if ((h() != prev_h) || (w() != prev_w) || (x() != prev_x) || (y() != prev_y)) {
fl_color(FL_BLACK);
fl_rectf(x(),y(),w(),h());
prev_h = h(); prev_w = w(); prev_x = x(); prev_y = y();
}
fl_push_clip(x(),y(),w(),h());
x_scale = (float)w()/x_max;
y_scale = (float)h()/(2.0*y_max);
// erase last sample
fl_color(FL_BLACK);
x1 = x_scale * index + x();
y1 = y_scale * mem[index];
y1 = clip(y1);
y1 = y() + h()/2 - y1;
fl_point(x1, y1);
// draw new sample
fl_color(FL_GREEN);
x1 = x_scale * index + x();
y1 = y_scale * new_sample;
y1 = clip(y1);
y1 = y() + h()/2 - y1;
fl_point(x1, y1);
mem[index] = new_sample;
index++;
if (index >= x_max)
index = 0;
// y axis graticule
step = 10;
while ((2.0*y_max/step) > 10)
step *= 2.0;
while ((2.0*y_max/step) < 4)
step /= 2.0;
fl_color(FL_DARK_GREEN);
fl_line_style(FL_DOT);
for(i=-y_max; i<y_max; i+=step) {
x1 = x();
y1 = y() + h()/2 - i*y_scale;
x2 = x() + w();
y2 = y1;
fl_line(x1,y1,x2,y2);
}
// y axis graticule labels
fl_color(FL_GREEN);
fl_line_style(FL_SOLID);
for(i=-y_max; i<y_max; i+=step) {
x1 = x();
y1 = y() + h()/2 - i*y_scale;
sprintf(label, "%d", i);
fl_draw(label, x1, y1);
}
fl_pop_clip();
}
public:
Scalar(int x, int y, int w, int h, int x_max_, int y_max_, const char name[]): Fl_Box(x, y, w, h, name)
{
int i;
align(FL_ALIGN_TOP);
labelsize(10);
x_max = x_max_; y_max = y_max_;
mem = new float[x_max];
for(i=0; i<x_max; i++) {
mem[i] = 0.0;
}
prev_w = 0; prev_h = 0; prev_x = 0; prev_y = 0;
index = 0;
};
~Scalar() {
delete mem;
}
void add_new_sample(float sample) {
new_sample = sample;
}
};
// update average of each spectrum point
void new_data(float mag_dB[]) {
int i;
for(i=0; i<FDMDV_NSPEC; i++)
av_mag[i] = (1.0 - BETA)*av_mag[i] + BETA*mag_dB[i];
}
/*------------------------------------------------------------------*\
FUNCTION: per_frame_rx_processing()
AUTHOR..: David Rowe
DATE....: July 2012
Called every rx frame to take a buffer of input modem samples and
convert them to a buffer of output speech samples.
The sample source could be a sound card or file. The sample source
supplies a fixed number of samples with each call. However
fdmdv_demod requires a variable number of samples for each call.
This function will buffer as appropriate and call fdmdv_demod with
the correct number of samples.
The processing sequence is:
collect demod input samples from sound card 1 A/D
while we have enough samples:
demod samples into bits
decode bits into speech samples
output a buffer of speech samples to sound card 2 D/A
Note that sound card 1 and sound card 2 will have slightly different
sample rates, as their sample clocks are not syncronised. We
effectively lock the system to the demod A/D (sound card 1) sample
rate. This ensures the demod gets a continuous sequence of samples,
maintaining sync. Sample underflow or overflow will instead occur on
the sound card 2 D/A. This is acceptable as a buffer of lost or
extra speech samples is unlikely to be noticed.
The situation is actually a little more complex than that. Through
the demod timing estimation the buffers supplied to sound card D/A 2
are effectively clocked at the remote modulator sound card D/A clock
rate. We slip/gain buffers supplied to sound card 2 to compensate.
The current demod handles varying clock rates by having a variable
number of input samples, e.g. 120 160 (nominal) or 200. However the
A/D always delivers a fixed number of samples.
So we currently need some logic between the A/D and the demod:
+ A/D delivers fixed number of samples
+ demod processes a variable number of samples
+ this means we run demod 0,1 or 2 times, depending
on number of buffered A/D samples
+ demod always outputs 1 frame of bits
+ so run demod and speech decoder 0, 1 or 2 times
The ouput of the demod is codec voice data so it's OK if we miss or
repeat a frame every now and again.
TODOs:
+ this might work with arbitrary input and output buffer lengths,
0,1, or 2 only apply if we are inputting the nominal number of
samples on every call.
+ so the I/O buffer sizes might not matter, as long as they of
reasonable size (say twice the nominal size).
\*------------------------------------------------------------------*/
void per_frame_rx_processing(short output_buf[], /* output buf of decoded speech samples */
int *n_output_buf, /* how many samples currently in output_buf[] */
int codec_bits[], /* current frame of bits for decoder */
short input_buf[], /* input buf of modem samples input to demod */
int *n_input_buf, /* how many samples currently in input_buf[] */
int *nin, /* amount of samples demod needs for next call */
int *state, /* used to collect codec_bits[] halves */
struct CODEC2 *c2 /* Codec 2 states */
)
{
struct FDMDV_STATS stats;
int sync_bit;
float rx_fdm[FDMDV_MAX_SAMPLES_PER_FRAME];
int rx_bits[FDMDV_BITS_PER_FRAME];
uchar packed_bits[BYTES_PER_CODEC_FRAME];
float rx_spec[FDMDV_NSPEC];
int i, nin_prev, bit, byte;
int next_state;
assert(*n_input_buf <= (2*FDMDV_NOM_SAMPLES_PER_FRAME));
/*
This while loop will run the demod 0, 1 (nominal) or 2 times:
0: when tx sample clock runs faster than rx, occasionally we
will run out of samples
1: normal, run decoder once, every 2nd frame output a frame of
speech samples to D/A
2: when tx sample clock runs slower than rx, occasionally we will
have enough samples to run demod twice.
With a +/- 10 Hz sample clock difference at FS=8000Hz (+/- 1250
ppm), case 0 or 1 occured about once every 30 seconds. This is
no problem for the decoded audio.
*/
while(*n_input_buf >= *nin) {
// demod per frame processing
for(i=0; i<*nin; i++)
rx_fdm[i] = (float)input_buf[i]/FDMDV_SCALE;
nin_prev = *nin;
fdmdv_demod(fdmdv, rx_bits, &sync_bit, rx_fdm, nin);
*n_input_buf -= nin_prev;
assert(*n_input_buf >= 0);
// shift input buffer
for(i=0; i<*n_input_buf; i++)
input_buf[i] = input_buf[i+nin_prev];
// compute rx spectrum & get demod stats, and update GUI plot data
fdmdv_get_rx_spectrum(fdmdv, rx_spec, rx_fdm, nin_prev);
fdmdv_get_demod_stats(fdmdv, &stats);
new_data(rx_spec);
aScatter->add_new_samples(stats.rx_symbols);
aTimingEst->add_new_sample(stats.rx_timing);
aFreqEst->add_new_sample(stats.foff);
aSNR->add_new_sample(stats.snr_est);
/*
State machine to:
+ Mute decoded audio when out of sync. The demod is synced
when we are using the fine freq estimate and SNR is above
a thresh.
+ Decode codec bits only if we have a 0,1 sync bit
sequence. Collects two frames of demod bits to decode
one frame of codec bits.
*/
next_state = *state;
switch (*state) {
case 0:
/* mute output audio when out of sync */
if (*n_output_buf < 2*codec2_samples_per_frame(c2) - N8) {
for(i=0; i<N8; i++)
output_buf[*n_output_buf + i] = 0;
*n_output_buf += N8;
}
assert(*n_output_buf <= (2*codec2_samples_per_frame(c2)));
if ((stats.fest_coarse_fine == 1) && (stats.snr_est > 3.0))
next_state = 1;
break;
case 1:
if (sync_bit == 0) {
next_state = 2;
/* first half of frame of codec bits */
memcpy(codec_bits, rx_bits, FDMDV_BITS_PER_FRAME*sizeof(int));
}
else
next_state = 1;
if (stats.fest_coarse_fine == 0)
next_state = 0;
break;
case 2:
next_state = 1;
if (stats.fest_coarse_fine == 0)
next_state = 0;
if (sync_bit == 1) {
/* second half of frame of codec bits */
memcpy(&codec_bits[FDMDV_BITS_PER_FRAME], rx_bits, FDMDV_BITS_PER_FRAME*sizeof(int));
/* pack bits, MSB received first */
bit = 7; byte = 0;
memset(packed_bits, 0, BYTES_PER_CODEC_FRAME);
for(i=0; i<BITS_PER_CODEC_FRAME; i++) {
packed_bits[byte] |= (codec_bits[i] << bit);
bit--;
if (bit < 0) {
bit = 7;
byte++;
}
}
assert(byte == BYTES_PER_CODEC_FRAME);
/* add decoded speech to end of output buffer */
if (*n_output_buf <= codec2_samples_per_frame(c2)) {
codec2_decode(c2, &output_buf[*n_output_buf], packed_bits);
*n_output_buf += codec2_samples_per_frame(c2);
}
assert(*n_output_buf <= (2*codec2_samples_per_frame(c2)));
}
break;
}
*state = next_state;
}
}
/*
Redraw windows every DT seconds.
*/
void update_gui(int nin, float *Ts) {
*Ts += (float)nin/FS;
*Ts += (float)nin/FS;
if (*Ts >= DT) {
*Ts -= DT;
if (!zoomSpectrumWindow->shown() && !zoomWaterfallWindow->shown()) {
aSpectrum->redraw();
aWaterfall->redraw();
aScatter->redraw();
aTimingEst->redraw();
aFreqEst->redraw();
aSNR->redraw();
}
if (zoomSpectrumWindow->shown())
aZoomedSpectrum->redraw();
if (zoomWaterfallWindow->shown())
aZoomedWaterfall->redraw();
}
}
/*
idle() is the FLTK function that gets continusouly called when FLTK
is not doing GUI work. We use this function for providing file
input to update the GUI when simulating real time operation.
*/
void idle(void*) {
int ret, i;
if (fin_name != NULL) {
ret = fread(&input_buf[n_input_buf],
sizeof(short),
FDMDV_NOM_SAMPLES_PER_FRAME,
fin);
n_input_buf += FDMDV_NOM_SAMPLES_PER_FRAME;
per_frame_rx_processing(output_buf, &n_output_buf,
codec_bits,
input_buf, &n_input_buf,
&nin, &state, codec2);
if (fout_name != NULL) {
if (n_output_buf >= N8) {
ret = fwrite(output_buf, sizeof(short), N8, fout);
n_output_buf -= N8;
assert(n_output_buf >= 0);
/* shift speech sample output buffer */
for(i=0; i<n_output_buf; i++)
output_buf[i] = output_buf[i+N8];
}
}
}
update_gui(nin, &Ts);
// simulate time delay from real world A/D input, and pause betwen
// screen updates
usleep(20000);
}
/*
This routine will be called by the PortAudio engine when audio is
available.
*/
static int callback( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
paCallBackData *cbData = (paCallBackData*)userData;
uint i;
short *rptr = (short*)inputBuffer;
short *wptr = (short*)outputBuffer;
float *in8k = cbData->in8k;
float *in48k = cbData->in48k;
float out8k[N8];
float out48k[N48];
short out48k_short[N48];
(void) timeInfo;
(void) statusFlags;
assert(inputBuffer != NULL);
/* Convert input model samples from 48 to 8 kHz ------------ */
/* just use left channel */
for(i=0; i<framesPerBuffer; i++,rptr+=2)
in48k[i+FDMDV_OS_TAPS] = *rptr;
/* downsample and update filter memory */
fdmdv_48_to_8(out8k, &in48k[FDMDV_OS_TAPS], N8);
for(i=0; i<FDMDV_OS_TAPS; i++)
in48k[i] = in48k[i+framesPerBuffer];
/* run demod, decoder and update GUI info ------------------ */
for(i=0; i<N8; i++)
input_buf[n_input_buf+i] = (short)out8k[i];
n_input_buf += FDMDV_NOM_SAMPLES_PER_FRAME;
per_frame_rx_processing(output_buf, &n_output_buf,
codec_bits,
input_buf, &n_input_buf,
&nin, &state, codec2);
/* if demod out of sync copy input audio from A/D to aid in tuning */
if (n_output_buf >= N8) {
if (state == 0) {
for(i=0; i<N8; i++)
in8k[MEM8+i] = out8k[i]; /* A/D signal */
}
else {
for(i=0; i<N8; i++)
in8k[MEM8+i] = output_buf[i]; /* decoded spech */
}
n_output_buf -= N8;
}
assert(n_output_buf >= 0);
/* shift speech samples in output buffer */
for(i=0; i<(uint)n_output_buf; i++)
output_buf[i] = output_buf[i+N8];
/* Convert output speech to 48 kHz sample rate ------------- */
/* upsample and update filter memory */
fdmdv_8_to_48(out48k, &in8k[MEM8], N8);
for(i=0; i<MEM8; i++)
in8k[i] = in8k[i+N8];
assert(outputBuffer != NULL);
/* write signal to both channels */
for(i=0; i<N48; i++)
out48k_short[i] = (short)out48k[i];
for(i=0; i<framesPerBuffer; i++,wptr+=2) {
wptr[0] = out48k_short[i];
wptr[1] = out48k_short[i];
}
return paContinue;
}
int arg_callback(int argc, char **argv, int &i) {
if (argv[i][1] == 'i') {
if ((i+1) >= argc)
return 0;
fin_name = argv[i+1];
i += 2;
return 2;
}
if (argv[i][1] == 'o') {
if ((i+1) >= argc)
return 0;
fout_name = argv[i+1];
i += 2;
return 2;
}
if (argv[i][1] == 's') {
if ((i+1) >= argc)
return 0;
sound_dev_name = argv[i+1];
i += 2;
return 2;
}
return 0;
}
/*------------------------------------------------------------*\
MAIN
\*------------------------------------------------------------*/
int main(int argc, char **argv) {
int ret;
int i;
PaStreamParameters inputParameters, outputParameters;
paCallBackData cbData;
i = 1;
Fl::args(argc,argv,i,arg_callback);
if (argc == 1) {
printf("usage: %s [-i inputFdmdvRawFile] [-o outputRawSoundFile] [-s inputSoundDevice]\n", argv[0]);
exit(0);
}
if (fin_name != NULL) {
fin = fopen(fin_name,"rb");
if (fin == NULL) {
fprintf(stderr, "Error opening input fdmdv raw file %s\n", fin_name);
exit(1);
}
}
if (fout_name != NULL) {
fout = fopen(fout_name,"wb");
if (fout == NULL) {
fprintf(stderr, "Error opening output speech raw file %s\n", fout_name);
exit(1);
}
}
for(i=0; i<FDMDV_NSPEC; i++)
av_mag[i] = -40.0;
fdmdv = fdmdv_create();
codec2 = codec2_create(CODEC2_MODE_1400);
output_buf = (short*)malloc(2*sizeof(short)*codec2_samples_per_frame(codec2));
/*------------------------------------------------------------------------*\
Init Sound Card I/O
\*------------------------------------------------------------------------*/
if (sound_dev_name != NULL) {
for(i=0; i<MEM8; i++)
cbData.in8k[i] = 0.0;
for(i=0; i<FDMDV_OS_TAPS; i++)
cbData.in48k[i] = 0.0;
err = Pa_Initialize();
if( err != paNoError ) goto pa_error;
inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
if (inputParameters.device == paNoDevice) {
fprintf(stderr,"Error: No default input device.\n");
goto pa_error;
}
inputParameters.channelCount = NUM_CHANNELS; /* stereo input */
inputParameters.sampleFormat = paInt16;
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
inputParameters.hostApiSpecificStreamInfo = NULL;
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
if (outputParameters.device == paNoDevice) {
fprintf(stderr,"Error: No default output device.\n");
goto pa_error;
}
outputParameters.channelCount = NUM_CHANNELS; /* stereo output */
outputParameters.sampleFormat = paInt16;
outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(
&stream,
&inputParameters,
&outputParameters,
SAMPLE_RATE,
N48,
paClipOff,
callback,
&cbData);
if( err != paNoError ) goto pa_error;
}
/*------------------------------------------------------------------------*\
Init GUI
\*------------------------------------------------------------------------*/
// recommended to prevent dithering and stopped display being
// covered by black flickering squares
Fl::visual(FL_RGB);
// set up main window
window = new Fl_Window(W, SP+H2+SP+SP+H2+SP, "fl_fmdv");
//window->size_range(100, 100);
//window->resizable();
aSpectrum = new Spectrum(SP, SP, W3-2*SP, H2);
aWaterfall = new Waterfall(SP, SP+H2+SP+SP, W3-2*SP, H2);
aScatter = new Scatter(W3+SP, SP, W3-2*SP, H2);
aTimingEst = new Scalar(W3+SP, SP+H2+SP+SP, W3-2*SP, H2, 100, 80, "Timing Est");
aFreqEst = new Scalar(2*W3+SP, SP, W3-2*SP, H2, 100, 100, "Frequency Est");
aSNR = new Scalar(2*W3+SP, SP+H2+SP+SP, W3-2*SP, H2, 100, 20, "SNR");
Fl::add_idle(idle);
window->end();
// set up zoomed spectrum window
zoomSpectrumWindow = new Fl_Window(W, H, "Spectrum");
aZoomedSpectrum = new Spectrum(SP, SP, W-2*SP, H-2*SP);
zoomSpectrumWindow->end();
// set up zoomed waterfall window
zoomWaterfallWindow = new Fl_Window(W, H, "Waterfall");
aZoomedWaterfall = new Waterfall(SP, SP, W-2*SP, H-2*SP);
zoomWaterfallWindow->end();
if (sound_dev_name != NULL) {
err = Pa_StartStream( stream );
if( err != paNoError ) goto pa_error;
}
// show the main window and start running
window->show(argc, argv);
Fl::run();
if (sound_dev_name != NULL) {
err = Pa_StopStream( stream );
if( err != paNoError ) goto pa_error;
Pa_CloseStream( stream );
Pa_Terminate();
}
fdmdv_destroy(fdmdv);
codec2_destroy(codec2);
free(output_buf);
if (fin_name != NULL)
fclose(fin);
if (fout_name != NULL)
fclose(fout);
return ret;
// Portaudio error handling
pa_error:
if( stream ) {
Pa_AbortStream( stream );
Pa_CloseStream( stream );
}
Pa_Terminate();
fprintf( stderr, "An error occured while using the portaudio stream\n" );
fprintf( stderr, "Error number: %d\n", err );
fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
return -1;
}