diff --git a/src/Makefile.am b/src/Makefile.am index f2c79a6..fc2538c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,7 +3,7 @@ AM_CFLAGS = -Wall -Werror INCLUDES = $(DEPS_CFLAGS) -bin_PROGRAMS = minimodem tscope +bin_PROGRAMS = minimodem tscope fsk SIMPLEAUDIO_SRC=\ simpleaudio.c \ @@ -13,6 +13,8 @@ SIMPLEAUDIO_SRC=\ minimodem_LDADD = $(DEPS_LIBS) minimodem_SOURCES = minimodem.c tscope_print.c $(SIMPLEAUDIO_SRC) +fsk_LDADD = $(DEPS_LIBS) +fsk_SOURCES = fsk.c tscope_print.c $(SIMPLEAUDIO_SRC) + tscope_LDADD = $(DEPS_LIBS) tscope_SOURCES = tscope.c tscope_print.c - diff --git a/src/Makefile.in b/src/Makefile.in index f4e3f28..20276a5 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -32,7 +32,7 @@ POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : -bin_PROGRAMS = minimodem$(EXEEXT) tscope$(EXEEXT) +bin_PROGRAMS = minimodem$(EXEEXT) tscope$(EXEEXT) fsk$(EXEEXT) subdir = src DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -47,10 +47,13 @@ am__installdirs = "$(DESTDIR)$(bindir)" PROGRAMS = $(bin_PROGRAMS) am__objects_1 = simpleaudio.$(OBJEXT) simpleaudio-pulse.$(OBJEXT) \ simpleaudio-sndfile.$(OBJEXT) +am_fsk_OBJECTS = fsk.$(OBJEXT) tscope_print.$(OBJEXT) $(am__objects_1) +fsk_OBJECTS = $(am_fsk_OBJECTS) +am__DEPENDENCIES_1 = +fsk_DEPENDENCIES = $(am__DEPENDENCIES_1) am_minimodem_OBJECTS = minimodem.$(OBJEXT) tscope_print.$(OBJEXT) \ $(am__objects_1) minimodem_OBJECTS = $(am_minimodem_OBJECTS) -am__DEPENDENCIES_1 = minimodem_DEPENDENCIES = $(am__DEPENDENCIES_1) am_tscope_OBJECTS = tscope.$(OBJEXT) tscope_print.$(OBJEXT) tscope_OBJECTS = $(am_tscope_OBJECTS) @@ -63,8 +66,8 @@ COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) CCLD = $(CC) LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ -SOURCES = $(minimodem_SOURCES) $(tscope_SOURCES) -DIST_SOURCES = $(minimodem_SOURCES) $(tscope_SOURCES) +SOURCES = $(fsk_SOURCES) $(minimodem_SOURCES) $(tscope_SOURCES) +DIST_SOURCES = $(fsk_SOURCES) $(minimodem_SOURCES) $(tscope_SOURCES) ETAGS = etags CTAGS = ctags DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) @@ -165,6 +168,8 @@ SIMPLEAUDIO_SRC = \ minimodem_LDADD = $(DEPS_LIBS) minimodem_SOURCES = minimodem.c tscope_print.c $(SIMPLEAUDIO_SRC) +fsk_LDADD = $(DEPS_LIBS) +fsk_SOURCES = fsk.c tscope_print.c $(SIMPLEAUDIO_SRC) tscope_LDADD = $(DEPS_LIBS) tscope_SOURCES = tscope.c tscope_print.c all: all-am @@ -238,6 +243,9 @@ uninstall-binPROGRAMS: clean-binPROGRAMS: -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +fsk$(EXEEXT): $(fsk_OBJECTS) $(fsk_DEPENDENCIES) + @rm -f fsk$(EXEEXT) + $(LINK) $(fsk_OBJECTS) $(fsk_LDADD) $(LIBS) minimodem$(EXEEXT): $(minimodem_OBJECTS) $(minimodem_DEPENDENCIES) @rm -f minimodem$(EXEEXT) $(LINK) $(minimodem_OBJECTS) $(minimodem_LDADD) $(LIBS) @@ -251,6 +259,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsk.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/minimodem.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simpleaudio-pulse.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simpleaudio-sndfile.Po@am__quote@ diff --git a/src/fsk.c b/src/fsk.c new file mode 100644 index 0000000..486753c --- /dev/null +++ b/src/fsk.c @@ -0,0 +1,437 @@ +#define USE_FFT + +#include +#include +#include // fabs, hypotf +#ifdef USE_FFT +#include +#endif + +#include + +#include +#include + + +//#define FSK_DEBUG + +#ifdef FSK_DEBUG +# define debug_log(format, args...) fprintf(stderr, format, ## args) +#else +# define debug_log(format, args...) +#endif + + +typedef struct fsk_plan fsk_plan; + +struct fsk_plan { + float sample_rate; + float f_mark; + float f_space; + float filter_bw; + unsigned int n_data_bits; +#ifdef USE_FFT + int fftsize; // fftw wants this to be signed. why? + unsigned int b_mark; + unsigned int b_space; + fftwf_plan fftplan; + float *fftin; + fftwf_complex *fftout; +#endif +}; + + +fsk_plan * +fsk_plan_new( + float sample_rate, + float f_mark, + float f_space, + float filter_bw, + unsigned int n_data_bits + ) +{ + fsk_plan *fskp = malloc(sizeof(fsk_plan)); + if ( !fskp ) + return NULL; + + fskp->sample_rate = sample_rate; + fskp->f_mark = f_mark; + fskp->f_space = f_space; + fskp->filter_bw = filter_bw; + fskp->n_data_bits = n_data_bits; + +#ifdef USE_FFT + unsigned int fft_bw = filter_bw; + + float fft_half_bw = (float)fft_bw / 2.0; + fskp->fftsize = (sample_rate + fft_half_bw) / fft_bw; + unsigned int nbands = fskp->fftsize / 2 + 1; + + fskp->b_mark = (f_mark + fft_half_bw) / fft_bw; + fskp->b_space = (f_space + fft_half_bw) / fft_bw; + if ( fskp->b_mark >= nbands || fskp->b_space >= nbands ) { + fprintf(stderr, "b_mark=%u or b_space=%u is invalid (nbands=%u)\n", + fskp->b_mark, fskp->b_space, nbands); + free(fskp); + errno = EINVAL; + return NULL; + } + debug_log("### b_mark=%u b_space=%u fftsize=%u\n", + fskp->b_mark, fskp->b_space, fskp->fftsize); + + + // FIXME: + unsigned int pa_nchannels = 1; + + // FIXME check these: + fskp->fftin = fftwf_malloc(fskp->fftsize * sizeof(float) * pa_nchannels); + fskp->fftout = fftwf_malloc(nbands * sizeof(fftwf_complex) * pa_nchannels); + + /* complex fftw plan, works for N channels: */ + fskp->fftplan = fftwf_plan_many_dft_r2c( + /*rank*/1, &fskp->fftsize, /*howmany*/pa_nchannels, + fskp->fftin, NULL, /*istride*/pa_nchannels, /*idist*/1, + fskp->fftout, NULL, /*ostride*/1, /*odist*/nbands, + FFTW_ESTIMATE); + + if ( !fskp->fftplan ) { + fprintf(stderr, "fftwf_plan_dft_r2c_1d() failed\n"); + fftwf_free(fskp->fftin); + fftwf_free(fskp->fftout); + free(fskp); + errno = EINVAL; + return NULL; + } +#endif + + return fskp; +} + +void +fsk_plan_destroy( fsk_plan *fskp ) +{ + fftwf_free(fskp->fftin); + fftwf_free(fskp->fftout); + fftwf_destroy_plan(fskp->fftplan); + free(fskp); +} + + +static inline float +band_mag( fftwf_complex * const cplx, unsigned int band, float scalar ) +{ + float re = cplx[band][0]; + float im = cplx[band][1]; + float mag = hypotf(re, im) * scalar; + return mag; +} + + +static void +fsk_bit_analyze( fsk_plan *fskp, float *samples, unsigned int bit_nsamples, + float *mag_mark_outp, float *mag_space_outp) +{ + unsigned int pa_nchannels = 1; + bzero(fskp->fftin, (fskp->fftsize * sizeof(float) * pa_nchannels)); + memcpy(fskp->fftin, samples, bit_nsamples * sizeof(float)); + fftwf_execute(fskp->fftplan); + float magscalar = 1.0 / ((float)bit_nsamples/2.0); + *mag_mark_outp = band_mag(fskp->fftout, fskp->b_mark, magscalar); + *mag_space_outp = band_mag(fskp->fftout, fskp->b_space, magscalar); + debug_log( "\t%.2f %.2f %s sig=%.2f\n", + *mag_mark_outp, *mag_space_outp, + *mag_mark_outp > *mag_space_outp ? "mark " : " space", + fabs(*mag_mark_outp - *mag_space_outp) + ); +} + + +static float +fsk_frame_decode( fsk_plan *fskp, float *samples, unsigned int frame_nsamples, + unsigned int *bits_outp ) +{ + float v = 0; + + /* 1 prev_stop + n_data_bits + 1 start + 1 stop == n_data_bits + 3 */ + float samples_per_bit = (float)frame_nsamples / (fskp->n_data_bits + 3); + + unsigned int bit_nsamples = (float)(samples_per_bit + 0.5); + + // 0123456789A + // isddddddddp i == idle bit (a.k.a. previous stop bit) + // s == start bit + // d == data bits + // p == stop bit + // MSddddddddM <-- expected mark/space framing pattern + + unsigned int begin_i_idlebit = 0; + unsigned int begin_s_startbit = (float)(samples_per_bit * 1 + 0.5); + unsigned int begin_p_stopbit = (float)(samples_per_bit * 10 + 0.5); + + /* + * To optimize performance for a streaming scenario, check start bit first, + * then stop, then idle bits... we're "searching" for start, must validate + * stop, and finally we want to to collect idle's v value. After all that + * collect the n_data_bits + */ + + float sM, sS, pM, pS, iM, iS; + + unsigned int bit; + +debug_log("\t\tstart "); + fsk_bit_analyze(fskp, samples+begin_s_startbit, bit_nsamples, &sM, &sS); + bit = sM > sS; + if ( bit != 0 ) + return 0.0; + v += sS - sM; + +debug_log("\t\tstop "); + fsk_bit_analyze(fskp, samples+begin_p_stopbit, bit_nsamples, &pM, &pS); + bit = pM > pS; + if ( bit != 1 ) + return 0.0; + v += pM - pS; + +debug_log("\t\tidle "); + fsk_bit_analyze(fskp, samples+begin_i_idlebit, bit_nsamples, &iM, &iS); + bit = iM > iS; + if ( bit != 1 ) + return 0.0; + v += iM - iS; + + unsigned int bits_out = 0; + int i; + for ( i=0; in_data_bits; i++ ) { + unsigned int begin = (float)(samples_per_bit * (i+2) + 0.5); + float dM, dS; +debug_log("\t\tdata "); + fsk_bit_analyze(fskp, samples+begin, bit_nsamples, &dM, &dS); + bit = dM > dS; + v += fabs(dM - dS); + bits_out |= bit << i; + } + *bits_outp = bits_out; + +debug_log( "v=%f\n", v ); + return v; +} + + +int +fsk_find_frame( fsk_plan *fskp, float *samples, unsigned int frame_nsamples, + unsigned int try_max_nsamples, + unsigned int try_step_nsamples, + unsigned int *bits_outp + ) +{ + unsigned int t; + unsigned int best_t = 0; + float best_v = 0.0; + unsigned int best_bits = 0; + for ( t=0; t +#include + +#include "simpleaudio.h" +#include "tscope_print.h" + +int +main( int argc, char*argv[] ) +{ + + int ret = 1; + + if ( argc < 2 ) { + fprintf(stderr, "usage: fsk [filename] baud_rate [ mark_hz space_hz ]\n"); + return 1; + } + + unsigned char textscope = 0; + int argi = 1; + if ( argi < argc && strcmp(argv[argi],"-s") == 0 ) { + textscope = 1; + argi++; + } + + simpleaudio *sa; + + char *p; + for ( p=argv[argi]; *p; p++ ) + if ( !isdigit(*p) ) + break; + if ( *p ) { + sa = simpleaudio_open_source_sndfile(argv[argi]); + argi++; + } else { + sa = simpleaudio_open_source_pulseaudio(argv[0], "bfsk demodulator"); + } + if ( !sa ) + return 1; + + unsigned int sample_rate = simpleaudio_get_rate(sa); +// unsigned int nchannels = simpleaudio_get_channels(sa); + + + unsigned int decode_rate; + decode_rate = atoi(argv[argi++]); + + unsigned int band_width; + band_width = decode_rate; + + /* + * Bell 103: baud=300 mark=1270 space=1070 + * ITU-T V.21: baud=300 mark=1280 space=1080 + */ + unsigned int bfsk_mark_f = 1270; + unsigned int bfsk_space_f = 1070; + // band_width = 10; + band_width = 100; /* close enough */ + + /* + * Bell 202: baud=1200 mark=1200 space=2200 + */ + if ( decode_rate >= 400 ) { + bfsk_mark_f = 1200; + bfsk_space_f = 2200; + band_width = 200; + } + + if ( argi < argc ) { + assert(argc-argi == 2); + bfsk_mark_f = atoi(argv[argi++]); + bfsk_space_f = atoi(argv[argi++]); + } + + /* + * Prepare the input sample chunk rate + */ + int nsamples_per_bit = sample_rate / decode_rate; + + + /* + * Run the main loop + */ + + ret = 0; + + + + + + fsk_plan *fskp = fsk_plan_new(sample_rate, + bfsk_mark_f, bfsk_space_f, + band_width, 8); + + if ( !fskp ) { + fprintf(stderr, "fsk_plan_new() failed\n"); + return 1; + } + + + size_t fill_nsamples = nsamples_per_bit * 12; + + size_t buf_nsamples = fill_nsamples; + float *samples = malloc(buf_nsamples * sizeof(float)); + + size_t read_nsamples = fill_nsamples; + float *read_bufptr = samples; + + int carrier = 0; + + while ( 1 ) { + + debug_log( "@read samples+%ld n=%lu\n", + read_bufptr - samples, read_nsamples); + + if ((ret=simpleaudio_read(sa, read_bufptr, read_nsamples)) <= 0) + break; +// carrier_nsamples += read_nsamples; + + + unsigned int frame_nsamples = nsamples_per_bit * 11; + + unsigned int bits = 0; + + unsigned int try_step_nsamples = nsamples_per_bit / 4; + if ( try_step_nsamples == 0 ) + try_step_nsamples = 1; + + +debug_log( "--------------------------\n"); + int t = + fsk_find_frame(fskp, samples, frame_nsamples, + /*try_max_nsamples*/ nsamples_per_bit, + try_step_nsamples, + &bits + ); + + if ( t <= 0 ) { + if ( carrier ) + fprintf(stderr, "\n### NOCARRIER ###\n"); + carrier = 0; + + t = nsamples_per_bit; + + } else { + if ( !carrier ) + fprintf(stderr, "\n### CARRIER ###\n"); + carrier = 1; + + // t += nsamples_per_bit * 10; + t += nsamples_per_bit * 9.5; + debug_log( "@ t=%u\n", t); + + char the_byte = isprint(bits)||isspace(bits) ? bits : '.'; + printf( "%c", the_byte ); + fflush(stdout); + + } + + memmove(samples, samples+t, (fill_nsamples-t)*sizeof(float)); + read_bufptr = samples + (fill_nsamples-t); + read_nsamples = t; + + assert ( read_nsamples <= buf_nsamples ); + assert ( read_nsamples > 0 ); + + } + + if ( ret != 0 ) + fprintf(stderr, "simpleaudio_read: error\n"); + + simpleaudio_close(sa); + + fsk_plan_destroy(fskp); + + return ret; +}