aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt11
-rw-r--r--src/fl2k_fm.c485
2 files changed, 495 insertions, 1 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index be8a429..413348b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -76,7 +76,8 @@ endif()
add_executable(fl2k_file fl2k_file.c)
add_executable(fl2k_tcp fl2k_tcp.c)
add_executable(fl2k_test fl2k_test.c)
-set(INSTALL_TARGETS libosmo-fl2k_shared libosmo-fl2k_static fl2k_file fl2k_tcp fl2k_test)
+add_executable(fl2k_fm fl2k_fm.c)
+set(INSTALL_TARGETS libosmo-fl2k_shared libosmo-fl2k_static fl2k_file fl2k_tcp fl2k_test fl2k_fm)
target_link_libraries(fl2k_file libosmo-fl2k_shared
${LIBUSB_LIBRARIES}
@@ -93,18 +94,26 @@ target_link_libraries(fl2k_test libosmo-fl2k_shared
${CMAKE_THREAD_LIBS_INIT}
)
+target_link_libraries(fl2k_fm libosmo-fl2k_shared
+ ${LIBUSB_LIBRARIES}
+ ${CMAKE_THREAD_LIBS_INIT}
+)
+
if(UNIX)
target_link_libraries(fl2k_test m)
+target_link_libraries(fl2k_fm m)
endif()
if(WIN32)
target_link_libraries(fl2k_file libgetopt_static)
target_link_libraries(fl2k_tcp ws2_32 libgetopt_static)
target_link_libraries(fl2k_test libgetopt_static)
+target_link_libraries(fl2k_fm libgetopt_static)
set_property(TARGET fl2k_file APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
set_property(TARGET fl2k_tcp APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
set_property(TARGET fl2k_test APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
+set_property(TARGET fl2k_fm APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
endif()
########################################################################
# Install built library files & utilities
diff --git a/src/fl2k_fm.c b/src/fl2k_fm.c
new file mode 100644
index 0000000..6d619dc
--- /dev/null
+++ b/src/fl2k_fm.c
@@ -0,0 +1,485 @@
+/*
+ * osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into
+ * low cost DACs
+ *
+ * Copyright (C) 2016-2018 by Steve Markgraf <steve@steve-m.de>
+ *
+ * based on FM modulator code from VGASIG:
+ * Copyright (C) 2009 by Bartek Kania <mbk@gnarf.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#include <fcntl.h>
+#else
+#include <windows.h>
+#include <io.h>
+#include <fcntl.h>
+#include "getopt/getopt.h"
+#endif
+
+#include <math.h>
+#include <pthread.h>
+
+#include "osmo-fl2k.h"
+
+#define BUFFER_SAMPLES_SHIFT 16
+#define BUFFER_SAMPLES (1 << BUFFER_SAMPLES_SHIFT)
+#define BUFFER_SAMPLES_MASK ((1 << BUFFER_SAMPLES_SHIFT)-1)
+
+#define AUDIO_BUF_SIZE 1024
+
+fl2k_dev_t *dev = NULL;
+int do_exit = 0;
+
+pthread_t fm_thread;
+pthread_mutex_t cb_mutex;
+pthread_mutex_t fm_mutex;
+pthread_cond_t cb_cond;
+pthread_cond_t fm_cond;
+
+FILE *file;
+int8_t *txbuf = NULL;
+int8_t *fmbuf = NULL;
+int8_t *buf1 = NULL;
+int8_t *buf2 = NULL;
+
+uint32_t samp_rate = 100000000;
+
+/* default signal parameters */
+int delta_freq = 75000;
+int carrier_freq = 97000000;
+int carrier_per_signal;
+int input_freq = 44100;
+
+double *freqbuf;
+double *slopebuf;
+int writepos, readpos;
+
+void usage(void)
+{
+ fprintf(stderr,
+ "fl2k_fm, an FM modulator for FL2K VGA dongles\n\n"
+ "Usage:"
+ "\t[-d device index (default: 0)]\n"
+ "\t[-c carrier frequency (default: 9.7 MHz)]\n"
+ "\t[-f FM deviation (default: 75000 Hz, WBFM)]\n"
+ "\t[-i input audio sample rate (default: 44100 Hz)]\n"
+ "\t[-s samplerate in Hz (default: 100 MS/s)]\n"
+ "\tfilename (use '-' to read from stdin)\n\n"
+ );
+ exit(1);
+}
+
+#ifdef _WIN32
+BOOL WINAPI
+sighandler(int signum)
+{
+ if (CTRL_C_EVENT == signum) {
+ fprintf(stderr, "Signal caught, exiting!\n");
+ fl2k_stop_tx(dev);
+ do_exit = 1;
+ pthread_cond_signal(&fm_cond);
+ return TRUE;
+ }
+ return FALSE;
+}
+#else
+static void sighandler(int signum)
+{
+ fprintf(stderr, "Signal caught, exiting!\n");
+ fl2k_stop_tx(dev);
+ do_exit = 1;
+ pthread_cond_signal(&fm_cond);
+}
+#endif
+
+/* DDS Functions */
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846 /* pi */
+# define M_PI_2 1.57079632679489661923 /* pi/2 */
+# define M_PI_4 0.78539816339744830962 /* pi/4 */
+# define M_1_PI 0.31830988618379067154 /* 1/pi */
+# define M_2_PI 0.63661977236758134308 /* 2/pi */
+#endif
+#define DDS_2PI (M_PI * 2) /* 2 * Pi */
+#define DDS_3PI2 (M_PI_2 * 3) /* 3/2 * pi */
+
+#define SIN_TABLE_ORDER 8
+#define SIN_TABLE_SHIFT (32 - SIN_TABLE_ORDER)
+#define SIN_TABLE_LEN (1 << SIN_TABLE_ORDER)
+#define ANG_INCR (0xffffffff / DDS_2PI)
+
+int8_t sine_table[SIN_TABLE_LEN];
+int sine_table_init = 0;
+
+typedef struct {
+ double sample_freq;
+ double freq;
+ double fslope;
+ unsigned long int phase;
+ unsigned long int phase_step;
+ unsigned long int phase_slope;
+} dds_t;
+
+inline void dds_setphase(dds_t *dds, double phase)
+{
+ dds->phase = phase * ANG_INCR;
+}
+
+inline double dds_getphase(dds_t *dds)
+{
+ return dds->phase / ANG_INCR;
+}
+
+inline void dds_set_freq(dds_t *dds, double freq, double fslope)
+{
+ dds->fslope = fslope;
+ dds->phase_step = (freq / dds->sample_freq) * 2 * M_PI * ANG_INCR;
+
+ /* The slope parameter is used with the FM modulator to create
+ * a simple but very fast and effective interpolation filter.
+ * See the fm modulator for details */
+ dds->freq = freq;
+ dds->phase_slope = (fslope / dds->sample_freq) * 2 * M_PI * ANG_INCR;
+}
+
+dds_t dds_init(double sample_freq, double freq, double phase)
+{
+ dds_t dds;
+ int i;
+
+ dds.sample_freq = sample_freq;
+ dds.phase = phase * ANG_INCR;
+ dds_set_freq(&dds, freq, 0);
+
+ /* Initialize sine table, prescaled for 8 bit signed integer */
+ if (!sine_table_init) {
+ double incr = 1.0 / (double)SIN_TABLE_LEN;
+ for (i = 0; i < SIN_TABLE_LEN; i++)
+ sine_table[i] = sin(incr * i * DDS_2PI) * 127;
+
+ sine_table_init = 1;
+ }
+
+ return dds;
+}
+
+inline int8_t dds_real(dds_t *dds)
+{
+ int tmp;
+
+ tmp = dds->phase >> SIN_TABLE_SHIFT;
+ dds->phase += dds->phase_step;
+ dds->phase &= 0xffffffff;
+
+ dds->phase_step += dds->phase_slope;
+
+ return sine_table[tmp];
+}
+
+inline void dds_real_buf(dds_t *dds, int8_t *buf, int count)
+{
+ int i;
+ for (i = 0; i < count; i++)
+ buf[i] = dds_real(dds);
+}
+
+/* Signal generation and some helpers */
+
+/* Generate the radio signal using the pre-calculated frequency information
+ * in the freq buffer */
+static void *fm_worker(void *arg)
+{
+ register double freq;
+ register double tmp;
+ dds_t carrier;
+ int8_t *tmp_ptr;
+ uint32_t len = 0;
+ uint32_t readlen, remaining;
+ int buf_prefilled = 0;
+
+ /* Prepare the oscillators */
+ carrier = dds_init(samp_rate, carrier_freq, 0);
+
+ while (!do_exit) {
+ dds_set_freq(&carrier, freqbuf[readpos], slopebuf[readpos]);
+ readpos++;
+ readpos &= BUFFER_SAMPLES_MASK;
+
+ /* check if we reach the end of the buffer */
+ if ((len + carrier_per_signal) > FL2K_BUF_LEN) {
+ readlen = FL2K_BUF_LEN - len;
+ remaining = carrier_per_signal - readlen;
+ dds_real_buf(&carrier, &fmbuf[len], readlen);
+
+ if (buf_prefilled) {
+ /* swap buffers */
+ tmp_ptr = fmbuf;
+ fmbuf = txbuf;
+ txbuf = tmp_ptr;
+ pthread_cond_wait(&cb_cond, &cb_mutex);
+ }
+
+ dds_real_buf(&carrier, fmbuf, remaining);
+ len = remaining;
+
+ buf_prefilled = 1;
+ } else {
+ dds_real_buf(&carrier, &fmbuf[len], carrier_per_signal);
+ len += carrier_per_signal;
+ }
+
+ pthread_cond_signal(&fm_cond);
+ }
+
+ pthread_exit(NULL);
+}
+
+inline int writelen(int maxlen)
+{
+ int rp = readpos;
+ int len;
+ int r;
+
+ if (rp < writepos)
+ rp += BUFFER_SAMPLES;
+
+ len = rp - writepos;
+
+ r = len > maxlen ? maxlen : len;
+
+ return r;
+}
+
+inline double modulate_sample(int lastwritepos, double lastfreq, double sample)
+{
+ double freq, slope;
+
+ /* Calculate modulator frequency at this point to lessen
+ * the calculations needed in the signal generator */
+ freq = sample * delta_freq;
+ freq += carrier_freq;
+
+ /* What we do here is calculate a linear "slope" from
+ the previous sample to this one. This is then used by
+ the modulator to gently increase/decrease the frequency
+ with each sample without the need to recalculate
+ the dds parameters. In fact this gives us a very
+ efficient and pretty good interpolation filter. */
+ slope = freq - lastfreq;
+ slope /= carrier_per_signal;
+ slopebuf[lastwritepos] = slope;
+ freqbuf[writepos] = freq;
+
+ return freq;
+}
+
+void modulator(void)
+{
+ unsigned int i;
+ size_t len;
+ double freq;
+ double lastfreq = carrier_freq;
+ double slope;
+ int16_t audio_buf[AUDIO_BUF_SIZE];
+ uint32_t lastwritepos = writepos;
+
+ while (!do_exit) {
+ len = writelen(AUDIO_BUF_SIZE);
+ if (len > 1) {
+ len = fread(audio_buf, 2, len, file);
+
+ if (len == 0)
+ do_exit = 1;
+
+ for (i = 0; i < len; i++) {
+ /* Modulate and buffer the sample */
+ lastfreq = modulate_sample(lastwritepos, lastfreq,
+ audio_buf[i]/32767.0);
+ lastwritepos = writepos++;
+ writepos %= BUFFER_SAMPLES;
+ }
+ } else {
+ pthread_cond_wait(&fm_cond, &fm_mutex);
+ }
+ }
+}
+
+void fl2k_callback(fl2k_data_info_t *data_info)
+{
+ if (data_info->device_error) {
+ do_exit = 1;
+ pthread_cond_signal(&fm_cond);
+ }
+
+ pthread_cond_signal(&cb_cond);
+
+ data_info->sampletype_signed = 1;
+ data_info->r_buf = (char *)txbuf;
+}
+
+int main(int argc, char **argv)
+{
+ int r, opt;
+ uint32_t buf_num = 0;
+ int dev_index = 0;
+ pthread_attr_t attr;
+ char *filename = NULL;
+
+#ifndef _WIN32
+ struct sigaction sigact, sigign;
+#endif
+
+ while ((opt = getopt(argc, argv, "d:c:f:i:s:")) != -1) {
+ switch (opt) {
+ case 'd':
+ dev_index = (uint32_t)atoi(optarg);
+ break;
+ case 'c':
+ carrier_freq = (uint32_t)atof(optarg);
+ break;
+ case 'f':
+ delta_freq = (uint32_t)atof(optarg);
+ break;
+ case 'i':
+ input_freq = (uint32_t)atof(optarg);
+ break;
+ case 's':
+ samp_rate = (uint32_t)atof(optarg);
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (argc <= optind) {
+ usage();
+ } else {
+ filename = argv[optind];
+ }
+
+ if (dev_index < 0) {
+ exit(1);
+ }
+
+ if(strcmp(filename, "-") == 0) { /* Read samples from stdin */
+ file = stdin;
+#ifdef _WIN32
+ _setmode(_fileno(stdin), _O_BINARY);
+#endif
+ } else {
+ file = fopen(filename, "rb");
+ if (!file) {
+ fprintf(stderr, "Failed to open %s\n", filename);
+ return -ENOENT;
+ }
+ }
+
+ /* allocate buffer */
+ buf1 = malloc(FL2K_BUF_LEN);
+ buf2 = malloc(FL2K_BUF_LEN);
+ if (!buf1 || !buf2) {
+ fprintf(stderr, "malloc error!\n");
+ exit(1);
+ }
+
+ fmbuf = buf1;
+ txbuf = buf2;
+
+ /* Decoded audio */
+ freqbuf = malloc(BUFFER_SAMPLES * sizeof(double));
+ slopebuf = malloc(BUFFER_SAMPLES * sizeof(double));
+ readpos = 0;
+ writepos = 1;
+
+ fprintf(stderr, "Samplerate:\t%3.2f MHz\n", (double)samp_rate/1000000);
+ fprintf(stderr, "Carrier:\t%3.2f MHz\n", (double)carrier_freq/1000000);
+ fprintf(stderr, "Frequencies:\t%3.2f MHz, %3.2f MHz\n",
+ (double)((samp_rate - carrier_freq) / 1000000.0),
+ (double)((samp_rate + carrier_freq) / 1000000.0));
+
+ pthread_mutex_init(&cb_mutex, NULL);
+ pthread_mutex_init(&fm_mutex, NULL);
+ pthread_cond_init(&cb_cond, NULL);
+ pthread_cond_init(&fm_cond, NULL);
+ pthread_attr_init(&attr);
+
+ fl2k_open(&dev, (uint32_t)dev_index);
+ if (NULL == dev) {
+ fprintf(stderr, "Failed to open fl2k device #%d.\n", dev_index);
+ goto out;
+ }
+
+ r = pthread_create(&fm_thread, &attr, fm_worker, NULL);
+ if (r < 0) {
+ fprintf(stderr, "Error spawning FM worker thread!\n");
+ goto out;
+ }
+
+ pthread_attr_destroy(&attr);
+ r = fl2k_start_tx(dev, fl2k_callback, NULL, 0);
+
+ /* Set the sample rate */
+ r = fl2k_set_sample_rate(dev, samp_rate);
+ if (r < 0)
+ fprintf(stderr, "WARNING: Failed to set sample rate. %d\n", r);
+
+ /* read back actual frequency */
+ samp_rate = fl2k_get_sample_rate(dev);
+
+ /* Calculate needed constants */
+ carrier_per_signal = samp_rate / input_freq;
+
+ carrier_freq = samp_rate - carrier_freq;
+
+
+#ifndef _WIN32
+ sigact.sa_handler = sighandler;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigign.sa_handler = SIG_IGN;
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGPIPE, &sigign, NULL);
+#else
+ SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
+#endif
+
+ modulator();
+
+out:
+ fl2k_close(dev);
+
+ if (file != stdin)
+ fclose(file);
+
+ free(freqbuf);
+ free(slopebuf);
+ free(buf1);
+ free(buf2);
+
+ return 0;
+}