diff options
Diffstat (limited to 'src/fl2k_fm.c')
-rw-r--r-- | src/fl2k_fm.c | 621 |
1 files changed, 0 insertions, 621 deletions
diff --git a/src/fl2k_fm.c b/src/fl2k_fm.c deleted file mode 100644 index b22efcd..0000000 --- a/src/fl2k_fm.c +++ /dev/null @@ -1,621 +0,0 @@ -/* - * 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> - * - * SPDX-License-Identifier: GPL-2.0+ - * - * 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> -#include <getopt.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" -#include "rds_mod.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 */ -#define PILOT_FREQ 19000 /* In Hz */ -#define STEREO_CARRIER 38000 /* In Hz */ - -int deviation = 75000; -int carrier_freq = 97000000; -int carrier_per_signal; -int input_freq = 44100; -int stereo_flag = 0; -int rds_flag = 0; - -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 for mono FM)]\n" - "\t[-s samplerate in Hz (default: 100 MS/s)]\n" - "\t[--rds (enables RDS, forces audio sample rate to 228 kHz)]\n" - "\t[--stereo (enables stereo, requires audio sample rate >= 114 kHz)]\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; - -static inline void dds_setphase(dds_t *dds, double phase) -{ - dds->phase = phase * ANG_INCR; -} - -static inline double dds_getphase(dds_t *dds) -{ - return dds->phase / ANG_INCR; -} - -static 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; -} - -static 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]; -} - -static 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); -} - -static 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; -} - -static 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 * deviation; - 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 fm_modulator_mono(int use_rds) -{ - unsigned int i; - size_t len; - double freq; - double lastfreq = carrier_freq; - int16_t audio_buf[AUDIO_BUF_SIZE]; - uint32_t lastwritepos = writepos; - double sample; - double rds_samples[AUDIO_BUF_SIZE]; - - while (!do_exit) { - len = writelen(AUDIO_BUF_SIZE); - if (len > 1) { - len = fread(audio_buf, 2, len, file); - - if (len == 0) - do_exit = 1; - - if (use_rds) - get_rds_samples(rds_samples, len); - - for (i = 0; i < len; i++) { - sample = audio_buf[i] / 32767.0; - - if (use_rds) { - sample *= 4; - sample += rds_samples[i]; - sample /= 5; - } - - /* Modulate and buffer the sample */ - lastfreq = modulate_sample(lastwritepos, lastfreq, sample); - lastwritepos = writepos++; - writepos %= BUFFER_SAMPLES; - } - } else { - pthread_cond_wait(&fm_cond, &fm_mutex); - } - } -} - -void fm_modulator_stereo(int use_rds) -{ - unsigned int i; - size_t len, sample_cnt; - double freq; - double lastfreq = carrier_freq; - int16_t audio_buf[AUDIO_BUF_SIZE]; - uint32_t lastwritepos = writepos; - - dds_t pilot, stereo; - double L, R, LpR, LmR, sample; - double rds_samples[AUDIO_BUF_SIZE]; - - /* Prepare stereo carriers */ - pilot = dds_init(input_freq, PILOT_FREQ, 0); - stereo = dds_init(input_freq, STEREO_CARRIER, 0); - - while (!do_exit) { - len = writelen(AUDIO_BUF_SIZE); - if (len > 1 && !(len % 2)) { - len = fread(audio_buf, 2, len, file); - - if (len == 0) - do_exit = 1; - - /* stereo => two audio samples per baseband sample */ - sample_cnt = len/2; - - if (use_rds) - get_rds_samples(rds_samples, sample_cnt); - - for (i = 0; i < sample_cnt; i++) { - /* Get samples for both channels, and calculate the - * mono (L+R) and the difference signal used to recreate - * the stereo data (L-R). */ - L = audio_buf[i*2] / 32767.0; - R = audio_buf[i*2+1] / 32767.0; - LpR = (L + R) / 2; - LmR = (L - R) / 2; - - /* Create a composite sample consisting of the mono - * signal at baseband, a 19kHz pilot and a the difference - * signal DSB-SC modulated on a 38kHz carrier */ - sample = 4.05 * LpR; /* Mono signal */ - sample += 0.9 * (dds_real(&pilot)/127.0); /* Pilot */ - sample += 4.05 * LmR * (dds_real(&stereo)/127.0); /* DSB-SC stereo */ - - if (use_rds) { - /* add RDS signal */ - sample += rds_samples[i]; - - /* Normalize so we get the signal within [-1, 1] */ - sample /= 10; - } else { - sample /= 9; - } - - lastfreq = modulate_sample(lastwritepos, lastfreq, sample); - - 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) { - fprintf(stderr, "Device error, exiting.\n"); - 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; - int option_index = 0; - int input_freq_specified = 0; - -#ifndef _WIN32 - struct sigaction sigact, sigign; -#endif - - static struct option long_options[] = - { - {"stereo", no_argument, &stereo_flag, 1}, - {"rds", no_argument, &rds_flag, 1}, - {0, 0, 0, 0} - }; - - while (1) { - opt = getopt_long(argc, argv, "d:c:f:i:s:", long_options, &option_index); - - /* end of options reached */ - if (opt == -1) - break; - - switch (opt) { - case 0: - break; - case 'd': - dev_index = (uint32_t)atoi(optarg); - break; - case 'c': - carrier_freq = (uint32_t)atof(optarg); - break; - case 'f': - deviation = (uint32_t)atof(optarg); - break; - case 'i': - input_freq = (uint32_t)atof(optarg); - input_freq_specified = 1; - 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 (rds_flag && input_freq_specified) { - if (input_freq != RDS_MODULATOR_RATE) { - fprintf(stderr, "RDS modulator only works with " - "228 kHz audio sample rate!\n"); - exit(1); - } - } else if (rds_flag && !input_freq_specified) { - input_freq = RDS_MODULATOR_RATE; - } - - if (stereo_flag && input_freq < (RDS_MODULATOR_RATE/2)) { - fprintf(stderr, "Audio sample rate needs to be at least " - "114 kHz for stereo FM!\n"); - 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; - - /* Set RDS parameters */ - set_rds_pi(0x0dac); - set_rds_ps("fl2k_fm"); - set_rds_rt("VGA FM transmitter"); - -#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 - - if (stereo_flag) { - fm_modulator_stereo(rds_flag); - } else { - if (rds_flag) - fprintf(stderr, "Warning: RDS with mono (without 19 kHz pilot" - " tone) doesn't work with all receivers!\n"); - - fm_modulator_mono(rds_flag); - } - -out: - fl2k_close(dev); - - if (file != stdin) - fclose(file); - - free(freqbuf); - free(slopebuf); - free(buf1); - free(buf2); - - return 0; -} |