minimodem, miniscope: initial implementation
This commit is contained in:
parent
48ea587ea2
commit
cdde307640
|
@ -3395,16 +3395,19 @@ if test -n "$DEPS_CFLAGS"; then
|
|||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
") 2>&5
|
||||
ac_status=$?
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_DEPS_CFLAGS=`$PKG_CONFIG --cflags "
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
" 2>/dev/null`
|
||||
else
|
||||
pkg_failed=yes
|
||||
|
@ -3417,16 +3420,19 @@ if test -n "$DEPS_LIBS"; then
|
|||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
") 2>&5
|
||||
ac_status=$?
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_DEPS_LIBS=`$PKG_CONFIG --libs "
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
" 2>/dev/null`
|
||||
else
|
||||
pkg_failed=yes
|
||||
|
@ -3448,18 +3454,21 @@ else
|
|||
fi
|
||||
if test $_pkg_short_errors_supported = yes; then
|
||||
DEPS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
" 2>&1`
|
||||
else
|
||||
DEPS_PKG_ERRORS=`$PKG_CONFIG --print-errors "
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
" 2>&1`
|
||||
fi
|
||||
# Put the nasty error message in config.log where it belongs
|
||||
echo "$DEPS_PKG_ERRORS" >&5
|
||||
|
||||
as_fn_error $? "Package requirements (
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
) were not met:
|
||||
|
||||
$DEPS_PKG_ERRORS
|
||||
|
|
|
@ -29,7 +29,8 @@ AC_PROG_CC
|
|||
|
||||
# Checks for libraries.
|
||||
PKG_CHECK_MODULES(DEPS, [
|
||||
glib-2.0
|
||||
libpulse-simple
|
||||
fftw3f
|
||||
])
|
||||
AC_SUBST(DEPS_CFLAGS)
|
||||
AC_SUBST(DEPS_LIBS)
|
||||
|
|
|
@ -3,10 +3,11 @@ AM_CFLAGS = -Wall -Werror
|
|||
INCLUDES = $(DEPS_CFLAGS)
|
||||
|
||||
|
||||
bin_PROGRAMS = minimodem
|
||||
bin_PROGRAMS = minimodem miniscope
|
||||
|
||||
minimodem_LDADD = $(DEPS_LIBS)
|
||||
minimodem_VERSION = 0.1.1
|
||||
minimodem_SOURCES = junkprog.c
|
||||
minimodem_SOURCES = minimodem.c
|
||||
|
||||
miniscope_LDADD = $(DEPS_LIBS)
|
||||
miniscope_SOURCES = miniscope.c
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ POST_INSTALL = :
|
|||
NORMAL_UNINSTALL = :
|
||||
PRE_UNINSTALL = :
|
||||
POST_UNINSTALL = :
|
||||
bin_PROGRAMS = minimodem$(EXEEXT)
|
||||
bin_PROGRAMS = minimodem$(EXEEXT) miniscope$(EXEEXT)
|
||||
subdir = src
|
||||
DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
|
||||
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
|
||||
|
@ -45,10 +45,13 @@ CONFIG_CLEAN_FILES =
|
|||
CONFIG_CLEAN_VPATH_FILES =
|
||||
am__installdirs = "$(DESTDIR)$(bindir)"
|
||||
PROGRAMS = $(bin_PROGRAMS)
|
||||
am_minimodem_OBJECTS = junkprog.$(OBJEXT)
|
||||
am_minimodem_OBJECTS = minimodem.$(OBJEXT)
|
||||
minimodem_OBJECTS = $(am_minimodem_OBJECTS)
|
||||
am__DEPENDENCIES_1 =
|
||||
minimodem_DEPENDENCIES = $(am__DEPENDENCIES_1)
|
||||
am_miniscope_OBJECTS = miniscope.$(OBJEXT)
|
||||
miniscope_OBJECTS = $(am_miniscope_OBJECTS)
|
||||
miniscope_DEPENDENCIES = $(am__DEPENDENCIES_1)
|
||||
DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
|
||||
depcomp = $(SHELL) $(top_srcdir)/depcomp
|
||||
am__depfiles_maybe = depfiles
|
||||
|
@ -57,8 +60,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)
|
||||
DIST_SOURCES = $(minimodem_SOURCES)
|
||||
SOURCES = $(minimodem_SOURCES) $(miniscope_SOURCES)
|
||||
DIST_SOURCES = $(minimodem_SOURCES) $(miniscope_SOURCES)
|
||||
ETAGS = etags
|
||||
CTAGS = ctags
|
||||
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||
|
@ -153,8 +156,9 @@ top_srcdir = @top_srcdir@
|
|||
AM_CFLAGS = -Wall -Werror
|
||||
INCLUDES = $(DEPS_CFLAGS)
|
||||
minimodem_LDADD = $(DEPS_LIBS)
|
||||
minimodem_VERSION = 0.1.1
|
||||
minimodem_SOURCES = junkprog.c
|
||||
minimodem_SOURCES = minimodem.c
|
||||
miniscope_LDADD = $(DEPS_LIBS)
|
||||
miniscope_SOURCES = miniscope.c
|
||||
all: all-am
|
||||
|
||||
.SUFFIXES:
|
||||
|
@ -229,6 +233,9 @@ clean-binPROGRAMS:
|
|||
minimodem$(EXEEXT): $(minimodem_OBJECTS) $(minimodem_DEPENDENCIES)
|
||||
@rm -f minimodem$(EXEEXT)
|
||||
$(LINK) $(minimodem_OBJECTS) $(minimodem_LDADD) $(LIBS)
|
||||
miniscope$(EXEEXT): $(miniscope_OBJECTS) $(miniscope_DEPENDENCIES)
|
||||
@rm -f miniscope$(EXEEXT)
|
||||
$(LINK) $(miniscope_OBJECTS) $(miniscope_LDADD) $(LIBS)
|
||||
|
||||
mostlyclean-compile:
|
||||
-rm -f *.$(OBJEXT)
|
||||
|
@ -236,7 +243,8 @@ mostlyclean-compile:
|
|||
distclean-compile:
|
||||
-rm -f *.tab.c
|
||||
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/junkprog.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/minimodem.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/miniscope.Po@am__quote@
|
||||
|
||||
.c.o:
|
||||
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
#define G_LOG_DOMAIN "minimodem"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
g_debug("hello world from junkprog");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,407 @@
|
|||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/gccmacro.h>
|
||||
|
||||
#include <fftw3.h>
|
||||
|
||||
|
||||
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 = hypot(re, im) * scalar;
|
||||
return mag;
|
||||
}
|
||||
|
||||
int main(int argc, char*argv[]) {
|
||||
/* The sample type to use */
|
||||
static const pa_sample_spec ss = {
|
||||
.format = PA_SAMPLE_FLOAT32,
|
||||
.rate = 48000, // pulseaudio will resample its configured audio rate
|
||||
|
||||
// .channels = 2 // 2 channel stereo
|
||||
.channels = 1 // pulseaudio will downmix (additively) to 1 channel
|
||||
// .channels = 3 // 2 channel stereo + 1 mixed channel
|
||||
};
|
||||
int ret = 1;
|
||||
int error;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Bell 103: mark=1270 space=1070
|
||||
* ITU-T V.21: mark=1280 space=1080
|
||||
*/
|
||||
unsigned int bfsk_mark_f = 1270;
|
||||
unsigned int bfsk_space_f = 1070;
|
||||
|
||||
if ( argc < 2 ) {
|
||||
fprintf(stderr, "usage: minimodem 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++;
|
||||
}
|
||||
|
||||
unsigned int decode_rate = atoi(argv[argi++]);
|
||||
|
||||
if ( argi < argc ) {
|
||||
assert(argc-argi == 2);
|
||||
bfsk_mark_f = atoi(argv[argi++]);
|
||||
bfsk_space_f = atoi(argv[argi++]);
|
||||
}
|
||||
|
||||
unsigned int sample_rate = ss.rate;
|
||||
|
||||
unsigned int band_width = decode_rate / 2;
|
||||
|
||||
unsigned int bfsk_mark_band = (bfsk_mark_f +(float)band_width/2) / band_width;
|
||||
unsigned int bfsk_space_band = (bfsk_space_f +(float)band_width/2) / band_width;
|
||||
|
||||
|
||||
if ( bfsk_mark_band == 0 || bfsk_space_band == 0 ) {
|
||||
fprintf(stderr, __FILE__": mark or space band is at dsp DC\n");
|
||||
// return 1;
|
||||
}
|
||||
if ( bfsk_mark_band == bfsk_space_band ) {
|
||||
fprintf(stderr, __FILE__": inadequate mark/space separation\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Create the recording stream */
|
||||
pa_simple *s;
|
||||
s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error);
|
||||
if ( !s ) {
|
||||
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pa_samplesize = pa_sample_size(&ss);
|
||||
int pa_framesize = pa_frame_size(&ss);
|
||||
int pa_nchannels = ss.channels;
|
||||
|
||||
assert( pa_framesize == pa_samplesize * pa_nchannels );
|
||||
|
||||
|
||||
/* Create the FFT plan */
|
||||
fftwf_plan fftplan;
|
||||
|
||||
int fftsize = sample_rate / band_width;
|
||||
|
||||
if ( fftsize & 1 )
|
||||
fprintf(stderr, __FILE__": WARNING: fftsize %u is not even\n", fftsize);
|
||||
|
||||
unsigned int nbands = fftsize / 2 + 1;
|
||||
|
||||
float *fftin = fftwf_malloc(fftsize * sizeof(float) * pa_nchannels);
|
||||
|
||||
fftwf_complex *fftout = fftwf_malloc(nbands * sizeof(fftwf_complex) * pa_nchannels);
|
||||
|
||||
/*
|
||||
* works only for 1 channel:
|
||||
fftplan = fftwf_plan_dft_r2c_1d(fftsize, fftin, fftout, FFTW_ESTIMATE);
|
||||
*/
|
||||
/*
|
||||
* works for N channels:
|
||||
*/
|
||||
fftplan = fftwf_plan_many_dft_r2c(
|
||||
/*rank*/1, &fftsize, /*howmany*/pa_nchannels,
|
||||
fftin, NULL, /*istride*/pa_nchannels, /*idist*/1,
|
||||
fftout, NULL, /*ostride*/1, /*odist*/nbands,
|
||||
FFTW_ESTIMATE | FFTW_PRESERVE_INPUT );
|
||||
/* Nb. FFTW_PRESERVE_INPUT is needed for the "shift the input window" trick */
|
||||
|
||||
if ( !fftplan ) {
|
||||
fprintf(stderr, __FILE__": fftwf_plan_dft_r2c_1d() failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void *pa_samples_in = fftin; // read samples directly into fftin
|
||||
assert( pa_samplesize == sizeof(float) );
|
||||
|
||||
|
||||
/*
|
||||
* Prepare the input sample chunk rate
|
||||
*/
|
||||
int nsamples = sample_rate / decode_rate;
|
||||
nsamples -= 1; // BLACK MAGIC!
|
||||
|
||||
// float magscalar = 1.0 / (fftsize/2.0); /* normalize fftw output */
|
||||
float magscalar = 1.0 / (nsamples/2.0); /* normalize fftw output */
|
||||
|
||||
|
||||
float actual_decode_rate = (float)sample_rate / nsamples;
|
||||
fprintf(stderr, "### baud=%.2f mark=%u space=%u ###\n",
|
||||
actual_decode_rate,
|
||||
bfsk_mark_band * band_width,
|
||||
bfsk_space_band * band_width
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Run the main loop
|
||||
*/
|
||||
|
||||
unsigned int bfsk_bits = 0xFFFFFFFF;
|
||||
unsigned char carrier_detected = 0;
|
||||
|
||||
ret = 0;
|
||||
|
||||
while ( 1 ) {
|
||||
|
||||
size_t nframes = nsamples;
|
||||
size_t nbytes = nframes * pa_framesize;
|
||||
|
||||
bzero(fftin, (fftsize * sizeof(float) * pa_nchannels));
|
||||
if (pa_simple_read(s, pa_samples_in, nbytes, &error) < 0) {
|
||||
fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n",
|
||||
pa_strerror(error));
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
#define TRICK
|
||||
|
||||
#ifdef TRICK
|
||||
reprocess_audio:
|
||||
{
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_PA_FORMAT_S16LE
|
||||
{ // convert S16LE samples to float [-1.0:+1.0]
|
||||
int j;
|
||||
for ( j=0; j<fftsize; j++ )
|
||||
fftin[j] = s16le_buf[j] / (float)(1<<15);
|
||||
}
|
||||
#endif
|
||||
|
||||
float inmax=0, inmin=0;
|
||||
int i;
|
||||
for ( i=0; i<fftsize; i++ ) {
|
||||
// if ( fftin[i] > 1.0 || fftin[i] < -1.0 )
|
||||
// fprintf(stderr, __FILE__": WARNING input datum %.3f\n", fftin[i]);
|
||||
if ( inmin > fftin[i] )
|
||||
inmin = fftin[i];
|
||||
if ( inmax < fftin[i] )
|
||||
inmax = fftin[i];
|
||||
}
|
||||
|
||||
|
||||
fftwf_execute(fftplan);
|
||||
|
||||
/* examine channel 0 only */
|
||||
float mag_mark = band_mag(fftout, bfsk_mark_band, magscalar);
|
||||
float mag_space = band_mag(fftout, bfsk_space_band, magscalar);
|
||||
|
||||
|
||||
static unsigned char lastbit;
|
||||
|
||||
#if 0 // TEST -- doesn't help?, sometimes gets it wrong
|
||||
// "clarify" mag_mark and mag_space according to whether lastbit
|
||||
// was a mark or a space ... If lastbit was a mark, then enhance
|
||||
// space and vice-versa
|
||||
float clarify_factor = 2.0;
|
||||
if ( lastbit ) {
|
||||
mag_space *= clarify_factor;
|
||||
} else {
|
||||
mag_mark *= clarify_factor;
|
||||
}
|
||||
#endif
|
||||
|
||||
float msdelta = mag_mark - mag_space;
|
||||
|
||||
|
||||
// Detect carrier
|
||||
float mag_detect = 0.01;
|
||||
/* pulseaudio *adds* when downmixing 2 channels to 1; if we're using only
|
||||
* one channel here, we blindly assume that pulseaudio downmixed from 2. */
|
||||
if ( pa_nchannels == 1 )
|
||||
mag_detect *= 2.0;
|
||||
unsigned char carrier_detect = mag_mark + mag_space > mag_detect ? 1 : 0;
|
||||
|
||||
|
||||
#ifdef TRICK
|
||||
|
||||
// #define TRICK_DETECT 0.0
|
||||
// #define TRICK_DETECT 0.4
|
||||
// #define TRICK_DETECT 0.1
|
||||
|
||||
// #define TRICK_DETECT mag_detect
|
||||
#define TRICK_DETECT ( 0.1 * (float)decode_rate/300 )
|
||||
|
||||
// EXCELLENT trick -- fixes 300 baud perfectly
|
||||
// shift the input window if the msdelta is small
|
||||
static unsigned int skipped_frames = 0;
|
||||
if ( carrier_detected && fabs(msdelta) < TRICK_DETECT )
|
||||
{
|
||||
# if 0
|
||||
skipped_frames++;
|
||||
if ( skipped_frames >= nsamples ) // maybe nsamples/2 ??
|
||||
nframes = 0;
|
||||
else
|
||||
nframes = 1;
|
||||
printf( "*" );
|
||||
# else
|
||||
|
||||
if ( nframes == nsamples ) {
|
||||
// nframes = nsamples / 2;
|
||||
// nframes = nsamples / 16; // shift by 1/4 the bit width
|
||||
nframes = 1;
|
||||
nframes = nframes ? nframes : 1;
|
||||
}
|
||||
skipped_frames += nframes;
|
||||
if ( skipped_frames >= nsamples ) // maybe nsamples/2 ??
|
||||
nframes = 0;
|
||||
# endif
|
||||
if ( nframes ) {
|
||||
size_t nbytes = nframes * pa_framesize;
|
||||
size_t reuse_bytes = nsamples*pa_framesize - nbytes;
|
||||
memmove(pa_samples_in, pa_samples_in+nbytes, reuse_bytes);
|
||||
void *in = pa_samples_in + reuse_bytes;
|
||||
if (pa_simple_read(s, in, nbytes, &error) < 0) {
|
||||
fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n",
|
||||
pa_strerror(error));
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
goto reprocess_audio;
|
||||
}
|
||||
}
|
||||
|
||||
if ( textscope ) {
|
||||
if ( skipped_frames )
|
||||
printf( "<skipped %u (of %u) frames>\n",
|
||||
skipped_frames, nsamples);
|
||||
}
|
||||
|
||||
skipped_frames = 0;
|
||||
#endif
|
||||
|
||||
unsigned char bit;
|
||||
|
||||
if ( carrier_detect ) {
|
||||
bit = signbit(msdelta) ? 0 : 1;
|
||||
|
||||
#if 0
|
||||
static unsigned char lastbit_strong = 0;
|
||||
if ( fabs(msdelta) < 0.5 ) { // TEST
|
||||
if ( lastbit_strong )
|
||||
bit = !lastbit;
|
||||
lastbit_strong = 0;
|
||||
} else {
|
||||
lastbit_strong = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
else
|
||||
bit = 1;
|
||||
|
||||
lastbit = bit;
|
||||
|
||||
// save 11 bits:
|
||||
// stop--- v v--- start bit
|
||||
// v v--- prev stop bit
|
||||
// 1dddddddd01
|
||||
bfsk_bits = (bfsk_bits>>1) | (bit << 10);
|
||||
|
||||
if ( ! carrier_detect ) {
|
||||
if ( carrier_detected ) {
|
||||
printf( "###NOCARRIER###\n");
|
||||
carrier_detected = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! carrier_detected )
|
||||
printf( "###CARRIER###\n");
|
||||
carrier_detected = carrier_detect;
|
||||
|
||||
if ( textscope ) {
|
||||
printf("%s %c ",
|
||||
carrier_detected ? "CD" : " ",
|
||||
carrier_detected ? ( bit ? '1' : '0' ) : ' ');
|
||||
|
||||
float magmax = 0;
|
||||
|
||||
for ( i=0; i<nbands*pa_nchannels; i++ ) {
|
||||
if ( i%nbands == 0 )
|
||||
printf("|");
|
||||
float mag = band_mag(fftout, i, magscalar);
|
||||
if ( mag > magmax )
|
||||
magmax = mag;
|
||||
char *magchars = " .-=^";
|
||||
if ( i%nbands == bfsk_mark_band )
|
||||
magchars = " mMM^";
|
||||
if ( i%nbands == bfsk_space_band )
|
||||
magchars = " sSS^";
|
||||
char c = magchars[0];
|
||||
if ( mag > 0.10 ) c = magchars[1];
|
||||
if ( mag > 0.25 ) c = magchars[2];
|
||||
if ( mag > 0.50 ) c = magchars[3];
|
||||
if ( mag > 1.00 ) c = magchars[4];
|
||||
printf("%c", c);
|
||||
|
||||
if ( i > 30 )
|
||||
break;
|
||||
}
|
||||
printf("| in[%+4.2f %+4.2f] >mag %+.2f", inmin, inmax, magmax);
|
||||
|
||||
printf(" ");
|
||||
for ( i=15; i>=0; i-- )
|
||||
printf("%c", bfsk_bits & (1<<i) ? '1' : '0');
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
if ( ! carrier_detected ) {
|
||||
if ( textscope )
|
||||
printf("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// stop--- v v--- start bit
|
||||
// v v--- prev stop bit
|
||||
if ( ( bfsk_bits & 0b10000000011 )
|
||||
== 0b10000000001 ) { // valid frame: start=space, stop=mark
|
||||
unsigned char byte = ( bfsk_bits >> 2) & 0xFF;
|
||||
if ( textscope )
|
||||
printf("+");
|
||||
printf("%c", isspace(byte)||isprint(byte) ? byte : '.');
|
||||
fflush(stdout);
|
||||
bfsk_bits = 1 << 10;
|
||||
}
|
||||
if ( textscope )
|
||||
printf("\n");
|
||||
|
||||
|
||||
}
|
||||
|
||||
pa_simple_free(s);
|
||||
|
||||
fftwf_free(fftin);
|
||||
fftwf_free(fftout);
|
||||
fftwf_destroy_plan(fftplan);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* miniscope.c
|
||||
*
|
||||
* Author: Kamal Mostafa <kamal@whence.com>
|
||||
*
|
||||
* Unpublished work, not licensed for any purpose.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/gccmacro.h>
|
||||
|
||||
#include <fftw3.h>
|
||||
|
||||
|
||||
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 = sqrtf(re*re + im*im) * scalar;
|
||||
return mag;
|
||||
}
|
||||
|
||||
int main(int argc, char*argv[]) {
|
||||
/* The sample type to use */
|
||||
static const pa_sample_spec ss = {
|
||||
.format = PA_SAMPLE_FLOAT32,
|
||||
.rate = 9600, // pulseaudio will resample its configured audio rate
|
||||
|
||||
.channels = 2 // 2 channel stereo
|
||||
// .channels = 1 // pulseaudio will downmix (additively) to 1 channel
|
||||
// .channels = 3 // 2 channel stereo + 1 mixed channel
|
||||
};
|
||||
int ret = 1;
|
||||
int error;
|
||||
|
||||
|
||||
if ( argc < 2 ) {
|
||||
fprintf(stderr, "usage: miniscope baud_rate [ band_width ]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int argi = 1;
|
||||
unsigned int decode_rate = atoi(argv[argi++]);
|
||||
unsigned int band_width = decode_rate;
|
||||
if ( argi < argc )
|
||||
band_width = atoi(argv[argi++]);
|
||||
assert( band_width <= decode_rate );
|
||||
|
||||
unsigned int sample_rate = ss.rate;
|
||||
|
||||
|
||||
|
||||
/* Create the recording stream */
|
||||
pa_simple *s;
|
||||
s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error);
|
||||
if ( !s ) {
|
||||
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pa_samplesize = pa_sample_size(&ss);
|
||||
int pa_framesize = pa_frame_size(&ss);
|
||||
int pa_nchannels = ss.channels;
|
||||
|
||||
assert( pa_framesize == pa_samplesize * pa_nchannels );
|
||||
|
||||
|
||||
/* Create the FFT plan */
|
||||
fftwf_plan fftplan;
|
||||
|
||||
int fftsize = sample_rate / band_width;
|
||||
|
||||
if ( fftsize & 1 )
|
||||
fprintf(stderr, __FILE__": WARNING: fftsize %u is not even\n", fftsize);
|
||||
|
||||
unsigned int nbands = fftsize / 2 + 1;
|
||||
|
||||
float *fftin = fftwf_malloc(fftsize * sizeof(float) * pa_nchannels);
|
||||
|
||||
fftwf_complex *fftout = fftwf_malloc(nbands * sizeof(fftwf_complex) * pa_nchannels);
|
||||
|
||||
/*
|
||||
* works only for 1 channel:
|
||||
fftplan = fftwf_plan_dft_r2c_1d(fftsize, fftin, fftout, FFTW_ESTIMATE);
|
||||
*/
|
||||
/*
|
||||
* works for N channels:
|
||||
*/
|
||||
fftplan = fftwf_plan_many_dft_r2c(
|
||||
/*rank*/1, &fftsize, /*howmany*/pa_nchannels,
|
||||
fftin, NULL, /*istride*/pa_nchannels, /*idist*/1,
|
||||
fftout, NULL, /*ostride*/1, /*odist*/nbands,
|
||||
FFTW_ESTIMATE | FFTW_PRESERVE_INPUT );
|
||||
/* Nb. FFTW_PRESERVE_INPUT is needed for the "shift the input window" trick */
|
||||
|
||||
if ( !fftplan ) {
|
||||
fprintf(stderr, __FILE__": fftwf_plan_dft_r2c_1d() failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void *pa_samples_in = fftin; // read samples directly into fftin
|
||||
assert( pa_samplesize == sizeof(float) );
|
||||
|
||||
|
||||
/*
|
||||
* Prepare the input sample chunk rate
|
||||
*/
|
||||
int nsamples = sample_rate / decode_rate;
|
||||
|
||||
// float magscalar = 1.0 / (fftsize/2.0); /* normalize fftw output */
|
||||
float magscalar = 1.0 / (nsamples/2.0); /* normalize fftw output */
|
||||
|
||||
|
||||
float actual_decode_rate = (float)sample_rate / nsamples;
|
||||
fprintf(stderr, "### baud=%.2f ###\n", actual_decode_rate);
|
||||
|
||||
|
||||
/*
|
||||
* Run the main loop
|
||||
*/
|
||||
|
||||
ret = 0;
|
||||
|
||||
while ( 1 ) {
|
||||
|
||||
size_t nframes = nsamples;
|
||||
size_t nbytes = nframes * pa_framesize;
|
||||
|
||||
bzero(fftin, (fftsize * sizeof(float) * pa_nchannels));
|
||||
if (pa_simple_read(s, pa_samples_in, nbytes, &error) < 0) {
|
||||
fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n",
|
||||
pa_strerror(error));
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
float inmax=0, inmin=0;
|
||||
int i;
|
||||
for ( i=0; i<fftsize; i++ ) {
|
||||
// if ( fftin[i] > 1.0 || fftin[i] < -1.0 )
|
||||
// fprintf(stderr, __FILE__": WARNING input datum %.3f\n", fftin[i]);
|
||||
if ( inmin > fftin[i] )
|
||||
inmin = fftin[i];
|
||||
if ( inmax < fftin[i] )
|
||||
inmax = fftin[i];
|
||||
}
|
||||
|
||||
|
||||
fftwf_execute(fftplan);
|
||||
|
||||
{
|
||||
float magmax = 0;
|
||||
|
||||
for ( i=0; i<nbands*pa_nchannels; i++ ) {
|
||||
if ( i%nbands == 0 )
|
||||
printf("|");
|
||||
float mag = band_mag(fftout, i, magscalar);
|
||||
if ( mag > magmax )
|
||||
magmax = mag;
|
||||
char *magchars = " .-=^";
|
||||
char c = magchars[0];
|
||||
if ( mag > 0.10 ) c = magchars[1];
|
||||
if ( mag > 0.25 ) c = magchars[2];
|
||||
if ( mag > 0.50 ) c = magchars[3];
|
||||
if ( mag > 1.00 ) c = magchars[4];
|
||||
printf("%c", c);
|
||||
|
||||
// if ( i > 70 )
|
||||
// break;
|
||||
}
|
||||
printf("|");
|
||||
// printf("in %+4.2f %+4.2f", inmin, inmax);
|
||||
printf(">mag %.2f", magmax);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pa_simple_free(s);
|
||||
|
||||
fftwf_free(fftin);
|
||||
fftwf_free(fftout);
|
||||
fftwf_destroy_plan(fftplan);
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Reference in New Issue