aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2018-01-20 16:03:47 +0100
committerAndreas Eversberg <jolly@eversberg.eu>2018-02-16 15:54:42 +0100
commit49050eff909563aebc921a67ff842dd81b4711ba (patch)
treea5704690386a374ec5e36da0246effb02df59f7e
parentc4d4e7feda4ea54e851458c8dc4046301d46802a (diff)
Add 'osmoradio', an analog radio (FM/AM)
This radio can be a receiver or a transmitter or both simultaniously.
-rw-r--r--.gitignore1
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile.am1
-rw-r--r--src/libdebug/debug.c1
-rw-r--r--src/libdebug/debug.h1
-rw-r--r--src/radio/Makefile.am44
-rw-r--r--src/radio/main.c488
-rw-r--r--src/radio/radio.c705
-rw-r--r--src/radio/radio.h85
9 files changed, 1327 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 8102018..574eb1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,6 +60,7 @@ src/jtacs/jtacs
src/r2000/radiocom2000
src/jolly/jollycom
src/tv/osmotv
+src/radio/osmoradio
sim/cnetz_sim
src/test/test_filter
src/test/test_sendevolumenregler
diff --git a/configure.ac b/configure.ac
index 62e67a3..aa34386 100644
--- a/configure.ac
+++ b/configure.ac
@@ -85,6 +85,7 @@ AC_OUTPUT(
src/r2000/Makefile
src/jolly/Makefile
src/tv/Makefile
+ src/radio/Makefile
src/test/Makefile
src/Makefile
sim/Makefile
diff --git a/src/Makefile.am b/src/Makefile.am
index 594dcf9..aa068e1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -47,5 +47,6 @@ SUBDIRS += \
r2000 \
jolly \
tv \
+ radio \
test
diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c
index e4dcfcc..fd891ec 100644
--- a/src/libdebug/debug.c
+++ b/src/libdebug/debug.c
@@ -62,6 +62,7 @@ struct debug_cat {
{ "uhd", "\033[1;35m" },
{ "soapy", "\033[1;35m" },
{ "wave", "\033[1;33m" },
+ { "radio", "\033[1;34m" },
{ NULL, NULL }
};
diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h
index 782b56c..8a76d45 100644
--- a/src/libdebug/debug.h
+++ b/src/libdebug/debug.h
@@ -25,6 +25,7 @@
#define DUHD 18
#define DSOAPY 19
#define DWAVE 20
+#define DRADIO 21
void get_win_size(int *w, int *h);
diff --git a/src/radio/Makefile.am b/src/radio/Makefile.am
new file mode 100644
index 0000000..24c65da
--- /dev/null
+++ b/src/radio/Makefile.am
@@ -0,0 +1,44 @@
+AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
+
+if HAVE_SDR
+
+bin_PROGRAMS = \
+ osmoradio
+
+osmoradio_SOURCES = \
+ radio.c \
+ main.c
+osmoradio_LDADD = \
+ $(COMMON_LA) \
+ $(top_builddir)/src/libdebug/libdebug.a \
+ $(top_builddir)/src/libwave/libwave.a \
+ $(top_builddir)/src/libsample/libsample.a \
+ $(top_builddir)/src/libsdr/libsdr.a \
+ $(top_builddir)/src/libclipper/libclipper.a \
+ $(top_builddir)/src/libfm/libfm.a \
+ $(top_builddir)/src/libam/libam.a \
+ $(top_builddir)/src/libemphasis/libemphasis.a \
+ $(top_builddir)/src/libsamplerate/libsamplerate.a \
+ $(top_builddir)/src/libjitter/libjitter.a \
+ $(top_builddir)/src/libfilter/libfilter.a \
+ $(top_builddir)/src/libdisplay/libdisplay.a \
+ $(top_builddir)/src/libfft/libfft.a \
+ $(top_builddir)/src/libtimer/libtimer.a \
+ $(UHD_LIBS) \
+ $(SOAPY_LIBS) \
+ -lm
+
+if HAVE_ALSA
+osmoradio_LDADD += \
+ $(top_builddir)/src/libsound/libsound.a \
+ $(ALSA_LIBS)
+endif
+
+if HAVE_ALSA
+AM_CPPFLAGS += -DHAVE_ALSA
+endif
+
+AM_CPPFLAGS += -DHAVE_SDR
+
+endif
+
diff --git a/src/radio/main.c b/src/radio/main.c
new file mode 100644
index 0000000..d576ffe
--- /dev/null
+++ b/src/radio/main.c
@@ -0,0 +1,488 @@
+/* main function
+ *
+ * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * 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 3 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/>.
+ */
+
+enum paging_signal;
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <signal.h>
+#include <math.h>
+#include <termios.h>
+#include <unistd.h>
+#include "../libsample/sample.h"
+#include "../libdebug/debug.h"
+#include "../libsdr/sdr_config.h"
+#include "../libsdr/sdr.h"
+#include "../libdisplay/display.h"
+#include "radio.h"
+
+#define DEFAULT_LO_OFFSET -1000000.0
+
+void *sender_head = NULL;
+int use_sdr = 0;
+int num_kanal = 1; /* only one channel used for debugging */
+
+void *get_sender_by_empfangsfrequenz() { return NULL; }
+
+static double frequency = 0.0;
+static int samplerate = 100000;
+static int latency = 30;
+static const char *tx_wave_file = NULL;
+static const char *rx_wave_file = NULL;
+static const char *tx_audiodev = NULL;
+static const char *rx_audiodev = NULL;
+static enum modulation modulation = MODULATION_NONE;
+static int rx = 0, tx = 0;
+static double bandwidth_am = 4500.0;
+static double bandwidth_fm = 15000.0;
+static double bandwidth = 0.0;
+static double deviation = 75000.0;
+static double modulation_index = 1.0;
+static double time_constant_us = 50.0;
+static int stereo = 0;
+static int rds = 0;
+static int rds2 = 0;
+
+/* global variable to quit main loop */
+int quit = 0;
+
+void sighandler(int sigset)
+{
+ if (sigset == SIGHUP)
+ return;
+ if (sigset == SIGPIPE)
+ return;
+
+// clear_console_text();
+ printf("Signal received: %d\n", sigset);
+
+ quit = 1;
+}
+
+static int get_char()
+{
+ struct timeval tv = {0, 0};
+ fd_set fds;
+ char c = 0;
+ int __attribute__((__unused__)) rc;
+
+ FD_ZERO(&fds);
+ FD_SET(0, &fds);
+ select(0+1, &fds, NULL, NULL, &tv);
+ if (FD_ISSET(0, &fds)) {
+ rc = read(0, &c, 1);
+ return c;
+ } else
+ return -1;
+}
+
+void print_help(const char *arg0)
+
+{
+ printf("Usage: %s --sdr-soapy|--sdr-uhd <sdr options> -f <frequency> -M <modulation> -R|-T [options]\n", arg0);
+ /* - - */
+ printf("\noptions:\n");
+ printf(" -f --frequency <frequency>\n");
+ printf(" Give frequency in Hertz.\n");
+ printf(" -s --samplerate <sample rate>\n");
+ printf(" Give signal processing sample rate in Hz. (default = %d)\n", samplerate);
+ printf(" This sample rate must be high enough for the signal's spectrum to fit.\n");
+ printf(" I will inform you, if this bandwidth is too low.\n");
+ printf(" -r --tx-wave-file <filename>\n");
+ printf(" Input transmitted audio from wave file\n");
+ printf(" -w --rx-wave-file <filename>\n");
+ printf(" Output received audio to wave file\n");
+ printf(" -a --audio-device hw:<card>,<device>\n");
+ printf(" Input audio from sound card's device number\n");
+ printf(" -M --modulation fm | am | usb | lsb\n");
+ printf(" fm = Frequency modulation to be used for VHF.\n");
+ printf(" am = Amplitude modulation to be used for long/medium/short wave.\n");
+ printf(" usb = Amplitude modulation with upper side band only.\n");
+ printf(" lsb = Amplitude modulation with lower side band only.\n");
+ printf(" -R --rx\n");
+ printf(" Receive radio signal.\n");
+ printf(" -T --tx\n");
+ printf(" Transmit radio signal.\n");
+ printf(" -B --bandwidth\n");
+ printf(" Give bandwidth of audio frequency. (default AM=%.0f FM=%.0f)\n", bandwidth_am, bandwidth_fm);
+ printf(" -D --deviation\n");
+ printf(" Give deviation of frequency modulated signal. (default %.0f)\n", deviation);
+ printf(" -I --modulation-index 0..1\n");
+ printf(" Give modulation index of amplitude modulated signal. (default %.0f)\n", deviation);
+ printf(" -E --emphasis <uS> | 0\n");
+ printf(" Use given time constant of pre- and de-emphasis for frequency\n");
+ printf(" modulation. Give 0 to disbale. (default = %.0f uS)\n", time_constant_us);
+ printf(" VHF broadcast 50 uS in Europe and 75 uS in the United States.\n");
+ printf(" Other radio FM should use 530 uS, to cover complete speech spectrum.\n");
+ printf(" -S --stereo\n");
+ printf(" Enables stereo carrier for frequency modulated UHF broadcast.\n");
+ printf(" It uses the 'Pilot-tone' system.\n");
+ sdr_config_print_help();
+}
+
+static struct option long_options_common[] = {
+ {"help", 0, 0, 'h'},
+ {"frequency", 1, 0, 'f'},
+ {"samplerate", 1, 0, 's'},
+ {"tx-wave-file", 1, 0, 'r'},
+ {"rx-wave-file", 1, 0, 'w'},
+ {"audio-device", 1, 0, 'a'},
+ {"modulation", 1, 0, 'M'},
+ {"rx", 0, 0, 'R'},
+ {"tx", 0, 0, 'T'},
+ {"bandwidth", 1, 0, 'B'},
+ {"deviation", 1, 0, 'D'},
+ {"modulation-index", 1, 0, 'I'},
+ {"emphasis", 1, 0, 'E'},
+ {"stereo", 0, 0, 'S'},
+ {0, 0, 0, 0}
+};
+
+static const char *optstring_common = "hf:s:r:w:a:M:RTB:D:I:E:S";
+
+struct option *long_options;
+char *optstring;
+
+static void check_duplicate_option(int num, struct option *option)
+{
+ int i;
+
+ for (i = 0; i < num; i++) {
+ if (long_options[i].val == option->val) {
+ fprintf(stderr, "Duplicate option %d. Please fix!\n", option->val);
+ abort();
+ }
+ }
+}
+
+void set_options_common(void)
+{
+ int i = 0, j;
+
+ long_options = calloc(sizeof(*long_options), 256);
+ for (j = 0; long_options_common[i].name; i++, j++) {
+ check_duplicate_option(i, &long_options_common[j]);
+ memcpy(&long_options[i], &long_options_common[j], sizeof(*long_options));
+ }
+ for (j = 0; sdr_config_long_options[j].name; i++, j++) {
+ check_duplicate_option(i, &sdr_config_long_options[j]);
+ memcpy(&long_options[i], &sdr_config_long_options[j], sizeof(*long_options));
+ }
+
+ optstring = calloc(256, 2);
+ strcpy(optstring, optstring_common);
+ strcat(optstring, sdr_config_optstring);
+}
+
+static int handle_options(int argc, char **argv)
+{
+ int skip_args = 0;
+ int rc;
+
+ set_options_common();
+
+ while (1) {
+ int option_index = 0, c;
+
+ c = getopt_long(argc, argv, optstring, long_options, &option_index);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help(argv[0]);
+ exit(0);
+ case 'f':
+ frequency = atof(optarg);
+ skip_args += 2;
+ break;
+ case 's':
+ samplerate = atof(optarg);
+ skip_args += 2;
+ break;
+ case 'r':
+ tx_wave_file = strdup(optarg);
+ skip_args += 2;
+ break;
+ case 'w':
+ rx_wave_file = strdup(optarg);
+ skip_args += 2;
+ break;
+ case 'a':
+ tx_audiodev = strdup(optarg);
+ rx_audiodev = strdup(optarg);
+ skip_args += 2;
+ break;
+ case 'M':
+ if (!strcasecmp(optarg, "fm"))
+ modulation = MODULATION_FM;
+ else
+ if (!strcasecmp(optarg, "am"))
+ modulation = MODULATION_AM_DSB;
+ else
+ if (!strcasecmp(optarg, "usb"))
+ modulation = MODULATION_AM_USB;
+ else
+ if (!strcasecmp(optarg, "lsb"))
+ modulation = MODULATION_AM_LSB;
+ else
+ {
+ fprintf(stderr, "Invalid modulation option, see help!\n");
+ exit(0);
+ }
+ skip_args += 2;
+ break;
+ case 'R':
+ rx = 1;
+ skip_args += 1;
+ break;
+ case 'T':
+ tx = 1;
+ skip_args += 1;
+ break;
+ case 'B':
+ bandwidth = atof(optarg);
+ skip_args += 2;
+ break;
+ case 'D':
+ deviation = atof(optarg);
+ skip_args += 2;
+ break;
+ case 'I':
+ modulation_index = atof(optarg);
+ if (modulation_index < 0.0 || modulation_index > 1.0) {
+ fprintf(stderr, "Invalid modulation index, see help!\n");
+ exit(0);
+ }
+ skip_args += 2;
+ break;
+ case 'E':
+ time_constant_us = atof(optarg);
+ skip_args += 2;
+ break;
+ case 'S':
+ stereo = 1;
+ skip_args += 1;
+ break;
+ default:
+ rc = sdr_config_opt_switch(c, &skip_args);
+ if (rc < 0)
+ exit(0);
+ break;
+ }
+ }
+
+ return skip_args;
+}
+
+int main(int argc, char *argv[])
+{
+ int skip_args;
+ int rc;
+ const char *arg0 = argv[0];
+ radio_t radio;
+ struct termios term, term_orig;
+ int c;
+ int latspl;
+
+ debuglevel = 0;
+
+ sdr_config_init(DEFAULT_LO_OFFSET);
+
+ skip_args = handle_options(argc, argv);
+ argc -= skip_args + 1;
+ argv += skip_args + 1;
+
+ if (frequency == 0.0) {
+ printf("No frequency given, I suggest to use 100000000 (100 MHz) and FM\n\n");
+ print_help(arg0);
+ exit(0);
+ }
+
+ rc = sdr_configure(samplerate);
+ if (rc < 0)
+ return rc;
+ if (rc == 0) {
+ fprintf(stderr, "Please select SDR, see help!\n");
+ exit(0);
+ }
+
+ if (modulation == MODULATION_NONE) {
+ fprintf(stderr, "Please select modulation, see help!\n");
+ exit(0);
+ }
+
+ if (bandwidth == 0) {
+ if (modulation == MODULATION_FM)
+ bandwidth = bandwidth_fm;
+ else
+ bandwidth = bandwidth_am;
+ }
+
+ if (stereo && modulation != MODULATION_FM) {
+ fprintf(stderr, "Stereo works with FM only, see help!\n");
+ exit(0);
+ }
+ if (!rx && !tx) {
+ fprintf(stderr, "You need to specify --rx (receiver) and/or --tx (transmitter), see help!\n");
+ exit(0);
+ }
+ if (stereo && bandwidth != 15000.0) {
+ fprintf(stderr, "Warning: Stereo works with bandwidth of 15 KHz only, using this bandwidth!\n");
+ }
+ if (stereo && time_constant_us != 75.0 && time_constant_us != 50.0) {
+ fprintf(stderr, "Stereo works with time constant of 50 uS or 75 uS only, see help!\n");
+ exit(0);
+ }
+
+ /* now we have latency and sample rate */
+ latspl = samplerate * latency / 1000;
+
+ rc = radio_init(&radio, latspl, samplerate, tx_wave_file, rx_wave_file, (tx) ? tx_audiodev : NULL, (rx) ? rx_audiodev : NULL, modulation, bandwidth, deviation, modulation_index, time_constant_us, stereo, rds, rds2);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to initialize radio with given options, exitting!\n");
+ exit(0);
+ }
+
+ void *sdr = NULL;
+ float *sendbuff = NULL;
+
+ sendbuff = calloc(latspl * 2, sizeof(*sendbuff));
+ if (!sendbuff) {
+ fprintf(stderr, "No mem!\n");
+ goto error;
+ }
+
+ double tx_frequencies[1], rx_frequencies[1];
+ tx_frequencies[0] = frequency;
+ rx_frequencies[0] = frequency;
+ sdr = sdr_open(NULL, tx_frequencies, rx_frequencies, 1, 0.0, samplerate, latspl, 0.0, 0.0);
+ if (!sdr)
+ goto error;
+ sdr_start(sdr);
+
+ /* prepare terminal */
+ tcgetattr(0, &term_orig);
+ term = term_orig;
+ term.c_lflag &= ~(ISIG|ICANON|ECHO);
+ term.c_cc[VMIN]=1;
+ term.c_cc[VTIME]=2;
+ tcsetattr(0, TCSANOW, &term);
+
+ /* catch signals */
+ signal(SIGINT, sighandler);
+ signal(SIGHUP, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+
+ printf("Starting radio...\n");
+ rc = radio_start(&radio);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to start radio's streaming, exitting!\n");
+ goto error;
+ }
+
+ int tosend, got;
+ while (!quit) {
+ usleep(1000);
+ got = sdr_read(sdr, (void *)sendbuff, latspl, 0, NULL);
+ if (rx) {
+ got = radio_rx(&radio, sendbuff, got);
+ if (got < 0)
+ break;
+ }
+ if (tx) {
+ tosend = sdr_get_tosend(sdr, latspl);
+ if (tosend > latspl / 10)
+ tosend = latspl / 10;
+ if (tosend == 0) {
+ continue;
+ }
+ /* perform radio modulation */
+ tosend = radio_tx(&radio, sendbuff, tosend);
+ if (tosend < 0)
+ break;
+ /* write to SDR */
+ sdr_write(sdr, (void *)sendbuff, NULL, tosend, NULL, NULL, 0);
+ }
+
+ /* process keyboard input */
+next_char:
+ c = get_char();
+ switch (c) {
+ case 3:
+ /* quit */
+// if (clear_console_text)
+// clear_console_text();
+ printf("CTRL+c received, quitting!\n");
+ quit = 1;
+ goto next_char;
+#if 0
+- carrier frequency
+- deviation
+- modulation index
+- stereo pilot
+ case 'm':
+ /* toggle measurements display */
+ display_iq_on(0);
+ display_spectrum_on(0);
+ display_measurements_on(-1);
+ goto next_char;
+#endif
+ case 'q':
+ /* toggle IQ display */
+ display_measurements_on(0);
+ display_spectrum_on(0);
+ display_iq_on(-1);
+ goto next_char;
+ case 's':
+ /* toggle spectrum display */
+ display_measurements_on(0);
+ display_iq_on(0);
+ display_spectrum_on(-1);
+ goto next_char;
+ case 'B':
+ calibrate_bias();
+ goto next_char;
+ }
+ }
+
+ /* reset signals */
+ signal(SIGINT, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+
+ /* reset terminal */
+ tcsetattr(0, TCSANOW, &term_orig);
+
+error:
+ free(sendbuff);
+ if (sdr)
+ sdr_close(sdr);
+ radio_exit(&radio);
+
+ return 0;
+}
+
diff --git a/src/radio/radio.c b/src/radio/radio.c
new file mode 100644
index 0000000..48c8daf
--- /dev/null
+++ b/src/radio/radio.c
@@ -0,0 +1,705 @@
+/* main function
+ *
+ * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * 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 3 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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <errno.h>
+#include <pthread.h>
+#include "../libsample/sample.h"
+#include "../libdebug/debug.h"
+#include "../libsound/sound.h"
+#include "../libclipper/clipper.h"
+#include "radio.h"
+
+#define CLIP_POINT 0.85
+#define DC_CUTOFF 30.0 // Wikipedia: UKW-Rundfunk
+#define STEREO_BW 15000.0
+#define PILOT_FREQ 19000.0
+#define PILOT_BW 5.0
+
+int radio_init(radio_t *radio, int latspl, int samplerate, const char *tx_wave_file, const char *rx_wave_file, const char *tx_audiodev, const char *rx_audiodev, enum modulation modulation, double bandwidth, double deviation, double modulation_index, double time_constant_us, int stereo, int rds, int rds2)
+{
+ int rc = -EINVAL;
+
+ clipper_init(CLIP_POINT);
+
+ memset(radio, 0, sizeof(*radio));
+ radio->latspl = latspl;
+ radio->stereo = stereo;
+ radio->rds = rds;
+ radio->rds2 = rds2;
+ radio->tx_wave_file = tx_wave_file;
+ radio->modulation = modulation;
+ radio->signal_samplerate = samplerate;
+ radio->audio_bandwidth = bandwidth;
+
+ switch (radio->modulation) {
+ case MODULATION_FM:
+ radio->fm_deviation = deviation;
+ radio->signal_bandwidth = deviation + bandwidth;
+ if (radio->stereo) {
+ radio->signal_bandwidth = deviation + 53000.0;
+ radio->audio_bandwidth = STEREO_BW;
+ }
+ if (radio->rds)
+ radio->signal_bandwidth = deviation + 60000.0;
+ if (radio->rds2)
+ radio->signal_bandwidth = deviation + 80000.0;
+ break;
+ case MODULATION_AM_DSB:
+ case MODULATION_AM_USB:
+ case MODULATION_AM_LSB:
+ /* level is 1.0, which is full amplitude */
+ radio->signal_bandwidth = bandwidth;
+ break;
+ case MODULATION_NONE:
+ PDEBUG(DRADIO, DEBUG_ERROR, "Wrong modulation, plese fix!\n");
+ goto error;
+ }
+
+ if (tx_wave_file) {
+ /* open wave file */
+ int _samplerate = 0;
+ radio->tx_audio_channels = 0;
+ rc = wave_create_playback(&radio->wave_tx_play, tx_wave_file, &_samplerate, &radio->tx_audio_channels, 1.0);
+ if (rc < 0) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to create WAVE playback instance!\n");
+ goto error;
+ }
+ if (radio->tx_audio_channels != 1 && radio->tx_audio_channels != 2)
+ {
+ PDEBUG(DRADIO, DEBUG_ERROR, "WAVE file must have one or two channels!\n");
+ goto error;
+ }
+ radio->tx_audio_samplerate = _samplerate;
+ radio->tx_audio_mode = AUDIO_MODE_WAVEFILE;
+ } else if (tx_audiodev) {
+#ifdef HAVE_ALSA
+ /* open audio device */
+ radio->tx_audio_samplerate = 48000;
+ radio->tx_audio_channels = (stereo) ? 2 : 1;
+ radio->tx_sound = sound_open(tx_audiodev, NULL, NULL, radio->tx_audio_channels, 0.0, radio->tx_audio_samplerate, radio->latspl, 1.0, 0.0);
+ if (!radio->tx_sound) {
+ rc = -EIO;
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to open sound device!\n");
+ goto error;
+ }
+ jitter_create(&radio->tx_dejitter[0], radio->tx_audio_samplerate / 5);
+ jitter_create(&radio->tx_dejitter[1], radio->tx_audio_samplerate / 5);
+ radio->tx_audio_mode = AUDIO_MODE_AUDIODEV;
+#else
+ rc = -ENOTSUP;
+ PDEBUG(DRADIO, DEBUG_ERROR, "No sound card support compiled in!\n");
+ goto error;
+#endif
+ } else {
+ int i;
+ double phase;
+ /* use built-in sample sound */
+ radio->tx_audio_samplerate = samplerate;
+ radio->tx_audio_channels = (radio->stereo) ? 2 : 1;
+ radio->testtone_length = radio->tx_audio_samplerate;
+ radio->testtone[0] = calloc(radio->testtone_length * 2, sizeof(sample_t));
+ if (!radio->testtone[0]) {
+ rc = -ENOMEM;
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to allocate test sound buffer!\n");
+ goto error;
+ }
+ radio->testtone[1] = radio->testtone[0] + radio->testtone_length;
+ /* generate tone */
+ phase = 2.0 * M_PI * 1000.0 / radio->tx_audio_samplerate;
+ if (radio->stereo) {
+ for (i = 0; i < radio->testtone_length / 2; i++) {
+ radio->testtone[0][i] = sin(i * phase);
+ radio->testtone[1][i] = 0.0;
+ }
+ for (; i < radio->testtone_length; i++) {
+ radio->testtone[0][i] = 0.0;
+ radio->testtone[1][i] = sin(i * phase);
+ }
+ } else {
+ for (i = 0; i < radio->testtone_length; i++) {
+ radio->testtone[0][i] = sin(i * phase);
+ }
+ }
+ radio->tx_audio_mode = AUDIO_MODE_TESTTONE;
+ }
+
+ if (rx_wave_file) {
+ /* open wave file */
+ radio->rx_audio_samplerate = 4800;
+ radio->rx_audio_channels = (radio->stereo) ? 2 : 1;
+ rc = wave_create_record(&radio->wave_rx_rec, rx_wave_file, radio->rx_audio_samplerate, radio->rx_audio_channels, 1.0);
+ if (rc < 0) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to create WAVE record instance!\n");
+ goto error;
+ }
+ radio->rx_audio_mode = AUDIO_MODE_WAVEFILE;
+ } else if (rx_audiodev) {
+#ifdef HAVE_ALSA
+ /* open audio device */
+ radio->rx_audio_samplerate = 48000;
+ radio->rx_audio_channels = (stereo) ? 2 : 1;
+ /* check if we use same device */
+ if (radio->tx_sound && !strcmp(tx_audiodev, rx_audiodev))
+ radio->rx_sound = radio->tx_sound;
+ else
+ radio->rx_sound = sound_open(rx_audiodev, NULL, NULL, radio->rx_audio_channels, 0.0, radio->rx_audio_samplerate, radio->latspl, 1.0, 0.0);
+ if (!radio->rx_sound) {
+ rc = -EIO;
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to open sound device!\n");
+ goto error;
+ }
+ jitter_create(&radio->rx_dejitter[0], radio->rx_audio_samplerate / 5);
+ jitter_create(&radio->rx_dejitter[1], radio->rx_audio_samplerate / 5);
+ radio->rx_audio_mode = AUDIO_MODE_AUDIODEV;
+#else
+ rc = -ENOTSUP;
+ PDEBUG(DRADIO, DEBUG_ERROR, "No sound card support compiled in!\n");
+ goto error;
+#endif
+ }
+
+ /* check if sample rate is too low */
+ if (radio->tx_audio_samplerate > radio->signal_samplerate) {
+ rc = -EINVAL;
+ PDEBUG(DRADIO, DEBUG_ERROR, "You have selected a signal processing sample rate of %.0f. Your audio sample rate is %.0f.\n", radio->signal_samplerate, radio->tx_audio_samplerate);
+ PDEBUG(DRADIO, DEBUG_ERROR, "Please select a sample rate that is higher or equal the audio sample rate!\n");
+ goto error;
+ }
+ if (radio->rx_audio_samplerate > radio->signal_samplerate) {
+ rc = -EINVAL;
+ PDEBUG(DRADIO, DEBUG_ERROR, "You have selected a signal processing sample rate of %.0f. Your audio sample rate is %.0f.\n", radio->signal_samplerate, radio->rx_audio_samplerate);
+ PDEBUG(DRADIO, DEBUG_ERROR, "Please select a sample rate that is higher or equal the audio sample rate!\n");
+ goto error;
+ }
+ if (radio->signal_samplerate < radio->signal_bandwidth * 2 / 0.75) {
+ rc = -EINVAL;
+ PDEBUG(DRADIO, DEBUG_ERROR, "You have selected a signal processing sample rate of %.0f. Your signal's bandwidth %.0f.\n", radio->signal_samplerate, radio->signal_bandwidth);
+ PDEBUG(DRADIO, DEBUG_ERROR, "Your signal processing sample rate must be at least one third greater than the signal's double bandwidth. Use at least %.0f.\n", radio->signal_bandwidth * 2.0 / 0.75);
+ goto error;
+ }
+
+ iir_highpass_init(&radio->tx_dc_removal[0], DC_CUTOFF, radio->tx_audio_samplerate, 1);
+ iir_highpass_init(&radio->tx_dc_removal[1], DC_CUTOFF, radio->tx_audio_samplerate, 1);
+
+ /* stereo pilot tone phase */
+ radio->pilot_phasestep = 2.0 * M_PI * PILOT_FREQ / radio->signal_samplerate;
+
+ /* stere decoding filters */
+ iir_lowpass_init(&radio->rx_lp_pilot_I, PILOT_BW, radio->signal_samplerate, 2);
+ iir_lowpass_init(&radio->rx_lp_pilot_Q, PILOT_BW, radio->signal_samplerate, 2);
+ iir_lowpass_init(&radio->rx_lp_sum, STEREO_BW, radio->signal_samplerate, 2);
+ iir_lowpass_init(&radio->rx_lp_diff, STEREO_BW, radio->signal_samplerate, 2);
+
+ /* init sample rate conversion, use complete bandwidth for resample filter */
+ rc = init_samplerate(&radio->tx_resampler[0], radio->tx_audio_samplerate, radio->signal_samplerate, radio->tx_audio_samplerate / 2.0);
+ if (rc < 0)
+ goto error;
+ rc = init_samplerate(&radio->tx_resampler[1], radio->tx_audio_samplerate, radio->signal_samplerate, radio->tx_audio_samplerate / 2.0);
+ if (rc < 0)
+ goto error;
+ rc = init_samplerate(&radio->rx_resampler[0], radio->rx_audio_samplerate, radio->signal_samplerate, radio->rx_audio_samplerate / 2.0);
+ if (rc < 0)
+ goto error;
+ rc = init_samplerate(&radio->rx_resampler[1], radio->rx_audio_samplerate, radio->signal_samplerate, radio->rx_audio_samplerate / 2.0);
+ if (rc < 0)
+ goto error;
+
+ /* init filters (using signal sample rate) */
+ switch (radio->modulation) {
+ case MODULATION_FM:
+ /* time constant */
+ PDEBUG(DRADIO, DEBUG_INFO, "Using emphasis cut-off at %.0f Hz.\n", timeconstant2cutoff(time_constant_us));
+ rc = init_emphasis(&radio->fm_emphasis[0], radio->signal_samplerate, timeconstant2cutoff(time_constant_us), DC_CUTOFF, radio->audio_bandwidth);
+ if (rc < 0)
+ goto error;
+ rc = init_emphasis(&radio->fm_emphasis[1], radio->signal_samplerate, timeconstant2cutoff(time_constant_us), DC_CUTOFF, radio->audio_bandwidth);
+ if (rc < 0)
+ goto error;
+ rc = fm_mod_init(&radio->fm_mod, radio->signal_samplerate, 0.0, 1.0);
+ if (rc < 0)
+ goto error;
+ rc = fm_demod_init(&radio->fm_demod, radio->signal_samplerate, 0.0, 2 * radio->signal_bandwidth);
+ if (rc < 0)
+ goto error;
+ break;
+ case MODULATION_AM_DSB:
+ iir_lowpass_init(&radio->tx_am_bw_limit, radio->audio_bandwidth, radio->signal_samplerate, 1);
+ /* modulation index 0.0 = no envelope, bias 1.0
+ * modulation index 1.0 = envelope +-0.5, bias 0.5
+ * modulation index 0.5 = envelope +-0.25, bias 0.75
+ */
+ double gain = modulation_index / 2.0;
+ double bias = 1.0 - gain;
+ rc = am_mod_init(&radio->am_mod, radio->signal_samplerate, 0.0, gain, bias);
+ if (rc < 0)
+ goto error;
+ rc = am_demod_init(&radio->am_demod, radio->signal_samplerate, 0.0, radio->signal_bandwidth, 1.0 / modulation_index);
+ if (rc < 0)
+ goto error;
+ break;
+ case MODULATION_AM_USB:
+ iir_lowpass_init(&radio->tx_am_bw_limit, radio->audio_bandwidth, radio->signal_samplerate, 1);
+ rc = am_mod_init(&radio->am_mod, radio->signal_samplerate, 0.0, 1.0, 0.0);
+ if (rc < 0)
+ goto error;
+ break;
+ case MODULATION_AM_LSB:
+ iir_lowpass_init(&radio->tx_am_bw_limit, radio->audio_bandwidth, radio->signal_samplerate, 1);
+ rc = am_mod_init(&radio->am_mod, radio->signal_samplerate, 0.0, 1.0, 0.0);
+ if (rc < 0)
+ goto error;
+ break;
+ default:
+ break;
+ }
+
+ if (radio->tx_audio_mode)
+ PDEBUG(DRADIO, DEBUG_INFO, "Bandwidth of audio source is %.0f Hz.\n", radio->tx_audio_samplerate / 2.0);
+ if (radio->rx_audio_mode)
+ PDEBUG(DRADIO, DEBUG_INFO, "Bandwidth of audio sink is %.0f Hz.\n", radio->rx_audio_samplerate / 2.0);
+ PDEBUG(DRADIO, DEBUG_INFO, "Bandwidth of audio signal is %.0f Hz.\n", radio->audio_bandwidth);
+ PDEBUG(DRADIO, DEBUG_INFO, "Bandwidth of modulated signal is %.0f Hz.\n", radio->signal_bandwidth);
+ if (radio->tx_audio_mode)
+ PDEBUG(DRADIO, DEBUG_INFO, "Sample rate of audio source is %.0f Hz.\n", radio->tx_audio_samplerate);
+ if (radio->rx_audio_mode)
+ PDEBUG(DRADIO, DEBUG_INFO, "Sample rate of audio sink is %.0f Hz.\n", radio->rx_audio_samplerate);
+ PDEBUG(DRADIO, DEBUG_INFO, "Sample rate of signal is %.0f Hz.\n", radio->signal_samplerate);
+
+ /* one or two audio channels */
+ if (radio->tx_audio_channels != 1 && radio->tx_audio_channels != 2)
+ {
+ PDEBUG(DRADIO, DEBUG_ERROR, "Wrong number of audio channels, please fix!\n");
+ goto error;
+ }
+
+ /* audio buffers: how many sample for audio (rounded down) */
+ int tx_size = (int)((double)latspl / radio->tx_resampler[0].factor);
+ int rx_size = (int)((double)latspl / radio->rx_resampler[0].factor);
+ if (tx_size > rx_size)
+ radio->audio_buffer_size = tx_size;
+ else
+ radio->audio_buffer_size = rx_size;
+ radio->audio_buffer = calloc(radio->audio_buffer_size * 2, sizeof(*radio->audio_buffer));
+ if (!radio->audio_buffer) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "No memory!!\n");
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ /* signal buffers */
+ radio->signal_buffer_size = latspl;
+ radio->signal_buffer = calloc(radio->signal_buffer_size * 3, sizeof(*radio->signal_buffer));
+ radio->signal_power_buffer = calloc(radio->signal_buffer_size, sizeof(*radio->signal_power_buffer));
+ if (!radio->signal_buffer || !radio->signal_power_buffer) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "No memory!!\n");
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ /* termporary I/Q/carrier buffers, used while demodulating */
+ radio->I_buffer = calloc(latspl, sizeof(*radio->I_buffer));
+ radio->Q_buffer = calloc(latspl, sizeof(*radio->Q_buffer));
+ radio->carrier_buffer = calloc(latspl, sizeof(*radio->carrier_buffer));
+ if (!radio->I_buffer || !radio->Q_buffer || !radio->carrier_buffer) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "No memory!!\n");
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ return 0;
+
+error:
+ radio_exit(radio);
+ return rc;
+}
+
+void radio_exit(radio_t *radio)
+{
+ if (radio->audio_buffer) {
+ free(radio->audio_buffer);
+ radio->audio_buffer = NULL;
+ }
+ if (radio->signal_buffer) {
+ free(radio->signal_buffer);
+ radio->signal_buffer = NULL;
+ }
+ if (radio->signal_power_buffer) {
+ free(radio->signal_power_buffer);
+ radio->signal_power_buffer = NULL;
+ }
+ if (radio->I_buffer) {
+ free(radio->I_buffer);
+ radio->I_buffer = NULL;
+ }
+ if (radio->Q_buffer) {
+ free(radio->Q_buffer);
+ radio->Q_buffer = NULL;
+ }
+ if (radio->carrier_buffer) {
+ free(radio->carrier_buffer);
+ radio->carrier_buffer = NULL;
+ }
+ if (radio->tx_audio_mode == AUDIO_MODE_WAVEFILE) {
+ wave_destroy_playback(&radio->wave_tx_play);
+ radio->tx_audio_mode = AUDIO_MODE_NONE;
+ }
+ if (radio->rx_audio_mode == AUDIO_MODE_WAVEFILE) {
+ wave_destroy_record(&radio->wave_rx_rec);
+ radio->rx_audio_mode = AUDIO_MODE_NONE;
+ }
+#ifdef HAVE_ALSA
+ if (radio->tx_sound) {
+ sound_close(radio->tx_sound);
+ /* if same device was used */
+ if (radio->tx_sound == radio->rx_sound)
+ radio->rx_sound = NULL;
+ radio->tx_sound = NULL;
+ radio->tx_audio_mode = AUDIO_MODE_NONE;
+ }
+ if (radio->rx_sound) {
+ sound_close(radio->rx_sound);
+ radio->rx_sound = NULL;
+ radio->rx_audio_mode = AUDIO_MODE_NONE;
+ }
+#endif
+ jitter_destroy(&radio->tx_dejitter[0]);
+ jitter_destroy(&radio->tx_dejitter[1]);
+ jitter_destroy(&radio->rx_dejitter[0]);
+ jitter_destroy(&radio->rx_dejitter[1]);
+ if (radio->tx_audio_mode == AUDIO_MODE_TESTTONE) {
+ free(radio->testtone[0]);
+ radio->tx_audio_mode = AUDIO_MODE_NONE;
+ }
+ if (radio->modulation == MODULATION_FM)
+ fm_mod_exit(&radio->fm_mod);
+ else
+ am_mod_exit(&radio->am_mod);
+}
+
+int radio_start(radio_t __attribute__((unused)) *radio)
+{
+ int rc = 0;
+
+#ifdef HAVE_ALSA
+ /* start rx sound */
+ if (radio->rx_sound)
+ rc = sound_start(radio->rx_sound);
+ /* start tx sound, if different device */
+ if (radio->tx_sound && radio->tx_sound != radio->rx_sound)
+ rc = sound_start(radio->tx_sound);
+#endif
+
+ return rc;
+}
+
+int radio_tx(radio_t *radio, float *baseband, int signal_num)
+{
+ int i;
+ int __attribute__((unused)) rc;
+ int audio_num;
+ sample_t *audio_samples[2];
+ sample_t *signal_samples[3];
+ uint8_t *signal_power;
+
+ if (signal_num > radio->latspl) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "signal_num > latspl, please fix!.\n");
+ abort();
+ }
+
+ /* audio buffers: how many sample for audio (rounded down) */
+ audio_num = (int)((double)signal_num / radio->tx_resampler[0].factor);
+ if (audio_num > radio->audio_buffer_size) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "audio_num > audio_buffer_size, please fix!.\n");
+ abort();
+ }
+ audio_samples[0] = radio->audio_buffer;
+ audio_samples[1] = radio->audio_buffer + radio->audio_buffer_size;
+
+ /* signal buffers: a bit more samples to be safe */
+ signal_num = (int)((double)audio_num * radio->tx_resampler[0].factor + 0.5) + 10;
+ if (signal_num > radio->signal_buffer_size) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "signal_num > signal_buffer_size, please fix!.\n");
+ abort();
+ }
+ signal_samples[0] = radio->signal_buffer;
+ signal_samples[1] = radio->signal_buffer + radio->signal_buffer_size;
+ signal_samples[2] = radio->signal_buffer + radio->signal_buffer_size * 2;
+ signal_power = radio->signal_power_buffer;
+
+ /* get audio to be sent */
+ switch (radio->tx_audio_mode) {
+ case AUDIO_MODE_WAVEFILE:
+ wave_read(&radio->wave_tx_play, audio_samples, audio_num);
+ if (!radio->wave_tx_play.left) {
+ int rc;
+ int _samplerate = 0;
+ wave_destroy_playback(&radio->wave_tx_play);
+ rc = wave_create_playback(&radio->wave_tx_play, radio->tx_wave_file, &_samplerate, &radio->tx_audio_channels, 1.0);
+ if (rc < 0) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to re-open wave file.\n");
+ return rc;
+ }
+ }
+ break;
+#ifdef HAVE_ALSA
+ case AUDIO_MODE_AUDIODEV:
+ rc = sound_read(radio->tx_sound, audio_samples, radio->audio_buffer_size, radio->tx_audio_channels, NULL);
+ if (rc < 0) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to read from sound device (rc = %d)!\n", audio_num);
+ if (rc == -EPIPE)
+ PDEBUG(DRADIO, DEBUG_ERROR, "Trying to recover.\n");
+ else
+ return 0;
+ }
+ jitter_save(&radio->tx_dejitter[0], audio_samples[0], rc);
+ jitter_load(&radio->tx_dejitter[0], audio_samples[0], audio_num);
+ if (radio->tx_audio_channels == 2) {
+ jitter_save(&radio->tx_dejitter[1], audio_samples[1], rc);
+ jitter_load(&radio->tx_dejitter[1], audio_samples[1], audio_num);
+ }
+ break;
+#endif
+ case AUDIO_MODE_TESTTONE:
+ for (i = 0; i < audio_num; i++) {
+ audio_samples[0][i] = radio->testtone[0][radio->testtone_pos];
+ audio_samples[1][i] = radio->testtone[1][radio->testtone_pos];
+ radio->testtone_pos = (radio->testtone_pos + 1) % radio->testtone_length;
+ }
+ break;
+ default:
+ PDEBUG(DRADIO, DEBUG_ERROR, "Wrong audio mode, plese fix!\n");
+ return -EINVAL;
+ }
+
+ /* convert mono/stereo, generate differential signal */
+ if (radio->stereo && radio->tx_audio_channels == 1) {
+ /* mono to stereo: sum is 90%, differential signal is 0 */
+ for (i = 0; i < audio_num; i++) {
+ audio_samples[0][i] = 0.9;
+ audio_samples[1][i] = 0.0;
+ }
+ }
+ if (radio->stereo && radio->tx_audio_channels == 2) {
+ /* stereo: sum is 90%, diffential is 90% */
+ double left, right;
+ for (i = 0; i < audio_num; i++) {
+ left = audio_samples[0][i];
+ right = audio_samples[1][i];
+ audio_samples[0][i] = (left + right) * 0.45;
+ audio_samples[1][i] = (left - right) * 0.45;
+ }
+ }
+ if (!radio->stereo && radio->tx_audio_channels == 2) {
+ /* stereo to mono: sum both channel */
+ for (i = 0; i < audio_num; i++)
+ audio_samples[0][i] = (audio_samples[0][i] + audio_samples[1][i]) / 2.0;
+ }
+
+ /* remove DC */
+ iir_process(&radio->tx_dc_removal[0], audio_samples[0], audio_num);
+ if (radio->stereo)
+ iir_process(&radio->tx_dc_removal[1], audio_samples[1], audio_num);
+
+ /* upsample */
+ signal_num = samplerate_upsample(&radio->tx_resampler[0], audio_samples[0], audio_num, signal_samples[0]);
+ if (radio->stereo)
+ samplerate_upsample(&radio->tx_resampler[1], audio_samples[1], audio_num, signal_samples[1]);
+
+ /* prepare baseband */
+ memset(baseband, 0, sizeof(float) * 2 * signal_num);
+
+ /* filter audio (remove DC, remove high frequencies, pre-emphasis)
+ * and modulate */
+ switch (radio->modulation) {
+ case MODULATION_FM:
+ memset(signal_power, 1, signal_num);
+ pre_emphasis(&radio->fm_emphasis[0], signal_samples[0], signal_num);
+ clipper_process(signal_samples[0], signal_num);
+ if (radio->stereo) {
+ pre_emphasis(&radio->fm_emphasis[1], signal_samples[1], signal_num);
+ clipper_process(signal_samples[1], signal_num);
+ /* add pilot tone */
+ double phasestep = radio->pilot_phasestep;
+ double phase = radio->tx_pilot_phase;
+ for (i = 0; i < signal_num; i++) {
+ signal_samples[0][i] += sin(phase) * 0.1;
+ signal_samples[0][i] += signal_samples[1][i] * sin(phase * 2);
+ phase += phasestep;
+ if (phase >= 2.0 * M_PI)
+ phase -= 2.0 * M_PI;
+ }
+ radio->tx_pilot_phase = phase;
+ }
+ for (i = 0; i < signal_num; i++)
+ signal_samples[0][i] *= radio->fm_deviation;
+ fm_modulate_complex(&radio->fm_mod, signal_samples[0], signal_power, signal_num, baseband);
+ break;
+ case MODULATION_AM_DSB:
+ /* also clip to prevent overshooting after audio filtering */
+ clipper_process(signal_samples[0], signal_num);
+ iir_process(&radio->tx_am_bw_limit, signal_samples[0], signal_num);
+ am_modulate_complex(&radio->am_mod, signal_samples[0], signal_num, baseband);
+ break;
+ case MODULATION_AM_USB:
+ case MODULATION_AM_LSB:
+ /* also clip to prevent overshooting after audio filtering */
+ clipper_process(signal_samples[0], signal_num);
+ iir_process(&radio->tx_am_bw_limit, signal_samples[0], signal_num);
+ am_modulate_complex(&radio->am_mod, signal_samples[0], signal_num, baseband);
+ break;
+ default:
+ break;
+ }
+
+ return signal_num;
+}
+
+int radio_rx(radio_t *radio, float *baseband, int signal_num)
+{
+ int i;
+ int audio_num;
+ sample_t *samples[3];
+ double p;
+
+ if (signal_num > radio->latspl) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "signal_num > latspl, please fix!.\n");
+ abort();
+ }
+
+ if (signal_num > radio->signal_buffer_size) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "signal_num > signal_buffer_size, please fix!.\n");
+ abort();
+ }
+ samples[0] = radio->signal_buffer;
+ samples[1] = radio->signal_buffer + radio->signal_buffer_size;
+ samples[2] = radio->signal_buffer + radio->signal_buffer_size * 2;
+
+ switch (radio->modulation) {
+ case MODULATION_FM:
+ fm_demodulate_complex(&radio->fm_demod, samples[0], signal_num, baseband, radio->I_buffer, radio->Q_buffer);
+ for (i = 0; i < signal_num; i++)
+ samples[0][i] /= radio->fm_deviation;
+ if (radio->stereo) {
+ /* filter pilot tone */
+ p = radio->rx_pilot_phase; /* don't increment in radio structure, will be done later */
+ for (i = 0; i < signal_num; i++) {
+ samples[1][i] = samples[0][i] * cos(p); /* I */
+ samples[2][i] = samples[0][i] * sin(p); /* Q */
+ p += radio->pilot_phasestep;
+ if (p >= 2.0 * M_PI)
+ p -= 2.0 * M_PI;
+ }
+ iir_process(&radio->rx_lp_pilot_I, samples[1], signal_num);
+ iir_process(&radio->rx_lp_pilot_Q, samples[2], signal_num);
+ /* mix pilot tone (double phase) with differential signal */
+ for (i = 0; i < signal_num; i++) {
+ p = atan2(samples[2][i], samples[1][i]);
+ /* substract measured phase difference (use double amplitude, because we filter later) */
+ samples[1][i] = samples[0][i] * sin((radio->rx_pilot_phase - p) * 2.0) * 2.0;
+ radio->rx_pilot_phase += radio->pilot_phasestep;
+ if (radio->rx_pilot_phase >= 2.0 * M_PI)
+ radio->rx_pilot_phase -= 2.0 * M_PI;
+ }
+ /* filter to match bandwidth */
+ iir_process(&radio->rx_lp_sum, samples[0], signal_num);
+ iir_process(&radio->rx_lp_diff, samples[1], signal_num);
+ }
+ dc_filter(&radio->fm_emphasis[0], samples[0], signal_num);
+ de_emphasis(&radio->fm_emphasis[0], samples[0], signal_num);
+ if (radio->stereo) {
+ dc_filter(&radio->fm_emphasis[1], samples[1], signal_num);
+ de_emphasis(&radio->fm_emphasis[1], samples[1], signal_num);
+ }
+ break;
+ case MODULATION_AM_DSB:
+ am_demodulate_complex(&radio->am_demod, samples[0], signal_num, baseband, radio->I_buffer, radio->Q_buffer, radio->carrier_buffer);
+ break;
+ case MODULATION_AM_USB:
+ case MODULATION_AM_LSB:
+ am_demodulate_complex(&radio->am_demod, samples[0], signal_num, baseband, radio->I_buffer, radio->Q_buffer, radio->carrier_buffer);
+ break;
+ default:
+ break;
+ }
+
+ /* downsample */
+ audio_num = samplerate_downsample(&radio->rx_resampler[0], samples[0], signal_num);
+ if (radio->stereo)
+ samplerate_downsample(&radio->rx_resampler[1], samples[1], signal_num);
+
+ /* convert mono/stereo, (from differential signal) */
+ if (radio->stereo && radio->rx_audio_channels == 1) {
+ /* stereo to mono */
+ for (i = 0; i < audio_num; i++) {
+ samples[0][i] = (samples[0][i] + samples[1][i]) / 2.0;
+ }
+ }
+ if (radio->stereo && radio->rx_audio_channels == 2) {
+ /* stereo from differential */
+ double sum, diff;
+ for (i = 0; i < audio_num; i++) {
+ sum = samples[0][i];
+ diff = samples[1][i];
+ samples[0][i] = sum + diff / 2.0;
+ samples[1][i] = sum - diff / 2.0;
+ }
+ }
+ if (!radio->stereo && radio->rx_audio_channels == 2) {
+ /* mono to stereo: clone channel */
+ for (i = 0; i < audio_num; i++)
+ samples[1][i] = samples[0][i];
+ }
+
+ /* store received audio */
+ switch (radio->rx_audio_mode) {
+ case AUDIO_MODE_WAVEFILE:
+ wave_write(&radio->wave_rx_rec, samples, audio_num);
+ break;
+#ifdef HAVE_ALSA
+ case AUDIO_MODE_AUDIODEV:
+ jitter_save(&radio->rx_dejitter[0], samples[0], audio_num);
+ if (radio->rx_audio_channels == 2)
+ jitter_save(&radio->rx_dejitter[1], samples[1], audio_num);
+ audio_num = sound_get_tosend(radio->rx_sound, radio->signal_buffer_size);
+ jitter_load(&radio->rx_dejitter[0], samples[0], audio_num);
+ if (radio->rx_audio_channels == 2)
+ jitter_load(&radio->rx_dejitter[1], samples[1], audio_num);
+ audio_num = sound_write(radio->rx_sound, samples, NULL, audio_num, NULL, NULL, radio->rx_audio_channels);
+ if (audio_num < 0) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to write to sound device (rc = %d)!\n", audio_num);
+ if (audio_num == -EPIPE)
+ PDEBUG(DRADIO, DEBUG_ERROR, "Trying to recover.\n");
+ else
+ return 0;
+ }
+ break;
+#endif
+ default:
+ PDEBUG(DRADIO, DEBUG_ERROR, "Wrong audio mode, plese fix!\n");
+ return -EINVAL;
+ }
+
+ return signal_num;
+}
+
diff --git a/src/radio/radio.h b/src/radio/radio.h
new file mode 100644
index 0000000..5bb5239
--- /dev/null
+++ b/src/radio/radio.h
@@ -0,0 +1,85 @@
+
+#include "../libwave/wave.h"
+#include "../libsamplerate/samplerate.h"
+#include "../libemphasis/emphasis.h"
+#include "../libjitter/jitter.h"
+#include "../libfm/fm.h"
+#include "../libam/am.h"
+
+enum modulation {
+ MODULATION_NONE = 0,
+ MODULATION_FM,
+ MODULATION_AM_DSB,
+ MODULATION_AM_USB,
+ MODULATION_AM_LSB,
+};
+
+enum audio_mode {
+ AUDIO_MODE_NONE = 0,
+ AUDIO_MODE_WAVEFILE,
+ AUDIO_MODE_AUDIODEV,
+ AUDIO_MODE_TESTTONE,
+};
+
+typedef struct radio {
+ /* modes */
+ int latspl; /* maximum number of samples */
+ enum modulation modulation; /* modulation type */
+ enum audio_mode tx_audio_mode; /* mode for audio source */
+ enum audio_mode rx_audio_mode; /* mode for audio sink */
+ int stereo; /* use stere FM */
+ int rds, rds2; /* use RDS */
+ /* audio stage */
+ double tx_audio_samplerate; /* sample rate of audio source */
+ double rx_audio_samplerate; /* sample rate of audio sink */
+ int tx_audio_channels; /* number of channels of audio source */
+ int rx_audio_channels; /* number of channels of audio sink */
+ double audio_bandwidth; /* audio bandwidth */
+ const char *tx_wave_file; /* wave file name of source */
+ const char *rx_wave_file; /* wave file name of sink */
+ wave_play_t wave_tx_play; /* wave playback process */
+ wave_rec_t wave_rx_rec; /* wave record process */
+ void *tx_sound; /* sound card process */
+ void *rx_sound; /* sound card process */
+ jitter_t tx_dejitter[2]; /* jitter buffer when reading from sound card */
+ jitter_t rx_dejitter[2]; /* jitter buffer when writing to sound card */
+ sample_t *testtone[2]; /* test tone sample */
+ int testtone_length;
+ int testtone_pos;
+ /* signal stage */
+ double signal_samplerate;
+ double signal_bandwidth;
+ samplerate_t tx_resampler[2]; /* resampling from audio rate to signal rate (two channels) */
+ samplerate_t rx_resampler[2]; /* resampling from signal rate to audi rate (two channels) */
+ emphasis_t fm_emphasis[2]; /* FM pre emphasis */
+ double fm_deviation; /* deviation of fm signal */
+ fm_mod_t fm_mod; /* FM modulation */
+ fm_demod_t fm_demod; /* FM modulation */
+ double pilot_phasestep; /* phase change of pilot tone for each sample */
+ double tx_pilot_phase; /* current phase of tx sine */
+ double rx_pilot_phase; /* current phase of rx mixer */
+ iir_filter_t tx_dc_removal[2]; /* AM/FM DC level removal */
+ iir_filter_t tx_am_bw_limit; /* AM bandwidth limiter */
+ iir_filter_t rx_lp_pilot_I; /* low pass filter for pilot tone extraction */
+ iir_filter_t rx_lp_pilot_Q; /* low pass filter for pilot tone extraction */
+ iir_filter_t rx_lp_sum; /* filter sum signal of stereo */
+ iir_filter_t rx_lp_diff; /* filter differential signal of stereo */
+ am_mod_t am_mod; /* AM modulation */
+ am_demod_t am_demod; /* AM modulation */
+ /* buffers */
+ sample_t *audio_buffer;
+ int audio_buffer_size;
+ sample_t *signal_buffer;
+ uint8_t *signal_power_buffer;
+ int signal_buffer_size;
+ sample_t *I_buffer;
+ sample_t *Q_buffer;
+ sample_t *carrier_buffer;
+} radio_t;
+
+int radio_init(radio_t *radio, int latspl, int samplerate, const char *tx_wave_file, const char *rx_wave_file, const char *tx_audiodev, const char *rx_audiodev, enum modulation modulation, double bandwidth, double deviation, double modulation_index, double time_constant, int stereo, int rds, int rds2);
+void radio_exit(radio_t *radio);
+int radio_start(radio_t *radio);
+int radio_tx(radio_t *radio, float *baseband, int num);
+int radio_rx(radio_t *radio, float *baseband, int num);
+