diff options
author | Steve Markgraf <steve@steve-m.de> | 2018-04-17 21:44:47 +0200 |
---|---|---|
committer | Steve Markgraf <steve@steve-m.de> | 2018-04-17 21:44:47 +0200 |
commit | 12ec69362e758b5f4f7f563b07b70f7126204122 (patch) | |
tree | 13e9ab68b40ecbd872306dde7821e3972cff3153 | |
parent | 05cb15b0f3a14f1fbfc73ec1080e68e16615579c (diff) | |
download | osmo-fl2k-12ec69362e758b5f4f7f563b07b70f7126204122.tar.gz osmo-fl2k-12ec69362e758b5f4f7f563b07b70f7126204122.tar.bz2 osmo-fl2k-12ec69362e758b5f4f7f563b07b70f7126204122.zip |
add fl2k_fm
Signed-off-by: Steve Markgraf <steve@steve-m.de>
-rw-r--r-- | src/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/fl2k_fm.c | 485 |
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; +} |