--- /dev/null
+% tfdmdv.m
+%
+% Octave script that evaluates the output of tfdmdv.c Unit Test program for FDMDV modem.
+%
+% Copyright David Rowe 2012
+% This program is distributed under the terms of the GNU General Public License
+% Version 2
+%
+
+fdmdv; % load modem code
+
+% Generate reference vectors using Octave implementation of FDMDV modem
+
+passes = fails = 0;
+frames = 10;
+prev_tx_symbols = ones(Nc+1,1);
+tx_bits_log = [];
+tx_symbols_log = [];
+
+for f=1:frames
+
+ tx_bits = get_test_bits(Nc*Nb);
+ tx_bits_log = [tx_bits_log tx_bits];
+ tx_symbols = bits_to_qpsk(prev_tx_symbols, tx_bits, 'dqpsk');
+ prev_tx_symbols = tx_symbols;
+ tx_symbols_log = [tx_symbols_log tx_symbols];
+
+end
+
+% Compare to the output from the C version
+
+load ../unittest/tfdmdv_out.txt
+
+figure(1)
+subplot(211)
+plot(tx_bits_log - tx_bits_tfdmdv);
+title('tx bits')
+subplot(212)
+plot(tx_symbols_log - tx_symbols_tfdmdv);
+title('tx symbols')
+
+if sum(tx_bits_log - tx_bits_tfdmdv) == 0
+ printf("fdmdv_get_test_bits..: OK\n");
+ passes++;
+else;
+ printf("fdmdv_get_test_bits..: FAIL\n");
+ fails++;
+end
+
+if sum(tx_symbols_log - tx_symbols_tfdmdv) == 0
+ printf("bits_to_dqpsk_symbols: OK\n");
+ passes++;
+else;
+ printf("bits_to_dqpsk_symbols: FAIL\n");
+ fails++;
+end
+
+printf("\npasses: %d fails: %d\n", passes, fails);
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
-/*---------------------------------------------------------------------------*\
-
- DEFINES
-
-\*---------------------------------------------------------------------------*/
-
-#define FS 8000 /* sample rate in Hz */
-#define T (1/Fs) /* sample period in seconds */
-#define RS 50 /* symbol rate in Hz */
-#define NC 14 /* number of carriers */
-#define NB 2 /* Bits/symbol for QPSK modulation */
-#define RB (Nc*Rs*Nb) /* bit rate */
-#define M Fs/Rs /* oversampling factor */
-#define NSYM 4 /* number of symbols to filter over */
-#define FSEP 75 /* Separation between carriers (Hz) */
-#define FCENTRE 1200 /* Centre frequency, Nc/2 carriers below this, Nc/2 carriers above (Hz) */
-#define NT 5 /* number of symbols we estimate timing over */
-#define P 4 /* oversample factor used for initial rx symbol filtering */
-#define NFILTER (NSYM*M) /* size of tx/rx filters at sampel rate M */
-#define NFILTERTIMING (M+Nfilter+M) /* filter memory used for resampling after timing estimation */
-
-#define NTEST_BITS (Nc*Nb*4) /* length of test bit sequence */
-
-/*---------------------------------------------------------------------------*\
-
- STRUCT for States
-
-\*---------------------------------------------------------------------------*/
-
-struct FDMDV {
- int current_test_bit;
-};
-
/*---------------------------------------------------------------------------*\
INCLUDES
\*---------------------------------------------------------------------------*/
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "fdmdv_internal.h"
#include "fdmdv.h"
#include "rn.h"
-#include "testbits.h"
+#include "test_bits.h"
/*---------------------------------------------------------------------------*\
int i;
for(i=0; i<n; i++)
- res = cadd(res, cmult(a,b));
+ res = cadd(res, cmult(a[i], b[i]));
return res;
}
struct FDMDV *fdmdv_create(void)
{
- struct FDMDV *fdmdv;
+ struct FDMDV *f;
+ int c;
+
+ assert(FDMDV_BITS_PER_FRAME == NC*NB);
+ assert(FDMDV_SAMPLES_PER_FRAME == M);
- fdmdv = (struct FDMDV*)malloc(sizeof(struct FDMDV));
- if (fdmdv == NULL)
+ f = (struct FDMDV*)malloc(sizeof(struct FDMDV));
+ if (f == NULL)
return NULL;
- return fdmdv;
+ f->current_test_bit = 0;
+ f->tx_pilot_bit = 0;
+ for(c=0; c<NC+1; c++) {
+ f->prev_tx_symbols[c].real = 1.0;
+ f->prev_tx_symbols[c].imag = 0.0;
+ }
+ return f;
}
/*---------------------------------------------------------------------------*\
free(fdmdv);
}
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fdmdv_get_test_bits()
+ AUTHOR......: David Rowe
+ DATE CREATED: 16/4/2012
+
+ Generate a frame of bits from a repeating sequence of random data. OK so
+ it's not very random if it repeats but it makes syncing at the demod easier
+ for test purposes.
+
+\*---------------------------------------------------------------------------*/
+
+void fdmdv_get_test_bits(struct FDMDV *f, int tx_bits[])
+{
+ int i;
+
+ for(i=0; i<FDMDV_BITS_PER_FRAME; i++) {
+ tx_bits[i] = test_bits[f->current_test_bit];
+ f->current_test_bit++;
+ if (f->current_test_bit > (NTEST_BITS-1))
+ f->current_test_bit = 0;
+ }
+ }
+
/*---------------------------------------------------------------------------*\
FUNCTION....: bits_to_dqpsk_symbols()
{
int c, msb, lsb;
COMP j = {0.0,1.0};
- COMP minusj = {0.0,-1.0};
/* map tx_bits to to Nc DQPSK symbols */
two spectral lines at +/- Rs/2 */
if (*pilot_bit)
- tx_symbols[Nc] = cneg(prev_tx_symbols[Nc]);
+ tx_symbols[NC] = cneg(prev_tx_symbols[NC]);
else
- tx_symbols[Nc] = prev_tx_symbols[Nc];
+ tx_symbols[NC] = prev_tx_symbols[NC];
if (*pilot_bit)
*pilot_bit = 0;
extern "C" {
#endif
+#define FDMDV_BITS_PER_FRAME 28
+#define FDMDV_SAMPLES_PER_FRAME 160
+
struct FDMDV;
+struct FDMDV_STATS {
+ float snr; /* estimated SNR of rx signal in dB */
+ COMP *rx_symbols; /* NC+1 latest received symbols, for scatter plot */
+ int fest_track; /* == 0, freq est in acquire mode, == 1 in track mode */
+ float foff; /* estimated freq offset in Hz */
+ float timing; /* timing offset 0..1 as fraction of symbol period */
+ float clock_offset; /* Estimated tx/rx sample clock offset in ppm */
+};
+
+
struct FDMDV *fdmdv_create(void);
void fdmdv_destroy(struct FDMDV *fdmdv_state);
-void fdmdv_mod(struct FDMDV *fdmdv_state, int tx_bits[], COMP tx_fdm[]);
-void fdmdv_demod(struct FDMDV *fdmdv_state, int tx_bits[], float rx_fdm[], int *nin);
+void fdmdv_mod(struct FDMDV *fdmdv_state, COMP tx_fdm[], int tx_bits[]);
+void fdmdv_demod(struct FDMDV *fdmdv_state, int rx_bits[], int *sync, float rx_fdm[], int *nin);
void fdmdv_get_test_bits(struct FDMDV *fdmdv_state, int tx_bits[]);
void fdmdv_put_test_bits(struct FDMDV *fdmdv_state, int rx_bits[]);
-float fdmdv_get_snr(struct FDMDV *fdmdv_state);
+float fdmdv_get_demod_stats(struct FDMDV *fdmdv_state, struct FDMDV_STATS *fdmdv_stats);
void fdmdv_get_waterfall_line(struct FDMDV *fdmdv_state, float magnitudes[], int *magnitude_points);
#endif
#include "comp.h"
+/*---------------------------------------------------------------------------*\
+
+ DEFINES
+
+\*---------------------------------------------------------------------------*/
+
+#define FS 8000 /* sample rate in Hz */
+#define T (1.0/FS) /* sample period in seconds */
+#define RS 50 /* symbol rate in Hz */
+#define NC 14 /* number of carriers */
+#define NB 2 /* Bits/symbol for QPSK modulation */
+#define RB (NC*RS*NB) /* bit rate */
+#define M (FS/RS) /* oversampling factor */
+#define NSYM 4 /* number of symbols to filter over */
+#define FSEP 75 /* Separation between carriers (Hz) */
+#define FCENTRE 1200 /* Centre frequency, Nc/2 carriers below this, Nc/2 carriers above (Hz) */
+#define NT 5 /* number of symbols we estimate timing over */
+#define P 4 /* oversample factor used for initial rx symbol filtering */
+#define NFILTER (NSYM*M) /* size of tx/rx filters at sampel rate M */
+#define NFILTERTIMING (M+Nfilter+M) /* filter memory used for resampling after timing estimation */
+
+#define NTEST_BITS (NC*NB*4) /* length of test bit sequence */
+
+/*---------------------------------------------------------------------------*\
+
+ STRUCT for States
+
+\*---------------------------------------------------------------------------*/
+
+struct FDMDV {
+ int current_test_bit;
+ int tx_pilot_bit;
+ COMP prev_tx_symbols[NC+1];
+};
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION PROTOTYPES
+
+\*---------------------------------------------------------------------------*/
+
void bits_to_dqpsk_symbols(COMP tx_symbols[], COMP prev_tx_symbols[], int tx_bits[], int *pilot_bit);
#endif
NAME = libcodec2
AM_CPPFLAGS = $(AM_CFLAGS)
-noinst_PROGRAMS = genres genlsp extract vqtrain vqtrainjnd tnlp tinterp tquant tcodec2 vq_train_jvm scalarlsptest
+noinst_PROGRAMS = genres genlsp extract vqtrain vqtrainjnd tnlp tinterp tquant tcodec2 vq_train_jvm scalarlsptest tfdmdv
genres_SOURCES = genres.c ../src/lpc.c
genres_LDADD = $(lib_LTLIBRARIES)
scalarlsptest_LDADD = $(lib_LTLIBRARIES)
scalarlsptest_LDFLAGS = $(LIBS)
+tfdmdv_SOURCES = tfdmdv.c ../src/fdmdv.c
+tfdmdv_LDADD = $(lib_LTLIBRARIES)
+tfdmdv_LDFLAGS = $(LIBS)
+
noinst_PROGRAMS = genres$(EXEEXT) genlsp$(EXEEXT) extract$(EXEEXT) \
vqtrain$(EXEEXT) vqtrainjnd$(EXEEXT) tnlp$(EXEEXT) \
tinterp$(EXEEXT) tquant$(EXEEXT) tcodec2$(EXEEXT) \
- vq_train_jvm$(EXEEXT) scalarlsptest$(EXEEXT)
+ vq_train_jvm$(EXEEXT) scalarlsptest$(EXEEXT) tfdmdv$(EXEEXT)
subdir = unittest
DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
interp.$(OBJEXT) pack.$(OBJEXT) $(am__objects_1)
tcodec2_OBJECTS = $(am_tcodec2_OBJECTS)
tcodec2_DEPENDENCIES =
+am_tfdmdv_OBJECTS = tfdmdv.$(OBJEXT) fdmdv.$(OBJEXT)
+tfdmdv_OBJECTS = $(am_tfdmdv_OBJECTS)
+tfdmdv_DEPENDENCIES =
am_tinterp_OBJECTS = tinterp.$(OBJEXT) sine.$(OBJEXT) fft.$(OBJEXT) \
kiss_fft.$(OBJEXT) interp.$(OBJEXT) lpc.$(OBJEXT) \
lsp.$(OBJEXT) quantise.$(OBJEXT) $(am__objects_1) \
LINK = $(LIBTOOL) --tag=CC --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
$(AM_LDFLAGS) $(LDFLAGS) -o $@
SOURCES = $(extract_SOURCES) $(genlsp_SOURCES) $(genres_SOURCES) \
- $(scalarlsptest_SOURCES) $(tcodec2_SOURCES) $(tinterp_SOURCES) \
- $(tnlp_SOURCES) $(tquant_SOURCES) $(vq_train_jvm_SOURCES) \
- $(vqtrain_SOURCES) $(vqtrainjnd_SOURCES)
+ $(scalarlsptest_SOURCES) $(tcodec2_SOURCES) $(tfdmdv_SOURCES) \
+ $(tinterp_SOURCES) $(tnlp_SOURCES) $(tquant_SOURCES) \
+ $(vq_train_jvm_SOURCES) $(vqtrain_SOURCES) \
+ $(vqtrainjnd_SOURCES)
DIST_SOURCES = $(extract_SOURCES) $(genlsp_SOURCES) $(genres_SOURCES) \
- $(scalarlsptest_SOURCES) $(tcodec2_SOURCES) $(tinterp_SOURCES) \
- $(tnlp_SOURCES) $(tquant_SOURCES) $(vq_train_jvm_SOURCES) \
- $(vqtrain_SOURCES) $(vqtrainjnd_SOURCES)
+ $(scalarlsptest_SOURCES) $(tcodec2_SOURCES) $(tfdmdv_SOURCES) \
+ $(tinterp_SOURCES) $(tnlp_SOURCES) $(tquant_SOURCES) \
+ $(vq_train_jvm_SOURCES) $(vqtrain_SOURCES) \
+ $(vqtrainjnd_SOURCES)
ETAGS = etags
CTAGS = ctags
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
scalarlsptest_SOURCES = scalarlsptest.c ../src/quantise.c ../src/lpc.c ../src/lsp.c ../src/dump.c ../src/fft.c ../src/kiss_fft.c $(CODEBOOKS)
scalarlsptest_LDADD = $(lib_LTLIBRARIES)
scalarlsptest_LDFLAGS = $(LIBS)
+tfdmdv_SOURCES = tfdmdv.c ../src/fdmdv.c
+tfdmdv_LDADD = $(lib_LTLIBRARIES)
+tfdmdv_LDFLAGS = $(LIBS)
all: all-am
.SUFFIXES:
tcodec2$(EXEEXT): $(tcodec2_OBJECTS) $(tcodec2_DEPENDENCIES)
@rm -f tcodec2$(EXEEXT)
$(LINK) $(tcodec2_LDFLAGS) $(tcodec2_OBJECTS) $(tcodec2_LDADD) $(LIBS)
+tfdmdv$(EXEEXT): $(tfdmdv_OBJECTS) $(tfdmdv_DEPENDENCIES)
+ @rm -f tfdmdv$(EXEEXT)
+ $(LINK) $(tfdmdv_LDFLAGS) $(tfdmdv_OBJECTS) $(tfdmdv_LDADD) $(LIBS)
tinterp$(EXEEXT): $(tinterp_OBJECTS) $(tinterp_DEPENDENCIES)
@rm -f tinterp$(EXEEXT)
$(LINK) $(tinterp_LDFLAGS) $(tinterp_OBJECTS) $(tinterp_LDADD) $(LIBS)
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/codec2.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/extract.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdmdv.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fft.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/genlsp.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/genres.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scalarlsptest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sine.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tcodec2.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tfdmdv.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tinterp.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tnlp.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tquant.Po@am__quote@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o pack.obj `if test -f '../src/pack.c'; then $(CYGPATH_W) '../src/pack.c'; else $(CYGPATH_W) '$(srcdir)/../src/pack.c'; fi`
+fdmdv.o: ../src/fdmdv.c
+@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fdmdv.o -MD -MP -MF "$(DEPDIR)/fdmdv.Tpo" -c -o fdmdv.o `test -f '../src/fdmdv.c' || echo '$(srcdir)/'`../src/fdmdv.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/fdmdv.Tpo" "$(DEPDIR)/fdmdv.Po"; else rm -f "$(DEPDIR)/fdmdv.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='../src/fdmdv.c' object='fdmdv.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fdmdv.o `test -f '../src/fdmdv.c' || echo '$(srcdir)/'`../src/fdmdv.c
+
+fdmdv.obj: ../src/fdmdv.c
+@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fdmdv.obj -MD -MP -MF "$(DEPDIR)/fdmdv.Tpo" -c -o fdmdv.obj `if test -f '../src/fdmdv.c'; then $(CYGPATH_W) '../src/fdmdv.c'; else $(CYGPATH_W) '$(srcdir)/../src/fdmdv.c'; fi`; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/fdmdv.Tpo" "$(DEPDIR)/fdmdv.Po"; else rm -f "$(DEPDIR)/fdmdv.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='../src/fdmdv.c' object='fdmdv.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fdmdv.obj `if test -f '../src/fdmdv.c'; then $(CYGPATH_W) '../src/fdmdv.c'; else $(CYGPATH_W) '$(srcdir)/../src/fdmdv.c'; fi`
+
mostlyclean-libtool:
-rm -f *.lo
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
+#include "fdmdv_internal.h"
#include "fdmdv.h"
+#define FRAMES 10
+
+void octave_save_int(FILE *f, char name[], int data[], int len);
+void octave_save_complex(FILE *f, char name[], COMP data[], int len);
+
int main(int argc, char *argv[])
{
-
- /* save to text file for comparison to Octave version */
+ struct FDMDV *fdmdv;
+ int tx_bits[FDMDV_BITS_PER_FRAME*FRAMES];
+ COMP tx_symbols[(NC+1)*FRAMES];
+ FILE *fout;
+ int f,i;
+
+ fdmdv = fdmdv_create();
+
+ for(f=0; f<FRAMES; f++) {
+ fdmdv_get_test_bits(fdmdv, &tx_bits[FDMDV_BITS_PER_FRAME*f]);
+ bits_to_dqpsk_symbols(&tx_symbols[(NC+1)*f], fdmdv->prev_tx_symbols,
+ &tx_bits[FDMDV_BITS_PER_FRAME*f], &fdmdv->tx_pilot_bit);
+ memcpy(fdmdv->prev_tx_symbols, &tx_symbols[(NC+1)*f], (NC+1)*sizeof(COMP));
+ }
+
+ codec2_destroy(fdmdv);
+
+ /* dump to Octave file for evaluation by Octave script */
+
+ fout = fopen("tfdmdv_out.txt","wt");
+ assert(fout != NULL);
+ fprintf(fout, "# Created by tfdmdv.c\n");
+ octave_save_int(fout, "tx_bits_tfdmdv", tx_bits, FDMDV_BITS_PER_FRAME*FRAMES);
+ octave_save_complex(fout, "tx_symbols_tfdmdv", tx_symbols, (NC+1)*FRAMES);
+ fclose(fout);
return 0;
}
+
+void octave_save_int(FILE *f, char name[], int data[], int len)
+{
+ int i;
+
+ fprintf(f, "# name: %s\n", name);
+ fprintf(f, "# type: matrix\n");
+ fprintf(f, "# rows: %d\n", 1);
+ fprintf(f, "# columns: %d\n", len);
+
+ for(i=0; i<len; i++)
+ fprintf(f, " %d", data[i]);
+
+ fprintf(f, "\n\n\n");
+}
+
+void octave_save_complex(FILE *f, char name[], COMP data[], int len)
+{
+ int i;
+
+ fprintf(f, "# name: %s\n", name);
+ fprintf(f, "# type: complex matrix\n");
+ fprintf(f, "# rows: %d\n", 1);
+ fprintf(f, "# columns: %d\n", len);
+
+ for(i=0; i<len; i++)
+ fprintf(f, " (%f,%f)", data[i].real, data[i].imag);
+
+ fprintf(f, "\n\n\n");
+}