first attempt at portaudio integration but decided to step back and do some unit...
authordrowe67 <drowe67@01035d8c-6547-0410-b346-abe4f91aad63>
Thu, 5 Jul 2012 22:14:37 +0000 (22:14 +0000)
committerdrowe67 <drowe67@01035d8c-6547-0410-b346-abe4f91aad63>
Thu, 5 Jul 2012 22:14:37 +0000 (22:14 +0000)
git-svn-id: https://svn.code.sf.net/p/freetel/code@579 01035d8c-6547-0410-b346-abe4f91aad63

codec2-dev/fltk/Makefile
codec2-dev/fltk/fl_fdmdv.cxx

index 7a3824d447c1bee6e1f4a8afefd0f4679074fd51..004ee46bc0f50437c86429e3506f57c1b3c5e380 100644 (file)
@@ -1,14 +1,14 @@
-# Requires FLTK 1.3
+# Requires FLTK 1.3 & Portaudio V19
 
 FLTK_CFLAGS += $(shell fltk-config --ldstaticflags)
 CFLAGS = -O3 -g
-
+LIBS = ../src/.libs/libcodec2.a -lm -lrt -lportaudio -pthread
 LC2POC_C = fl_fdmdv.cxx
 
 all: fl_fdmdv
 
 fl_fdmdv: Makefile $(LC2POC_C)
-       g++ $(LC2POC_C) -I../src/ -o fl_fdmdv $(CFLAGS) $(FLTK_CFLAGS) ../src/.libs/libcodec2.a
+       g++ $(LC2POC_C) -I../src/ -o fl_fdmdv $(CFLAGS) $(FLTK_CFLAGS) $(LIBS)
 
 clean:
        rm -f fl_fdmdv
index 7ef14bcd4369613fd8301e81b45b3b679f8ee047..39f88cfc368dc098f7c3a87d1116376c55dafaee 100644 (file)
   FDMDV GUI displays.
 */
 
+#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 <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
+#include <FL/names.h>
+
+#include "portaudio.h"
 
 #include "fdmdv.h"
 
-#define MIN_DB             -40.0
+#define MIN_DB             -40.0 
 #define MAX_DB               0.0
-#define BETA                 0.1
+#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
+#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
 
+#define SOUND_CARD_FS      48000
+
+// forward class declarations
 
 class Spectrum;
 class Waterfall;
 class Scatter;
 class Scalar;
 
+// Globals --------------------------------------
+
 char         *fin_name = NULL;
+char         *sound_dev_name = NULL;
 FILE         *fin = NULL;
 struct FDMDV *fdmdv;
+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;
 
-float  av_mag[FDMDV_NSPEC]; // shared between a few classes
+// Main processing loop states ------------------
 
 float  Ts = 0.0;
+int    nbuf = 0;
+short  rx_fdm_scaled[7*FDMDV_NOM_SAMPLES_PER_FRAME];
+int    nin = FDMDV_NOM_SAMPLES_PER_FRAME;
+
+// Portaudio states -----------------------------
+
+PaStreamParameters inputParameters;
+PaStream *stream = NULL;
+PaError err;
+
+// 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;
@@ -165,6 +215,23 @@ protected:
            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/
 
@@ -283,7 +350,7 @@ class Scatter: public Fl_Box {
 protected:
     COMP mem[SCATTER_MEM];
     COMP new_samples[FDMDV_NSYM];
-    int  prev_w, prev_h;
+    int  prev_w, prev_h, prev_x, prev_y;
 
     void draw() {
        float x_scale;
@@ -294,10 +361,10 @@ protected:
 
        /* detect resizing of window */
 
-       if ((h() != prev_h) || (w() != prev_w)) {
+       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_h = h(); prev_w = w(); prev_x = x(); prev_y = y();
        }
 
        fl_push_clip(x(),y(),w(),h());
@@ -346,7 +413,7 @@ public:
            mem[i].imag = 0.0;
        }
 
-       prev_w = 0; prev_h = 0;
+       prev_w = 0; prev_h = 0; prev_x = 0; prev_y = 0;
     };
 
     void add_new_samples(COMP samples[]) {
@@ -367,22 +434,31 @@ protected:
     int    x_max, y_max;
     float *mem;              /* array of x_max samples */
     float  new_sample;
-    int    index;
-    int    prev_w, prev_h;
+    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, j, x1, y1;
+       int   i, j, x1, y1, x2, y2;
+       char  label[100];
 
        Fl_Box::draw();
 
        /* detect resizing of window */
 
-       if ((h() != prev_h) || (w() != prev_w)) {
+       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_h = h(); prev_w = w(); prev_x = x(); prev_y = y();
        }
 
        fl_push_clip(x(),y(),w(),h());
@@ -394,25 +470,60 @@ protected:
 
        fl_color(FL_BLACK);
        x1 = x_scale * index + x();
-       y1 = -y_scale * mem[index] + y() + h()/2;
+       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 + y() + h()/2;
+       y1 = y_scale * new_sample;
+       y1 = clip(y1);
+       y1 = y() + h()/2 - y1;
        fl_point(x1, y1);
        mem[index] = new_sample;
-       fl_pop_clip();
 
        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_, char name[]): Fl_Box(x, y, w, h, name)
+    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;
 
@@ -425,7 +536,7 @@ public:
            mem[i] = 0.0;
        }
 
-       prev_w = 0; prev_h = 0;
+       prev_w = 0; prev_h = 0; prev_x = 0; prev_y = 0;
        index = 0;
     };
 
@@ -449,21 +560,74 @@ void new_data(float mag_dB[]) {
        av_mag[i] = (1.0 - BETA)*av_mag[i] + BETA*mag_dB[i];
 }
 
-// simulates real time operation by reading a raw file then pausing
+/*
+  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.
+
+  idle() is the FLTK function that gets continusouly called when FLTK
+  is not doing GUI work.  In a non-GUI program this could just be a
+  while(1) loop.
+*/
 
 void idle(void*) {
     struct FDMDV_STATS stats;
     int   rx_bits[FDMDV_BITS_PER_FRAME];
     int   sync_bit;
-    int   nin, nin_prev;
-    short rx_fdm_scaled[FDMDV_MAX_SAMPLES_PER_FRAME];
     float rx_fdm[FDMDV_MAX_SAMPLES_PER_FRAME];
     float rx_spec[FDMDV_NSPEC];
-    int   i;
+    int   i, j, nin_prev, ret;
+
+    if (fin_name != NULL) {
+       ret = fread(&rx_fdm_scaled[nbuf], 
+                   sizeof(short), 
+                   FDMDV_NOM_SAMPLES_PER_FRAME, 
+                   fin);
 
-    nin = FDMDV_NOM_SAMPLES_PER_FRAME;
+       // simulate time delay from real world A/D input
+
+       usleep(20000);
+    }
+
+    if (sound_dev_name != NULL) {    
+       err = Pa_ReadStream(stream, 
+                          &rx_fdm_scaled[nbuf], 
+                          FDMDV_NOM_SAMPLES_PER_FRAME);
+       if (err & paInputOverflow) { 
+           fprintf( stderr, "Input Overflow.\n" );
+       }
+    }
+
+    nbuf += FDMDV_NOM_SAMPLES_PER_FRAME;
+    assert(nbuf <= (2*FDMDV_NOM_SAMPLES_PER_FRAME));
+
+    // this will run the demod 0, 1 (nominal) or 2 time
+
+    while(nbuf >= nin) {
 
-    if (fread(rx_fdm_scaled, sizeof(short), nin, fin) == nin) {
        Ts += (float)nin/FS;
        
        // demod per frame processing
@@ -471,12 +635,14 @@ void idle(void*) {
        for(i=0; i<nin; i++)
            rx_fdm[i] = (float)rx_fdm_scaled[i]/FDMDV_SCALE;
        nin_prev = nin;
-       fdmdv_demod(fdmdv, rx_bits, &sync_bit, rx_fdm, &nin);
+       //fdmdv_demod(fdmdv, rx_bits, &sync_bit, rx_fdm, &nin);
+       nbuf -= nin_prev;
+       assert(nbuf >= 0);
 
-       // get stats and spectrum and update
+       // get spectrum, stats and update windows
 
-       fdmdv_get_demod_stats(fdmdv, &stats);
        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);
@@ -487,15 +653,26 @@ void idle(void*) {
 
        if (Ts >= DT) {
            Ts -= DT;
-           aSpectrum->redraw();
-           aWaterfall->redraw();
-           aScatter->redraw();
-           aTimingEst->redraw();
-           aFreqEst->redraw();
-           aSNR->redraw();
+           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();             
        }
+
+       // shift buffer
+
+       for(i=0; i<nbuf; i++)
+           rx_fdm_scaled[i] = rx_fdm_scaled[i+nin_prev];
+
     }
-    usleep(20000);
 }
 
 int arg_callback(int argc, char **argv, int &i) {
@@ -506,6 +683,13 @@ int arg_callback(int argc, char **argv, int &i) {
        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;
 }
 
@@ -515,46 +699,110 @@ int main(int argc, char **argv) {
 
     i = 1;
     Fl::args(argc,argv,i,arg_callback);
-    
-    if (argc != 3) {
-       printf("usage: %s -i inputFdmdvRawFile\n", argv[0]);
+
+    if (argc == 1) {
+       printf("usage: %s [-i inputFdmdvRawFile] [-s inputSoundDevice]\n", argv[0]);
        exit(0);
     }
 
-    fin = fopen(fin_name,"rb");
-    if (fin == NULL) {
-       fprintf(stderr, "Error opening input fdmdv raw file %s\n", argv[1]);
-       exit(1);
+    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 (sound_dev_name != NULL) {
+       err = Pa_Initialize();
+       if( err != paNoError ) goto pa_error;
+       inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
+       printf( "Input device # %d.\n", inputParameters.device );
+       printf( "Input LL: %g s\n", Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency );
+       printf( "Input HL: %g s\n", Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency );
+       inputParameters.channelCount = 1;
+       inputParameters.sampleFormat = paInt16;
+       inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
+       inputParameters.hostApiSpecificStreamInfo = NULL;
+
+       err = Pa_OpenStream(
+              &stream,
+              &inputParameters,
+              NULL,                         // no output
+              /*SOUND_CARD_FS*/8000,                
+              FDMDV_NOM_SAMPLES_PER_FRAME,
+              paClipOff,      
+              NULL,                         // no callback, use blocking API
+              NULL );                       // no callback, so no callback userData
+       if( err != paNoError ) goto pa_error;
+       err = Pa_StartStream( stream );
+       if( err != paNoError ) goto pa_error;
     }
-    
     for(i=0; i<FDMDV_NSPEC; i++)
        av_mag[i] = -40.0;
 
-    // reccommended to prevent dithering and stopped display being
+    // 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();
+    //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, 40, "SNR");
+    aSNR = new Scalar(2*W3+SP, SP+H2+SP+SP, W3-2*SP, H2, 100, 10, "SNR");
     fdmdv = fdmdv_create();
 
     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();
+
+    // show the main window and start running
+
     window->show(argc, argv);
-    ret = Fl::run();
+    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);
-    fclose(fin);
+    if (fin_name != NULL)
+       fclose(fin);
 
     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;
 }