diff --git a/debian/control b/debian/control
index 5415402c0..5d515509f 100644
--- a/debian/control
+++ b/debian/control
@@ -10,7 +10,7 @@ Homepage: https://github.com/f4exb/sdrangel
Package: sdrangel
Architecture: any
-Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, pulseaudio, libxml2, ffmpeg, libavcodec-dev, libavformat-dev, libopus-dev, libcodec2-dev ${shlibs:Depends}, ${misc:Depends}
+Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, pulseaudio, libxml2, ffmpeg, libavcodec-dev, libavformat-dev, libopus-dev, libcodec2-dev, ${shlibs:Depends}, ${misc:Depends}
Description: SDR/Analyzer/Generator front-end for various hardware
SDR/Analyzer/Generator front-end for Airspy, BladeRF, HackRF, RTL-SDR, FunCube, LimeSDR, PlutoSDR.
Also File source and sink for I/Q samples, network I/Q sources with remote instance.
diff --git a/libfreedv/CMakeLists.txt b/libfreedv/CMakeLists.txt
index 42029a406..75ea15c25 100644
--- a/libfreedv/CMakeLists.txt
+++ b/libfreedv/CMakeLists.txt
@@ -8,6 +8,7 @@ set(freedv_SOURCES
freedv_data_channel.cpp
freedv_filter.cpp
freedv_vhf_framing.cpp
+ fmfsk.cpp
fsk.cpp
gp_interleaver.cpp
HRA_112_112.cpp
diff --git a/libfreedv/fmfsk.cpp b/libfreedv/fmfsk.cpp
new file mode 100644
index 000000000..4056dc6c9
--- /dev/null
+++ b/libfreedv/fmfsk.cpp
@@ -0,0 +1,373 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: fmfsk.c
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 6 February 2016
+
+ C Implementation of a FM+ME+FSK modem for FreeDV mode B and other applications
+ (better APRS, anyone?)
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2016 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. This program is
+ distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, see .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#include "fmfsk.h"
+#include "modem_probe.h"
+#include "comp_prim.h"
+
+#define STD_PROC_BITS 96
+
+namespace FreeDV
+{
+
+/*
+ * Create a new fmfsk modem instance.
+ *
+ * int Fs - sample rate
+ * int Rb - non-manchester bitrate
+ * returns - new struct FMFSK on sucess, NULL on failure
+ */
+struct FMFSK * fmfsk_create(int Fs,int Rb){
+ assert( Fs % (Rb*2) == 0 ); /* Sample freq must be divisible by symbol rate */
+
+ int nbits = STD_PROC_BITS;
+
+ /* Allocate the struct */
+ struct FMFSK *fmfsk = (struct FMFSK *) malloc(sizeof(struct FMFSK));
+ if(fmfsk==NULL) return NULL;
+
+ /* Set up static parameters */
+ fmfsk->Rb = Rb;
+ fmfsk->Rs = Rb*2;
+ fmfsk->Fs = Fs;
+ fmfsk->Ts = Fs/fmfsk->Rs;
+ fmfsk->N = nbits*2*fmfsk->Ts;
+ fmfsk->nmem = fmfsk->N+(fmfsk->Ts*4);
+ fmfsk->nsym = nbits*2;
+ fmfsk->nbit = nbits;
+
+ /* Set up demod state */
+ fmfsk->lodd = 0;
+ fmfsk->nin = fmfsk->N;
+ fmfsk->snr_mean = 0;
+
+ float *oldsamps = (float*) malloc(sizeof(float)*fmfsk->nmem);
+ if(oldsamps == NULL){
+ free(fmfsk);
+ return NULL;
+ }
+
+ fmfsk->oldsamps = oldsamps;
+
+ fmfsk->stats = (struct MODEM_STATS*)malloc(sizeof(struct MODEM_STATS));
+ if (fmfsk->stats == NULL) {
+ free(oldsamps);
+ free(fmfsk);
+ return NULL;
+ }
+
+ return fmfsk;
+}
+
+/*
+ * Destroys an fmfsk modem and deallocates memory
+ */
+void fmfsk_destroy(struct FMFSK *fmfsk){
+ free(fmfsk->oldsamps);
+ free(fmfsk);
+}
+
+/*
+ * Returns the number of samples that must be fed to fmfsk_demod the next
+ * cycle
+ */
+uint32_t fmfsk_nin(struct FMFSK *fmfsk){
+ return (uint32_t)fmfsk->nin;
+}
+
+void fmfsk_get_demod_stats(struct FMFSK *fmfsk,struct MODEM_STATS *stats){
+ /* copy from internal stats, note we can't overwrite stats completely
+ as it has other states rqd by caller, also we want a consistent
+ interface across modem types for the freedv_api.
+ */
+
+ stats->clock_offset = fmfsk->stats->clock_offset;
+ stats->snr_est = fmfsk->stats->snr_est; // TODO: make this SNR not Eb/No
+ stats->rx_timing = fmfsk->stats->rx_timing;
+ stats->foff = fmfsk->stats->foff;
+
+ stats->neyesamp = fmfsk->stats->neyesamp;
+ stats->neyetr = fmfsk->stats->neyetr;
+ memcpy(stats->rx_eye, fmfsk->stats->rx_eye, sizeof(stats->rx_eye));
+
+ /* these fields not used for FSK so set to something sensible */
+
+ stats->sync = 0;
+ stats->nr = fmfsk->stats->nr;
+ stats->Nc = fmfsk->stats->Nc;
+}
+
+/*
+ * Modulates nbit bits into N samples to be sent through an FM radio
+ *
+ * struct FSK *fsk - FSK config/state struct, set up by fsk_create
+ * float mod_out[] - Buffer for N samples of modulated FMFSK
+ * uint8_t tx_bits[] - Buffer containing Nbits unpacked bits
+ */
+
+void fmfsk_mod(struct FMFSK *fmfsk, float fmfsk_out[],uint8_t bits_in[]){
+ int i,j;
+ int nbit = fmfsk->nbit;
+ int Ts = fmfsk->Ts;
+
+ for(i=0; iTs;
+ int Fs = fmfsk->Fs;
+ int Rs = fmfsk->Rs;
+ int nin = fmfsk->nin;
+ int N = fmfsk->N;
+ int nsym = fmfsk->nsym;
+ int nbit = fmfsk->nbit;
+ int nmem = fmfsk->nmem;
+ float *oldsamps = fmfsk->oldsamps;
+ int nold = nmem-nin;
+ COMP phi_ft,dphi_ft; /* Phase and delta-phase for fine timing estimator */
+ float t;
+ COMP x; /* Magic fine timing angle */
+ float norm_rx_timing,old_norm_rx_timing,d_norm_rx_timing,appm;
+ int rx_timing,sample_offset;
+ int next_nin;
+ float apeven,apodd; /* Approx. prob of even or odd stream being correct */
+ float currv,mdiff,lastv;
+ int neyesamp;
+ int neyeoffset;
+ float eye_max;
+ uint8_t mbit;
+ float var_signal = 0, var_noise = 0, lastFabsV;
+
+ /* Shift in nin samples */
+ memmove(&oldsamps[0] , &oldsamps[nmem-nold], sizeof(float)*nold);
+ memcpy (&oldsamps[nold], &fmfsk_in[0] , sizeof(float)*nin );
+
+ /* Allocate memory for filtering */
+ float *rx_filt = (float*) malloc(sizeof(float)*(nsym+1)*Ts);
+
+ /* Integrate over Ts input symbols at every offset */
+ for(i=0; i<(nsym+1)*Ts; i++){
+ t=0;
+ /* Integrate over some samples */
+ for(j=i;jnorm_rx_timing;
+ fmfsk->norm_rx_timing = norm_rx_timing;
+
+ /* Estimate sample clock offset */
+ d_norm_rx_timing = norm_rx_timing - old_norm_rx_timing;
+
+ /* Filter out big jumps in due to nin change */
+ if(fabsf(d_norm_rx_timing) < .2){
+ appm = 1e6*d_norm_rx_timing/(float)nsym;
+ fmfsk->ppm = .9*fmfsk->ppm + .1*appm;
+ }
+
+ /* Figure out how far offset the sample points are */
+ sample_offset = (Ts/2)+Ts+rx_timing-1;
+
+ /* Request fewer or greater samples next time, if fine timing is far
+ * enough off. This also makes it possible to tolerate clock offsets */
+ next_nin = N;
+ if(norm_rx_timing > -.2)
+ next_nin += Ts/2;
+ if(norm_rx_timing < -.65)
+ next_nin -= Ts/2;
+ fmfsk->nin = next_nin;
+
+ /* Make first diff of this round the last sample of the last round,
+ * for the odd stream */
+ lastv = fmfsk->lodd;
+ lastFabsV = fabs(lastv);
+ apeven = 0;
+ apodd = 0;
+ for(i=0; i0 ? 1 : 0;
+ lastv = currv;
+
+ // Calculate the signal variance. Note that the mean is zero
+ var_signal += currv * currv;
+
+ /* Calculate the variance of the noise between samples (symbols). A quick variance estimate
+ * without calculating mean can be done by differentiating (remove mean) and then
+ * dividing by 2. Fabs the samples as we are looking at how close the samples are to each
+ * other as if they were all the same polarity/symbol. */
+ currv = fabs(currv);
+ var_noise += (currv - lastFabsV) * (currv - lastFabsV);
+ lastFabsV = currv;
+
+ mdiff = mdiff>0 ? mdiff : 0-mdiff;
+
+ /* Put bit in it's stream */
+ if((i%2)==1){
+ apeven += mdiff;
+ /* Even stream goes in LSB */
+ rx_bits[i>>1] |= mbit ? 0x1 : 0x0;
+ }else{
+ apodd += mdiff;
+ /* Odd in second-to-LSB */
+ rx_bits[i>>1] = mbit ? 0x2 : 0x0;
+ }
+ }
+
+ /* Div by 2 to correct variance when doing via differentiation.*/
+ var_noise *= 0.5;
+
+ if(apeven>apodd){
+ /* Zero out odd bits from output bitstream */
+ for(i=0;i>1;
+ }
+
+ /* Save last sample of int stream for next demod round */
+ fmfsk->lodd = lastv;
+
+ /* Save demod statistics */
+ fmfsk->stats->Nc = 0;
+ fmfsk->stats->nr = 0;
+
+ /* Clock offset and RX timing are all we know here */
+ fmfsk->stats->clock_offset = fmfsk->ppm;
+ fmfsk->stats->rx_timing = (float)rx_timing;
+
+ /* Zero out all of the other things */
+ fmfsk->stats->foff = 0;
+
+ /* Use moving average to smooth SNR display */
+ if(fmfsk->snr_mean < 0.1)
+ fmfsk->snr_mean = (10.0 * log10f(var_signal / var_noise));
+ else
+ fmfsk->snr_mean = 0.9 * fmfsk->snr_mean + 0.1 * (10.0 * log10f(var_signal / var_noise));
+ fmfsk->stats->snr_est = fmfsk->snr_mean;
+
+ /* Collect an eye diagram */
+ /* Take a sample for the eye diagrams */
+ neyesamp = fmfsk->stats->neyesamp = Ts*4;
+ neyeoffset = sample_offset+(Ts*2*28);
+
+ fmfsk->stats->neyetr = 8;
+ for(k=0; kstats->neyetr; k++)
+ for(j=0; jstats->rx_eye[k][j] = rx_filt[k*neyesamp+neyeoffset+j];
+ //fmfsk->stats->rx_eye[k][j] = fmfsk_in[k*neyesamp+neyeoffset+j];
+ eye_max = 0;
+
+ /* Normalize eye to +/- 1 */
+ for(i=0; istats->neyetr; i++)
+ for(j=0; jstats->rx_eye[i][j])>eye_max)
+ eye_max = fabsf(fmfsk->stats->rx_eye[i][j]);
+
+ for(i=0; istats->neyetr; i++)
+ for(j=0; jstats->rx_eye[i][j] = (fmfsk->stats->rx_eye[i][j]/(2*eye_max))+.5;
+
+ modem_probe_samp_f("t_norm_rx_timing",&norm_rx_timing,1);
+ modem_probe_samp_f("t_rx_filt",rx_filt,(nsym+1)*Ts);
+ free(rx_filt);
+}
+
+} // FreeDV
+