├── res ├── .gitignore └── sdr.svg ├── .gitignore ├── src ├── PQ.hpp ├── PQ.cpp └── sdr │ ├── rtl-sdr.h │ ├── convenience.h │ ├── SDR.cpp │ ├── convenience.c │ └── rtl-sdr.c ├── README.md ├── plugin.json ├── .travis.yml ├── Makefile └── LICENSE /res/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | *.dylib 4 | -------------------------------------------------------------------------------- /src/PQ.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | 6 | extern Plugin *pluginInstance; 7 | 8 | //////////////////// 9 | // module widgets 10 | //////////////////// 11 | extern Model *sdrModule; 12 | -------------------------------------------------------------------------------- /src/PQ.cpp: -------------------------------------------------------------------------------- 1 | #include "PQ.hpp" 2 | 3 | // The pluginInstance-wide instance of the Plugin class 4 | Plugin *pluginInstance; 5 | 6 | void init(rack::Plugin *p) { 7 | pluginInstance = p; 8 | p->addModel(sdrModule); 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RTL-SDR VCVRack plugin 2 | 3 | A voltage-controlled FM radio tuner for [VCVRack](https://vcvrack.com) supporting USB dongles via [librtlsdr](https://osmocom.org/projects/sdr/wiki/rtl-sdr). Based on [rtl_fm](http://kmkeen.com/rtl-demod-guide/) 4 | 5 | [Downloads](https://vcvrack.com/plugins.html#RTL_SDR) 6 | 7 | [![Build Status](https://travis-ci.org/WIZARDISHUNGRY/vcvrack-rtlsdr.svg?branch=master)](https://travis-ci.org/WIZARDISHUNGRY/vcvrack-rtlsdr) 8 | -------------------------------------------------------------------------------- /src/sdr/rtl-sdr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | struct RtlSdr { 12 | pthread_mutex_t* rack_mutex; 13 | int16_t* rack_buffer; 14 | long* rack_buffer_pos; 15 | }; 16 | int RtlSdr_init(struct RtlSdr* radio, int engineSampleRate); 17 | void RtlSdr_end(struct RtlSdr* radio); 18 | void RtlSdr_tune(struct RtlSdr* radio, long freq); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "PulsumQuadratum-SDR", 3 | "name": "Pulsum Quadratum", 4 | "version": "1.0.0", 5 | "license": " GPL-2.0-only", 6 | "brand": "PulsumQuadratum-SDR", 7 | "author": "Jon Williams", 8 | "authorEmail": "", 9 | "authorUrl": "https://bongo.zone/", 10 | "pluginUrl": "https://bongo.zone/modules/rtl-sdr", 11 | "manualUrl": "https://bongo.zone/modules/rtl-sdr", 12 | "sourceUrl": "https://github.com/bongozone/vcvrack-rtlsdr", 13 | "donateUrl": "", 14 | "changelogUrl": "", 15 | "modules": [ 16 | { 17 | "slug": "SDRWidget", 18 | "name": "SDRWidget", 19 | "description": "rtl-sdr FM Radio Tuner", 20 | "tags": [ 21 | "Tuner" 22 | ] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | cache: 3 | directories: 4 | - /tmp/Rack 5 | before_cache: 6 | - rm -fr /tmp/Rack/plugins/sdr 7 | osx_image: xcode9.2 8 | os: 9 | - linux 10 | - osx 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - g++-7 17 | install: 18 | - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get install librtlsdr-dev git libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libgl1-mesa-dev libglu1-mesa-dev zlib1g-dev libasound2-dev libgtk2.0-dev unzip cmake3 libudev-dev libusb-dev libusb-1.0-0-dev; fi 19 | - if [ $TRAVIS_OS_NAME == osx ]; then brew install librtlsdr libusb; fi 20 | before_script: 21 | - git clone https://github.com/VCVRack/Rack.git /tmp/Rack || cd /tmp/Rack && git pull 22 | - cd /tmp/Rack 23 | - git checkout master # for 0.6 series 24 | - git submodule update --init --recursive 25 | - if [ $TRAVIS_OS_NAME == linux ]; then export CC=gcc-7 && CXX=g++-7; fi 26 | - make -j 8 dep > /dev/null 27 | - make -j 8 28 | - rm -rf /tmp/Rack/plugins/sdr || true 29 | - cp -r $TRAVIS_BUILD_DIR /tmp/Rack/plugins/sdr 30 | script: 31 | - cd /tmp/Rack/plugins/sdr && VERSION=0.0.0 make dist 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If RACK_DIR is not defined when calling the Makefile, default to two levels above 2 | RACK_DIR ?= ../.. 3 | 4 | PKGCONFIG= pkg-config 5 | PACKAGES= libusb-1.0 librtlsdr 6 | 7 | # FLAGS will be passed to both the C and C++ compiler 8 | FLAGS += $(shell $(PKGCONFIG) --cflags $(PACKAGES)) 9 | CFLAGS += 10 | CXXFLAGS += 11 | 12 | # Add .cpp and .c files to the build 13 | SOURCES = $(wildcard src/*.cpp src/*.c src/*/*.cpp src/*/*.c) 14 | 15 | # Must include the VCV plugin Makefile framework 16 | include $(RACK_DIR)/arch.mk 17 | 18 | # Careful about linking to libraries, since you can't assume much about the user's environment and library search path. 19 | # Static libraries are fine. 20 | ifeq ($(ARCH), lin) 21 | # WARNING: static compilation is broken on Linux 22 | LDFLAGS +=$(shell $(PKGCONFIG) --libs $(PACKAGES)) 23 | endif 24 | 25 | ifeq ($(ARCH), mac) 26 | LDFLAGS +=$(shell $(PKGCONFIG) --variable=libdir libusb-1.0)/libusb-1.0.a 27 | LDFLAGS +=$(shell $(PKGCONFIG) --variable=libdir librtlsdr)/librtlsdr.a 28 | endif 29 | 30 | ifeq ($(ARCH), win) 31 | LDFLAGS +=$(shell $(PKGCONFIG) --variable=libdir librtlsdr)/librtlsdr_static.a 32 | LDFLAGS +=$(shell $(PKGCONFIG) --variable=libdir libusb-1.0)/libusb-1.0.a 33 | endif 34 | 35 | DISTRIBUTABLES += $(wildcard LICENSE*) res 36 | 37 | # Include the VCV Rack plugin Makefile framework 38 | include $(RACK_DIR)/plugin.mk 39 | -------------------------------------------------------------------------------- /res/sdr.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | panel 26 | 27 | 28 | 29 | 31 | 51 | panel 53 | 60 | 61 | -------------------------------------------------------------------------------- /src/sdr/convenience.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2014 by Kyle Keen 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /* a collection of user friendly tools */ 19 | 20 | /*! 21 | * Convert standard suffixes (k, M, G) to double 22 | * 23 | * \param s a string to be parsed 24 | * \return double 25 | */ 26 | 27 | double atofs(char *s); 28 | 29 | /*! 30 | * Convert time suffixes (s, m, h) to double 31 | * 32 | * \param s a string to be parsed 33 | * \return seconds as double 34 | */ 35 | 36 | double atoft(char *s); 37 | 38 | /*! 39 | * Convert percent suffixe (%) to double 40 | * 41 | * \param s a string to be parsed 42 | * \return double 43 | */ 44 | 45 | double atofp(char *s); 46 | 47 | /*! 48 | * Find nearest supported gain 49 | * 50 | * \param dev the device handle given by rtlsdr_open() 51 | * \param target_gain in tenths of a dB 52 | * \return 0 on success 53 | */ 54 | 55 | int nearest_gain(rtlsdr_dev_t *dev, int target_gain); 56 | 57 | /*! 58 | * Set device frequency and report status on stderr 59 | * 60 | * \param dev the device handle given by rtlsdr_open() 61 | * \param frequency in Hz 62 | * \return 0 on success 63 | */ 64 | 65 | int verbose_set_frequency(rtlsdr_dev_t *dev, uint32_t frequency); 66 | 67 | /*! 68 | * Set device sample rate and report status on stderr 69 | * 70 | * \param dev the device handle given by rtlsdr_open() 71 | * \param samp_rate in samples/second 72 | * \return 0 on success 73 | */ 74 | 75 | int verbose_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate); 76 | 77 | /*! 78 | * Enable or disable the direct sampling mode and report status on stderr 79 | * 80 | * \param dev the device handle given by rtlsdr_open() 81 | * \param on 0 means disabled, 1 I-ADC input enabled, 2 Q-ADC input enabled 82 | * \return 0 on success 83 | */ 84 | 85 | int verbose_direct_sampling(rtlsdr_dev_t *dev, int on); 86 | 87 | /*! 88 | * Enable offset tuning and report status on stderr 89 | * 90 | * \param dev the device handle given by rtlsdr_open() 91 | * \return 0 on success 92 | */ 93 | 94 | int verbose_offset_tuning(rtlsdr_dev_t *dev); 95 | 96 | /*! 97 | * Enable auto gain and report status on stderr 98 | * 99 | * \param dev the device handle given by rtlsdr_open() 100 | * \return 0 on success 101 | */ 102 | 103 | int verbose_auto_gain(rtlsdr_dev_t *dev); 104 | 105 | /*! 106 | * Set tuner gain and report status on stderr 107 | * 108 | * \param dev the device handle given by rtlsdr_open() 109 | * \param gain in tenths of a dB 110 | * \return 0 on success 111 | */ 112 | 113 | int verbose_gain_set(rtlsdr_dev_t *dev, int gain); 114 | 115 | /*! 116 | * Set the frequency correction value for the device and report status on stderr. 117 | * 118 | * \param dev the device handle given by rtlsdr_open() 119 | * \param ppm_error correction value in parts per million (ppm) 120 | * \return 0 on success 121 | */ 122 | 123 | int verbose_ppm_set(rtlsdr_dev_t *dev, int ppm_error); 124 | 125 | /*! 126 | * Attempts to extract a correction value from eeprom and store it to an int. 127 | * 128 | * \param dev the device handle given by rtlsdr_open() 129 | * \param ppm_error correction value in parts per million (ppm) 130 | * \return 0 on success 131 | */ 132 | int verbose_ppm_eeprom(rtlsdr_dev_t *dev, int *ppm_error); 133 | 134 | /*! 135 | * Reset buffer 136 | * 137 | * \param dev the device handle given by rtlsdr_open() 138 | * \return 0 on success 139 | */ 140 | 141 | int verbose_reset_buffer(rtlsdr_dev_t *dev); 142 | 143 | /*! 144 | * Find the closest matching device. 145 | * 146 | * \param s a string to be parsed 147 | * \return dev_index int, -1 on error 148 | */ 149 | 150 | int verbose_device_search(char *s); 151 | -------------------------------------------------------------------------------- /src/sdr/SDR.cpp: -------------------------------------------------------------------------------- 1 | #include "../PQ.hpp" 2 | #include "rtl-sdr.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include // setprecision 8 | #include // stringstream 9 | #define HZ_CEIL 110.0 10 | #define HZ_FLOOR 80.0 11 | #define HZ_SPAN (HZ_CEIL-HZ_FLOOR) 12 | #define HZ_CENTER (HZ_FLOOR+0.5*HZ_SPAN) 13 | #define MAX_VOLTAGE 5.0 14 | 15 | struct MyLabel : Widget { 16 | std::string text; 17 | int fontSize; 18 | NVGcolor color = nvgRGB(255,20,20); 19 | MyLabel(int _fontSize = 18) { 20 | fontSize = _fontSize; 21 | } 22 | void draw(const DrawArgs& args) override { 23 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER|NVG_ALIGN_BASELINE); 24 | nvgFillColor(args.vg, color); 25 | nvgFontSize(args.vg, fontSize); 26 | nvgText(args.vg, box.pos.x, box.pos.y, text.c_str(), NULL); 27 | } 28 | }; 29 | 30 | struct SDR : Module { 31 | enum ParamIds { 32 | TUNE_PARAM, 33 | TUNE_ATT, 34 | QUANT_PARAM, 35 | NUM_PARAMS 36 | }; 37 | enum InputIds { 38 | TUNE_INPUT, 39 | NUM_INPUTS 40 | }; 41 | enum OutputIds { 42 | AUDIO_OUT, 43 | NUM_OUTPUTS 44 | }; 45 | enum LightIds { 46 | NUM_LIGHTS 47 | }; 48 | 49 | RtlSdr radio; 50 | rack::dsp::RingBuffer buffer; 51 | long currentFreq; 52 | 53 | SDR() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { 54 | buffer.clear(); 55 | RtlSdr_init(&radio, (int)APP->engine->getSampleRate()); 56 | configParam(SDR::TUNE_PARAM, HZ_FLOOR, HZ_CEIL, HZ_CENTER, ""); 57 | configParam(SDR::TUNE_ATT, -HZ_SPAN/2.0, +HZ_SPAN/2.0, 0.0, ""); 58 | configParam(SDR::QUANT_PARAM, 0.0, 2.0, 0.0, ""); 59 | } 60 | ~SDR() { 61 | RtlSdr_end(&radio); 62 | } 63 | void onSampleRateChange() override; 64 | void process(const ProcessArgs& args) override; 65 | void openFile(); 66 | long getFreq(float); 67 | float getMegaFreq(long); 68 | MyLabel* linkedLabel; 69 | long stepCount; 70 | }; 71 | void SDR::process(const ProcessArgs& args) { 72 | 73 | if (radio.rack_buffer==NULL && stepCount++ % 100000 == 0) { 74 | RtlSdr_init(&radio, (int)args.sampleRate); 75 | return; 76 | } 77 | 78 | if (radio.rack_buffer==NULL) { 79 | return; 80 | } 81 | 82 | if(buffer.size() < 10 ) { // This seems reasonable 83 | //printf("📻 ring buffer is getting low (%ld), try mutex\n", buffer.size()); 84 | int error = pthread_mutex_trylock(radio.rack_mutex); 85 | if (error != 0) { 86 | if(error==EBUSY) { 87 | printf("📻 mutex busy\n"); 88 | } else { 89 | printf("📻 mutex error\n"); 90 | } 91 | } else { 92 | if(*(radio.rack_buffer_pos) != 0) { 93 | for(int i = 0; i < *(radio.rack_buffer_pos); i++) { 94 | if(buffer.full()) { 95 | printf("📻 sdr buffer overrun\n"); 96 | break; 97 | } 98 | buffer.push(radio.rack_buffer[i]); 99 | } 100 | //printf("📻 ring buffer consumed %ld, size is now %d\n", *(radio.rack_buffer_pos), buffer.size()); 101 | *(radio.rack_buffer_pos) = 0; 102 | } 103 | pthread_mutex_unlock(radio.rack_mutex); 104 | } 105 | } 106 | 107 | float freq = params[TUNE_PARAM].getValue(); 108 | float freqOff = params[TUNE_ATT].getValue()*inputs[TUNE_INPUT].getVoltage()/MAX_VOLTAGE; 109 | float freqComputed = freq + freqOff; 110 | long longFreq = getFreq(freqComputed) ; // lots of zeros 111 | 112 | enum Quantization {HUNDREDK, TENK, NONE}; 113 | Quantization scale = static_cast(roundf(params[QUANT_PARAM].getValue())); 114 | long modulo; 115 | switch(scale) { 116 | case HUNDREDK: 117 | modulo = 100000; 118 | break; 119 | case TENK: 120 | modulo = 10000; 121 | break; 122 | case NONE: 123 | modulo = 1; 124 | } 125 | long modulus = longFreq%modulo; 126 | longFreq -= modulus; 127 | if(modulus>=modulo/2) { 128 | longFreq+=modulo; 129 | } 130 | 131 | 132 | if (longFreq - currentFreq) { 133 | RtlSdr_tune(&radio, longFreq); 134 | currentFreq = longFreq; 135 | std::stringstream stream; 136 | stream << std::fixed << std::setprecision(3) << std::setw(7) << getMegaFreq(longFreq); 137 | std::cout << std::setw(7) << std::fixed << std::setprecision(3) << getMegaFreq(longFreq) << "\n"; 138 | linkedLabel->text = stream.str(); 139 | } 140 | 141 | if(!buffer.empty()) { 142 | int16_t sample = buffer.shift(); 143 | float value = MAX_VOLTAGE*float(sample)/(float)SHRT_MAX; 144 | outputs[SDR::AUDIO_OUT].value = value; 145 | } else { 146 | //printf("📻 awaiting buffer\n"); 147 | } 148 | } 149 | 150 | void SDR::onSampleRateChange() { 151 | } 152 | 153 | long SDR::getFreq(float knob) { 154 | return int(knob*1000000.f); // float quantities are in millions so this is a million 155 | } 156 | 157 | float SDR::getMegaFreq(long longFreq) { 158 | return float(longFreq)/ 1000000.f; // float quantities are in millions so this is a million 159 | } 160 | 161 | struct SDRWidget : ModuleWidget { 162 | SDRWidget(SDR *module); 163 | }; 164 | 165 | SDRWidget::SDRWidget(SDR *module) : ModuleWidget(module) { 166 | 167 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 168 | 169 | // Panel *panel = new LightPanel(); 170 | SVGPanel *panel = new SVGPanel(); 171 | panel->box.size = box.size; 172 | panel->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/sdr.svg"))); 173 | addChild(panel); 174 | 175 | addChild(createWidget(Vec(0, 0))); 176 | addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 177 | 178 | { 179 | MyLabel* const freqLabel = new MyLabel; 180 | freqLabel->box.pos = Vec(box.size.x/4,RACK_GRID_WIDTH*3); // coordinate system is broken FIXME 181 | freqLabel->text = "0"; 182 | if(module) 183 | module->linkedLabel = freqLabel; 184 | addChild(freqLabel); 185 | } 186 | 187 | { 188 | MyLabel* const cLabel = new MyLabel(10); 189 | cLabel->box.pos = Vec(19,5); // coordinate system is broken FIXME 190 | cLabel->color = nvgRGB(0,0,0); 191 | cLabel->text = "rtl-sdr FM"; 192 | addChild(cLabel); 193 | } 194 | 195 | { 196 | MyLabel* const cLabel = new MyLabel(9); 197 | cLabel->box.pos = Vec(14.5,(RACK_GRID_HEIGHT - RACK_GRID_WIDTH/1.5)/2); // coordinate system is broken FIXME 198 | cLabel->color = nvgRGB(0,0,0); 199 | cLabel->text = "pulsum"; 200 | addChild(cLabel); 201 | } 202 | { 203 | MyLabel* const cLabel = new MyLabel(9); 204 | cLabel->box.pos = Vec(18,(RACK_GRID_HEIGHT - RACK_GRID_WIDTH/5)/2); // coordinate system is broken FIXME 205 | cLabel->color = nvgRGB(0,0,0); 206 | cLabel->text = "quadratum"; 207 | addChild(cLabel); 208 | } 209 | 210 | SVGKnob *knob = dynamic_cast(createParam(Vec(RACK_GRID_WIDTH/6, 100), module, SDR::TUNE_PARAM)); 211 | knob->maxAngle += 10*2*M_PI; 212 | knob->speed /= 20.f; 213 | addParam(knob); 214 | addParam(createParam(Vec(RACK_GRID_WIDTH, 170), module, SDR::TUNE_ATT)); 215 | addInput(createInput(Vec(RACK_GRID_WIDTH, 200), module, SDR::TUNE_INPUT)); 216 | addParam(createParam(Vec(RACK_GRID_WIDTH/2, 240), module, SDR::QUANT_PARAM)); 217 | { 218 | MyLabel* const cLabel = new MyLabel(12); 219 | cLabel->box.pos = Vec(16,236/2); // coordinate system is broken FIXME 220 | cLabel->color = nvgRGB(0,0,0); 221 | cLabel->text = "Stepping"; 222 | addChild(cLabel); 223 | } 224 | { 225 | MyLabel* const cLabel = new MyLabel(12); 226 | cLabel->box.pos = Vec(20,240/2+4); // coordinate system is broken FIXME 227 | cLabel->color = nvgRGB(0,0,0); 228 | cLabel->text = "none "; 229 | addChild(cLabel); 230 | } 231 | { 232 | MyLabel* const cLabel = new MyLabel(12); 233 | cLabel->box.pos = Vec(20,240/2+9); // coordinate system is broken FIXME 234 | cLabel->color = nvgRGB(0,0,0); 235 | cLabel->text = "10 k "; 236 | addChild(cLabel); 237 | } 238 | { 239 | MyLabel* const cLabel = new MyLabel(12); 240 | cLabel->box.pos = Vec(20,240/2+14); // coordinate system is broken FIXME 241 | cLabel->color = nvgRGB(0,0,0); 242 | cLabel->text = "100k"; 243 | addChild(cLabel); 244 | } 245 | 246 | 247 | addOutput(createOutput(Vec(RACK_GRID_WIDTH, box.size.y-3*RACK_GRID_WIDTH), module, SDR::AUDIO_OUT)); 248 | } 249 | 250 | 251 | Model *sdrModule = createModel("SDRWidget"); 252 | -------------------------------------------------------------------------------- /src/sdr/convenience.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2014 by Kyle Keen 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /* a collection of user friendly tools 19 | * todo: use strtol for more flexible int parsing 20 | * */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef _WIN32 27 | #include 28 | #else 29 | #include 30 | #include 31 | #include 32 | #define _USE_MATH_DEFINES 33 | #endif 34 | 35 | #include 36 | 37 | #include "rtl-sdr.h" 38 | 39 | double atofs(char *s) 40 | /* standard suffixes */ 41 | { 42 | char last; 43 | int len; 44 | double suff = 1.0; 45 | len = strlen(s); 46 | last = s[len-1]; 47 | s[len-1] = '\0'; 48 | switch (last) { 49 | case 'g': 50 | case 'G': 51 | suff *= 1e3; 52 | case 'm': 53 | case 'M': 54 | suff *= 1e3; 55 | case 'k': 56 | case 'K': 57 | suff *= 1e3; 58 | suff *= atof(s); 59 | s[len-1] = last; 60 | return suff; 61 | } 62 | s[len-1] = last; 63 | return atof(s); 64 | } 65 | 66 | double atoft(char *s) 67 | /* time suffixes, returns seconds */ 68 | { 69 | char last; 70 | int len; 71 | double suff = 1.0; 72 | len = strlen(s); 73 | last = s[len-1]; 74 | s[len-1] = '\0'; 75 | switch (last) { 76 | case 'h': 77 | case 'H': 78 | suff *= 60; 79 | case 'm': 80 | case 'M': 81 | suff *= 60; 82 | case 's': 83 | case 'S': 84 | suff *= atof(s); 85 | s[len-1] = last; 86 | return suff; 87 | } 88 | s[len-1] = last; 89 | return atof(s); 90 | } 91 | 92 | double atofp(char *s) 93 | /* percent suffixes */ 94 | { 95 | char last; 96 | int len; 97 | double suff = 1.0; 98 | len = strlen(s); 99 | last = s[len-1]; 100 | s[len-1] = '\0'; 101 | switch (last) { 102 | case '%': 103 | suff *= 0.01; 104 | suff *= atof(s); 105 | s[len-1] = last; 106 | return suff; 107 | } 108 | s[len-1] = last; 109 | return atof(s); 110 | } 111 | 112 | int nearest_gain(rtlsdr_dev_t *dev, int target_gain) 113 | { 114 | int i, r, err1, err2, count, nearest; 115 | int* gains; 116 | r = rtlsdr_set_tuner_gain_mode(dev, 1); 117 | if (r < 0) { 118 | fprintf(stderr, "WARNING: Failed to enable manual gain.\n"); 119 | return r; 120 | } 121 | count = rtlsdr_get_tuner_gains(dev, NULL); 122 | if (count <= 0) { 123 | return 0; 124 | } 125 | gains = malloc(sizeof(int) * count); 126 | count = rtlsdr_get_tuner_gains(dev, gains); 127 | nearest = gains[0]; 128 | for (i=0; i=0; i--) { 254 | if (serial[i] != start_char) { 255 | continue;} 256 | fprintf(stderr, "PPM calibration found in eeprom.\n"); 257 | status = 0; 258 | *ppm_error = atoi(serial + i + 1); 259 | break; 260 | } 261 | serial[len-1] = stop_char; 262 | return status; 263 | } 264 | 265 | int verbose_reset_buffer(rtlsdr_dev_t *dev) 266 | { 267 | int r; 268 | r = rtlsdr_reset_buffer(dev); 269 | if (r < 0) { 270 | fprintf(stderr, "WARNING: Failed to reset buffers.\n");} 271 | return r; 272 | } 273 | 274 | int verbose_device_search(char *s) 275 | { 276 | int i, device_count, device, offset; 277 | char *s2; 278 | char vendor[256] = {0}, product[256] = {0}, serial[256] = {0}; 279 | device_count = rtlsdr_get_device_count(); 280 | if (!device_count) { 281 | fprintf(stderr, "No supported devices found.\n"); 282 | return -1; 283 | } 284 | fprintf(stderr, "Found %d device(s):\n", device_count); 285 | for (i = 0; i < device_count; i++) { 286 | if (rtlsdr_get_device_usb_strings(i, vendor, product, serial) == 0) { 287 | fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial); 288 | } else { 289 | fprintf(stderr, " %d: %s\n", i, "Failed to query data"); 290 | } 291 | } 292 | fprintf(stderr, "\n"); 293 | /* does string look like raw id number */ 294 | device = (int)strtol(s, &s2, 0); 295 | if (s2[0] == '\0' && device >= 0 && device < device_count) { 296 | fprintf(stderr, "Using device %d: %s\n", 297 | device, rtlsdr_get_device_name((uint32_t)device)); 298 | return device; 299 | } 300 | /* does string exact match a serial */ 301 | for (i = 0; i < device_count; i++) { 302 | rtlsdr_get_device_usb_strings(i, vendor, product, serial); 303 | if (strcmp(s, serial) != 0) { 304 | continue;} 305 | device = i; 306 | fprintf(stderr, "Using device %d: %s\n", 307 | device, rtlsdr_get_device_name((uint32_t)device)); 308 | return device; 309 | } 310 | /* does string prefix match a serial */ 311 | for (i = 0; i < device_count; i++) { 312 | rtlsdr_get_device_usb_strings(i, vendor, product, serial); 313 | if (strncmp(s, serial, strlen(s)) != 0) { 314 | continue;} 315 | device = i; 316 | fprintf(stderr, "Using device %d: %s\n", 317 | device, rtlsdr_get_device_name((uint32_t)device)); 318 | return device; 319 | } 320 | /* does string suffix match a serial */ 321 | for (i = 0; i < device_count; i++) { 322 | rtlsdr_get_device_usb_strings(i, vendor, product, serial); 323 | offset = strlen(serial) - strlen(s); 324 | if (offset < 0) { 325 | continue;} 326 | if (strncmp(s, serial+offset, strlen(s)) != 0) { 327 | continue;} 328 | device = i; 329 | fprintf(stderr, "Using device %d: %s\n", 330 | device, rtlsdr_get_device_name((uint32_t)device)); 331 | return device; 332 | } 333 | fprintf(stderr, "No matching devices found.\n"); 334 | return -1; 335 | } 336 | 337 | // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab 338 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /src/sdr/rtl-sdr.c: -------------------------------------------------------------------------------- 1 | #include "rtl-sdr.h" 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | // https://github.com/keenerd/rtl-sdr/blob/master/src/rtl_fm.c 7 | 8 | /* 9 | * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver 10 | * Copyright (C) 2012 by Steve Markgraf 11 | * Copyright (C) 2012 by Hoernchen 12 | * Copyright (C) 2012 by Kyle Keen 13 | * Copyright (C) 2013 by Elias Oenal 14 | * 15 | * This program is free software: you can redistribute it and/or modify 16 | * it under the terms of the GNU General Public License as published by 17 | * the Free Software Foundation, either version 2 of the License, or 18 | * (at your option) any later version. 19 | * 20 | * This program is distributed in the hope that it will be useful, 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | * GNU General Public License for more details. 24 | * 25 | * You should have received a copy of the GNU General Public License 26 | * along with this program. If not, see . 27 | */ 28 | 29 | 30 | /* 31 | * written because people could not do real time 32 | * FM demod on Atom hardware with GNU radio 33 | * based on rtl_sdr.c and rtl_tcp.c 34 | * 35 | * lots of locks, but that is okay 36 | * (no many-to-many locks) 37 | * 38 | * todo: 39 | * sanity checks 40 | * frequency ranges could be stored better 41 | * auto-hop after time limit 42 | * peak detector to tune onto stronger signals 43 | * fifo for active hop frequency 44 | * clips 45 | * noise squelch 46 | * merge stereo patch 47 | * merge udp patch 48 | * testmode to detect overruns 49 | * watchdog to reset bad dongle 50 | * fix oversampling 51 | */ 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | #ifdef __APPLE__ 60 | #include 61 | #else 62 | #include 63 | #endif 64 | 65 | #ifndef _WIN32 66 | #include 67 | #else 68 | #include 69 | #include 70 | #include 71 | #include "getopt/getopt.h" 72 | #define usleep(x) Sleep(x/1000) 73 | #if defined(_MSC_VER) && _MSC_VER < 1800 74 | #define round(x) (x > 0.0 ? floor(x + 0.5): ceil(x - 0.5)) 75 | #endif 76 | #define _USE_MATH_DEFINES 77 | #endif 78 | 79 | #include 80 | #include 81 | #include 82 | #include 83 | 84 | #include "rtl-sdr.h" 85 | #include "convenience.h" 86 | 87 | #define DEFAULT_SAMPLE_RATE 24000 88 | #define DEFAULT_BUF_LENGTH (1 * 16384) 89 | #define MAXIMUM_OVERSAMPLE 16 90 | #define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH) 91 | #define AUTO_GAIN -100 92 | #define BUFFER_DUMP 4096 93 | #define MAXIMUM_RATE 2400000 94 | 95 | #define FREQUENCIES_LIMIT 1000 96 | 97 | #define PI_INT (1<<14) 98 | #define ONE_INT (1<<14) 99 | 100 | static volatile int do_exit = 0; 101 | static int lcm_post[17] = {1,1,1,3,1,5,3,7,1,9,5,11,3,13,7,15,1}; 102 | static int ACTUAL_BUF_LENGTH; 103 | static uint32_t MINIMUM_RATE = 1000000; 104 | 105 | static int *atan_lut = NULL; 106 | static int atan_lut_size = 131072; /* 512 KB */ 107 | static int atan_lut_coef = 8; 108 | 109 | // rewrite as dynamic and thread-safe for multi demod/dongle 110 | #define SHARED_SIZE 6 111 | int16_t shared_samples[SHARED_SIZE][MAXIMUM_BUF_LENGTH]; 112 | int ss_busy[SHARED_SIZE] = {0}; 113 | 114 | enum agc_mode_t 115 | { 116 | agc_off = 0, 117 | agc_normal, 118 | agc_aggressive 119 | }; 120 | 121 | struct dongle_state 122 | { 123 | int exit_flag; 124 | pthread_t thread; 125 | rtlsdr_dev_t *dev; 126 | int dev_index; 127 | uint32_t freq; 128 | uint32_t rate; 129 | int gain; 130 | int16_t *buf16; 131 | uint32_t buf_len; 132 | int ppm_error; 133 | int offset_tuning; 134 | int direct_sampling; 135 | int mute; 136 | int pre_rotate; 137 | struct demod_state *targets[2]; 138 | }; 139 | 140 | struct agc_state 141 | { 142 | int32_t gain_num; 143 | int32_t gain_den; 144 | int32_t gain_max; 145 | int peak_target; 146 | int attack_step; 147 | int decay_step; 148 | int error; 149 | }; 150 | 151 | struct translate_state 152 | { 153 | double angle; /* radians */ 154 | int16_t *sincos; /* pairs */ 155 | int len; 156 | int i; 157 | }; 158 | 159 | struct demod_state 160 | { 161 | int exit_flag; 162 | pthread_t thread; 163 | int16_t *lowpassed; 164 | int lp_len; 165 | int16_t lp_i_hist[10][6]; 166 | int16_t lp_q_hist[10][6]; 167 | int16_t droop_i_hist[9]; 168 | int16_t droop_q_hist[9]; 169 | int rate_in; 170 | int rate_out; 171 | int rate_out2; 172 | int now_r, now_j; 173 | int pre_r, pre_j; 174 | int prev_index; 175 | int downsample; /* min 1, max 256 */ 176 | int post_downsample; 177 | int output_scale; 178 | int squelch_level, conseq_squelch, squelch_hits, terminate_on_squelch; 179 | int downsample_passes; 180 | int comp_fir_size; 181 | int custom_atan; 182 | int deemph, deemph_a; 183 | int now_lpr; 184 | int prev_lpr_index; 185 | int dc_block, dc_avg; 186 | int rotate_enable; 187 | struct translate_state rotate; 188 | enum agc_mode_t agc_mode; 189 | struct agc_state *agc; 190 | void (*mode_demod)(struct demod_state*); 191 | pthread_rwlock_t rw; 192 | pthread_cond_t ready; 193 | pthread_mutex_t ready_m; 194 | struct buffer_bucket *output_target; 195 | pthread_mutex_t rack_mutex; 196 | int16_t rack_buffer[MAXIMUM_BUF_LENGTH]; 197 | long rack_buffer_pos; 198 | }; 199 | 200 | struct buffer_bucket 201 | { 202 | int16_t *buf; 203 | int len; 204 | pthread_rwlock_t rw; 205 | pthread_cond_t ready; 206 | pthread_mutex_t ready_m; 207 | pthread_mutex_t trycond_m; 208 | int trycond; 209 | }; 210 | 211 | struct output_state 212 | { 213 | int exit_flag; 214 | pthread_t thread; 215 | FILE *file; 216 | char *filename; 217 | struct buffer_bucket results[2]; 218 | int rate; 219 | int wav_format; 220 | int padded; 221 | int lrmix; 222 | }; 223 | 224 | struct controller_state 225 | { 226 | int exit_flag; 227 | pthread_t thread; 228 | uint32_t freqs[FREQUENCIES_LIMIT]; 229 | int freq_len; 230 | int freq_now; 231 | int edge; 232 | int wb_mode; 233 | pthread_cond_t hop; 234 | pthread_mutex_t hop_m; 235 | }; 236 | 237 | // multiple of these, eventually 238 | struct dongle_state dongle; 239 | struct demod_state demod; 240 | struct demod_state demod2; 241 | struct output_state output; 242 | struct controller_state controller; 243 | 244 | void usage(void) 245 | { 246 | fprintf(stderr, 247 | "rtl_fm, a simple narrow band FM demodulator for RTL2832 based DVB-T receivers\n\n" 248 | "Use:\trtl_fm -f freq [-options] [filename]\n" 249 | "\t-f frequency_to_tune_to [Hz]\n" 250 | "\t use multiple -f for scanning (requires squelch)\n" 251 | "\t ranges supported, -f 118M:137M:25k\n" 252 | "\t[-M modulation (default: fm)]\n" 253 | "\t fm, wbfm, raw, am, usb, lsb\n" 254 | "\t wbfm == -M fm -s 170k -o 4 -A fast -r 32k -l 0 -E deemp\n" 255 | "\t raw mode outputs 2x16 bit IQ pairs\n" 256 | "\t[-s sample_rate (default: 24k)]\n" 257 | "\t[-d device_index (default: 0)]\n" 258 | "\t[-g tuner_gain (default: automatic)]\n" 259 | "\t[-l squelch_level (default: 0/off)]\n" 260 | //"\t for fm squelch is inverted\n" 261 | //"\t[-o oversampling (default: 1, 4 recommended)]\n" 262 | "\t[-p ppm_error (default: 0)]\n" 263 | "\t[-E enable_option (default: none)]\n" 264 | "\t use multiple -E to enable multiple options\n" 265 | "\t edge: enable lower edge tuning\n" 266 | "\t no-dc: disable dc blocking filter\n" 267 | "\t deemp: enable de-emphasis filter\n" 268 | "\t swagc: enable software agc (only for AM modes)\n" 269 | "\t swagc-aggressive: enable aggressive software agc (only for AM modes)\n" 270 | "\t direct: enable direct sampling\n" 271 | "\t no-mod: enable no-mod direct sampling\n" 272 | "\t offset: enable offset tuning\n" 273 | "\t wav: generate WAV header\n" 274 | "\t pad: pad output gaps with zeros\n" 275 | "\t lrmix: one channel goes to left audio, one to right (broken)\n" 276 | "\t remember to enable stereo (-c 2) in sox\n" 277 | "\tfilename ('-' means stdout)\n" 278 | "\t omitting the filename also uses stdout\n\n" 279 | "Experimental options:\n" 280 | "\t[-r resample_rate (default: none / same as -s)]\n" 281 | "\t[-t squelch_delay (default: 10)]\n" 282 | "\t +values will mute/scan, -values will exit\n" 283 | "\t[-F fir_size (default: off)]\n" 284 | "\t enables low-leakage downsample filter\n" 285 | "\t size can be 0 or 9. 0 has bad roll off\n" 286 | "\t[-A std/fast/lut/ale choose atan math (default: std)]\n" 287 | //"\t[-C clip_path (default: off)\n" 288 | //"\t (create time stamped raw clips, requires squelch)\n" 289 | //"\t (path must have '\%s' and will expand to date_time_freq)\n" 290 | //"\t[-H hop_fifo (default: off)\n" 291 | //"\t (fifo will contain the active frequency)\n" 292 | "\n" 293 | "Produces signed 16 bit ints, use Sox or aplay to hear them.\n" 294 | "\trtl_fm ... | play -t raw -r 24k -es -b 16 -c 1 -V1 -\n" 295 | "\t | aplay -r 24k -f S16_LE -t raw -c 1\n" 296 | "\t -M wbfm | play -r 32k ... \n" 297 | "\t -E wav | play -t wav - \n" 298 | "\t -s 22050 | multimon -t raw /dev/stdin\n\n"); 299 | exit(1); 300 | } 301 | 302 | #ifdef _WIN32 303 | BOOL WINAPI 304 | sighandler(int signum) 305 | { 306 | if (CTRL_C_EVENT == signum) { 307 | fprintf(stderr, "Signal caught, exiting!\n"); 308 | do_exit = 1; 309 | rtlsdr_cancel_async(dongle.dev); 310 | return TRUE; 311 | } 312 | return FALSE; 313 | } 314 | #else 315 | static void sighandler(int signum) 316 | { 317 | fprintf(stderr, "Signal caught, exiting!\n"); 318 | do_exit = 1; 319 | rtlsdr_cancel_async(dongle.dev); 320 | } 321 | #endif 322 | 323 | /* more cond dumbness */ 324 | #define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) 325 | #define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) 326 | 327 | /* {length, coef, coef, coef} and scaled by 2^15 328 | for now, only length 9, optimal way to get +85% bandwidth */ 329 | #define CIC_TABLE_MAX 10 330 | int cic_9_tables[][10] = { 331 | {0,}, 332 | {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156}, 333 | {9, -128, -568, 5593, -24125, 74126, -24125, 5593, -568, -128}, 334 | {9, -129, -639, 6187, -26281, 77511, -26281, 6187, -639, -129}, 335 | {9, -122, -612, 6082, -26353, 77818, -26353, 6082, -612, -122}, 336 | {9, -120, -602, 6015, -26269, 77757, -26269, 6015, -602, -120}, 337 | {9, -120, -582, 5951, -26128, 77542, -26128, 5951, -582, -120}, 338 | {9, -119, -580, 5931, -26094, 77505, -26094, 5931, -580, -119}, 339 | {9, -119, -578, 5921, -26077, 77484, -26077, 5921, -578, -119}, 340 | {9, -119, -577, 5917, -26067, 77473, -26067, 5917, -577, -119}, 341 | {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199}, 342 | }; 343 | 344 | #if defined(_MSC_VER) && _MSC_VER < 1800 345 | double log2(double n) 346 | { 347 | return log(n) / log(2.0); 348 | } 349 | #endif 350 | 351 | void rotate_90(unsigned char *buf, uint32_t len) 352 | /* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j 353 | or [0, 1, -3, 2, -4, -5, 7, -6] */ 354 | { 355 | uint32_t i; 356 | unsigned char tmp; 357 | for (i=0; iangle) < 2*M_PI/max_length) { 379 | fprintf(stderr, "angle too small or array too short\n"); 380 | return 1; 381 | } 382 | ts->i = 0; 383 | ts->sincos = NULL; 384 | if (ts->len) { 385 | max_length = ts->len; 386 | ts->sincos = malloc(max_length * sizeof(int16_t)); 387 | } 388 | a = 0.0; 389 | err = 0.0; 390 | best_i = 0; 391 | best_360 = 4.0; 392 | for (i=0; i < max_length; i+=2) { 393 | s = (int)round(sin(a + err) * (1<<14)); 394 | c = (int)round(cos(a + err) * (1<<14)); 395 | a2 = atan2(s, c); 396 | err = fmod(a, 2*M_PI) - a2; 397 | a += ts->angle; 398 | while (a > M_PI) { 399 | a -= 2*M_PI;} 400 | while (a < -M_PI) { 401 | a += 2*M_PI;} 402 | if (i != 0 && fabs(a) < best_360) { 403 | best_i = i; 404 | best_360 = fabs(a); 405 | } 406 | if (i != 0 && fabs(a) < 0.0000001) { 407 | break;} 408 | if (ts->sincos) { 409 | ts->sincos[i] = s; 410 | ts->sincos[i+1] = c; 411 | //fprintf(stderr, "%i %i %i\n", i, s, c); 412 | } 413 | } 414 | if (ts->sincos) { 415 | return 0; 416 | } 417 | ts->len = best_i + 2; 418 | return translate_init(ts); 419 | } 420 | 421 | void translate(struct demod_state *d) 422 | { 423 | int i, len, sc_i, sc_len; 424 | int32_t tmp, ar, aj, br, bj; 425 | int16_t *buf = d->lowpassed; 426 | int16_t *sincos = d->rotate.sincos; 427 | len = d->lp_len; 428 | sc_i = d->rotate.i; 429 | sc_len = d->rotate.len; 430 | for (i=0; i> 14); 438 | tmp = aj*br + ar*bj; 439 | buf[i+1] = (int16_t)(tmp >> 14); 440 | } 441 | d->rotate.i = sc_i; 442 | } 443 | 444 | void low_pass(struct demod_state *d) 445 | /* simple square window FIR */ 446 | { 447 | int i=0, i2=0; 448 | while (i < d->lp_len) { 449 | d->now_r += d->lowpassed[i]; 450 | d->now_j += d->lowpassed[i+1]; 451 | i += 2; 452 | d->prev_index++; 453 | if (d->prev_index < d->downsample) { 454 | continue; 455 | } 456 | d->lowpassed[i2] = d->now_r; // * d->output_scale; 457 | d->lowpassed[i2+1] = d->now_j; // * d->output_scale; 458 | d->prev_index = 0; 459 | d->now_r = 0; 460 | d->now_j = 0; 461 | i2 += 2; 462 | } 463 | d->lp_len = i2; 464 | } 465 | 466 | int low_pass_simple(int16_t *signal2, int len, int step) 467 | // no wrap around, length must be multiple of step 468 | { 469 | int i, i2, sum; 470 | for(i=0; i < len; i+=step) { 471 | sum = 0; 472 | for(i2=0; i2lowpassed; 487 | int i=0, i2=0; 488 | int fast = (int)s->rate_out; 489 | int slow = s->rate_out2; 490 | while (i < s->lp_len) { 491 | s->now_lpr += lp[i]; 492 | i++; 493 | s->prev_lpr_index += slow; 494 | if (s->prev_lpr_index < fast) { 495 | continue; 496 | } 497 | lp[i2] = (int16_t)(s->now_lpr / (fast/slow)); 498 | s->prev_lpr_index -= fast; 499 | s->now_lpr = 0; 500 | i2 += 1; 501 | } 502 | s->lp_len = i2; 503 | } 504 | 505 | void fifth_order(int16_t *data, int length, int16_t *hist) 506 | /* for half of interleaved data */ 507 | { 508 | int i; 509 | int16_t a, b, c, d, e, f; 510 | a = hist[1]; 511 | b = hist[2]; 512 | c = hist[3]; 513 | d = hist[4]; 514 | e = hist[5]; 515 | f = data[0]; 516 | /* a downsample should improve resolution, so don't fully shift */ 517 | data[0] = (a + (b+e)*5 + (c+d)*10 + f) >> 4; 518 | for (i=4; i> 4; 526 | } 527 | /* archive */ 528 | hist[0] = a; 529 | hist[1] = b; 530 | hist[2] = c; 531 | hist[3] = d; 532 | hist[4] = e; 533 | hist[5] = f; 534 | } 535 | 536 | void generic_fir(int16_t *data, int length, int *fir, int16_t *hist) 537 | /* Okay, not at all generic. Assumes length 9, fix that eventually. */ 538 | { 539 | int d, temp, sum; 540 | for (d=0; d> 15 ; 549 | hist[0] = hist[1]; 550 | hist[1] = hist[2]; 551 | hist[2] = hist[3]; 552 | hist[3] = hist[4]; 553 | hist[4] = hist[5]; 554 | hist[5] = hist[6]; 555 | hist[6] = hist[7]; 556 | hist[7] = hist[8]; 557 | hist[8] = temp; 558 | } 559 | } 560 | 561 | /* define our own complex math ops 562 | because ARMv5 has no hardware float */ 563 | 564 | void multiply(int ar, int aj, int br, int bj, int *cr, int *cj) 565 | { 566 | *cr = ar*br - aj*bj; 567 | *cj = aj*br + ar*bj; 568 | } 569 | 570 | int polar_discriminant(int ar, int aj, int br, int bj) 571 | { 572 | int cr, cj; 573 | double angle; 574 | multiply(ar, aj, br, -bj, &cr, &cj); 575 | angle = atan2((double)cj, (double)cr); 576 | return (int)(angle / M_PI * (1<<14)); 577 | } 578 | 579 | int fast_atan2(int y, int x) 580 | /* pre scaled for int16 */ 581 | { 582 | int yabs, angle; 583 | int pi4=(1<<12), pi34=3*(1<<12); // note pi = 1<<14 584 | if (x==0 && y==0) { 585 | return 0; 586 | } 587 | yabs = y; 588 | if (yabs < 0) { 589 | yabs = -yabs; 590 | } 591 | if (x >= 0) { 592 | angle = pi4 - pi4 * (x-yabs) / (x+yabs); 593 | } else { 594 | angle = pi34 - pi4 * (x+yabs) / (yabs-x); 595 | } 596 | if (y < 0) { 597 | return -angle; 598 | } 599 | return angle; 600 | } 601 | 602 | int polar_disc_fast(int ar, int aj, int br, int bj) 603 | { 604 | int cr, cj; 605 | multiply(ar, aj, br, -bj, &cr, &cj); 606 | return fast_atan2(cj, cr); 607 | } 608 | 609 | int atan_lut_init(void) 610 | { 611 | int i = 0; 612 | 613 | atan_lut = malloc(atan_lut_size * sizeof(int)); 614 | 615 | for (i = 0; i < atan_lut_size; i++) { 616 | atan_lut[i] = (int) (atan((double) i / (1< 0) 633 | {return 1 << 13;} 634 | if (cr == 0 && cj < 0) 635 | {return -(1 << 13);} 636 | if (cj == 0 && cr > 0) 637 | {return 0;} 638 | if (cj == 0 && cr < 0) 639 | {return 1 << 14;} 640 | } 641 | 642 | /* real range -32768 - 32768 use 64x range -> absolute maximum: 2097152 */ 643 | x = (cj << atan_lut_coef) / cr; 644 | x_abs = abs(x); 645 | 646 | if (x_abs >= atan_lut_size) { 647 | /* we can use linear range, but it is not necessary */ 648 | return (cj > 0) ? 1<<13 : -1<<13; 649 | } 650 | 651 | if (x > 0) { 652 | return (cj > 0) ? atan_lut[x] : atan_lut[x] - (1<<14); 653 | } else { 654 | return (cj > 0) ? (1<<14) - atan_lut[-x] : -atan_lut[-x]; 655 | } 656 | 657 | return 0; 658 | } 659 | 660 | int esbensen(int ar, int aj, int br, int bj) 661 | /* 662 | input signal: s(t) = a*exp(-i*w*t+p) 663 | a = amplitude, w = angular freq, p = phase difference 664 | solve w 665 | s' = -i(w)*a*exp(-i*w*t+p) 666 | s'*conj(s) = -i*w*a*a 667 | s'*conj(s) / |s|^2 = -i*w 668 | */ 669 | { 670 | int cj, dr, dj; 671 | int scaled_pi = 2608; /* 1<<14 / (2*pi) */ 672 | dr = (br - ar) * 2; 673 | dj = (bj - aj) * 2; 674 | cj = bj*dr - br*dj; /* imag(ds*conj(s)) */ 675 | return (scaled_pi * cj / (ar*ar+aj*aj+1)); 676 | } 677 | 678 | void fm_demod(struct demod_state *fm) 679 | { 680 | int i, pcm = 0; 681 | int16_t *lp = fm->lowpassed; 682 | int16_t pr = fm->pre_r; 683 | int16_t pj = fm->pre_j; 684 | for (i = 0; i < (fm->lp_len-1); i += 2) { 685 | switch (fm->custom_atan) { 686 | case 0: 687 | pcm = polar_discriminant(lp[i], lp[i+1], pr, pj); 688 | break; 689 | case 1: 690 | pcm = polar_disc_fast(lp[i], lp[i+1], pr, pj); 691 | break; 692 | case 2: 693 | pcm = polar_disc_lut(lp[i], lp[i+1], pr, pj); 694 | break; 695 | case 3: 696 | pcm = esbensen(lp[i], lp[i+1], pr, pj); 697 | break; 698 | } 699 | pr = lp[i]; 700 | pj = lp[i+1]; 701 | fm->lowpassed[i/2] = (int16_t)pcm; 702 | } 703 | fm->pre_r = pr; 704 | fm->pre_j = pj; 705 | fm->lp_len = fm->lp_len / 2; 706 | } 707 | 708 | void am_demod(struct demod_state *fm) 709 | // todo, fix this extreme laziness 710 | { 711 | int32_t i, pcm; 712 | int16_t *lp = fm->lowpassed; 713 | for (i = 0; i < fm->lp_len; i += 2) { 714 | // hypot uses floats but won't overflow 715 | //r[i/2] = (int16_t)hypot(lp[i], lp[i+1]); 716 | pcm = lp[i] * lp[i]; 717 | pcm += lp[i+1] * lp[i+1]; 718 | lp[i/2] = (int16_t)sqrt(pcm) * fm->output_scale; 719 | } 720 | fm->lp_len = fm->lp_len / 2; 721 | // lowpass? (3khz) 722 | } 723 | 724 | void usb_demod(struct demod_state *fm) 725 | { 726 | int i, pcm; 727 | int16_t *lp = fm->lowpassed; 728 | for (i = 0; i < fm->lp_len; i += 2) { 729 | pcm = lp[i] + lp[i+1]; 730 | lp[i/2] = (int16_t)pcm * fm->output_scale; 731 | } 732 | fm->lp_len = fm->lp_len / 2; 733 | } 734 | 735 | void lsb_demod(struct demod_state *fm) 736 | { 737 | int i, pcm; 738 | int16_t *lp = fm->lowpassed; 739 | for (i = 0; i < fm->lp_len; i += 2) { 740 | pcm = lp[i] - lp[i+1]; 741 | lp[i/2] = (int16_t)pcm * fm->output_scale; 742 | } 743 | fm->lp_len = fm->lp_len / 2; 744 | } 745 | 746 | void raw_demod(struct demod_state *fm) 747 | { 748 | return; 749 | } 750 | 751 | void deemph_filter(struct demod_state *fm) 752 | { 753 | static int avg; // cheating, not threadsafe 754 | int i, d; 755 | int16_t *lp = fm->lowpassed; 756 | // de-emph IIR 757 | // avg = avg * (1 - alpha) + sample * alpha; 758 | for (i = 0; i < fm->lp_len; i++) { 759 | d = lp[i] - avg; 760 | if (d > 0) { 761 | avg += (d + fm->deemph_a/2) / fm->deemph_a; 762 | } else { 763 | avg += (d - fm->deemph_a/2) / fm->deemph_a; 764 | } 765 | lp[i] = (int16_t)avg; 766 | } 767 | } 768 | 769 | void dc_block_filter(struct demod_state *fm) 770 | { 771 | int i, avg; 772 | int64_t sum = 0; 773 | int16_t *lp = fm->lowpassed; 774 | for (i=0; i < fm->lp_len; i++) { 775 | sum += lp[i]; 776 | } 777 | avg = (int)(sum / fm->lp_len); 778 | avg = (avg + fm->dc_avg * 9) / 10; 779 | for (i=0; i < fm->lp_len; i++) { 780 | lp[i] -= avg; 781 | } 782 | fm->dc_avg = avg; 783 | } 784 | 785 | int mad(int16_t *samples, int len, int step) 786 | /* mean average deviation */ 787 | { 788 | int i=0, sum=0, ave=0; 789 | if (len == 0) 790 | {return 0;} 791 | for (i=0; igain != AUTO_GAIN) { 831 | gain = (double)(dongle->gain) / 10.0; 832 | } 833 | gain = 50.0 - gain; 834 | gain = pow(10.0, gain/20.0); 835 | downsample = 1024.0 / (double)demod->downsample; 836 | linear = linear / gain; 837 | linear = linear / downsample; 838 | return (int)linear + 1; 839 | } 840 | 841 | void software_agc(struct demod_state *d) 842 | { 843 | int i = 0; 844 | int peaked = 0; 845 | int32_t output = 0; 846 | int abs_output = 0; 847 | struct agc_state *agc = d->agc; 848 | int16_t *lp = d->lowpassed; 849 | int attack_step = agc->attack_step; 850 | int aggressive = agc_aggressive == d->agc_mode; 851 | float peak_factor = 1.0; 852 | 853 | for (i=0; i < d->lp_len; i++) { 854 | output = (int32_t)lp[i] * agc->gain_num + agc->error; 855 | agc->error = output % agc->gain_den; 856 | output /= agc->gain_den; 857 | abs_output = abs(output); 858 | peaked = abs_output > agc->peak_target; 859 | 860 | if (peaked && aggressive && attack_step <= 1) { 861 | peak_factor = fmin(5.0, (float) abs_output / agc->peak_target); 862 | attack_step = (int) (pow(agc->attack_step - peak_factor, peak_factor) * (176 + 3 * peak_factor)); 863 | } 864 | 865 | if (peaked) { 866 | agc->gain_num -= attack_step; 867 | if (aggressive) { 868 | attack_step = (int) (attack_step / 1.2); 869 | } 870 | } else { 871 | agc->gain_num += agc->decay_step; 872 | } 873 | 874 | if (agc->gain_num < agc->gain_den) { 875 | agc->gain_num = agc->gain_den;} 876 | if (agc->gain_num > agc->gain_max) { 877 | agc->gain_num = agc->gain_max;} 878 | 879 | if (output >= (1<<15)) { 880 | output = (1<<15) - 1;} 881 | if (output < -(1<<15)) { 882 | output = -(1<<15) + 1;} 883 | 884 | lp[i] = (int16_t)output; 885 | } 886 | } 887 | 888 | void full_demod(struct demod_state *d) 889 | { 890 | int i, ds_p; 891 | int do_squelch = 0; 892 | int sr = 0; 893 | if(d->rotate_enable) { 894 | translate(d); 895 | } 896 | ds_p = d->downsample_passes; 897 | if (ds_p) { 898 | for (i=0; i < ds_p; i++) { 899 | fifth_order(d->lowpassed, (d->lp_len >> i), d->lp_i_hist[i]); 900 | fifth_order(d->lowpassed+1, (d->lp_len >> i) - 1, d->lp_q_hist[i]); 901 | } 902 | d->lp_len = d->lp_len >> ds_p; 903 | /* droop compensation */ 904 | if (d->comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) { 905 | generic_fir(d->lowpassed, d->lp_len, 906 | cic_9_tables[ds_p], d->droop_i_hist); 907 | generic_fir(d->lowpassed+1, d->lp_len-1, 908 | cic_9_tables[ds_p], d->droop_q_hist); 909 | } 910 | } else { 911 | low_pass(d); 912 | } 913 | /* power squelch */ 914 | if (d->squelch_level) { 915 | sr = rms(d->lowpassed, d->lp_len, 1); 916 | if (sr < d->squelch_level) { 917 | do_squelch = 1;} 918 | } 919 | if (do_squelch) { 920 | d->squelch_hits++; 921 | for (i=0; ilp_len; i++) { 922 | d->lowpassed[i] = 0; 923 | } 924 | } else { 925 | d->squelch_hits = 0; 926 | } 927 | if (d->squelch_level && d->squelch_hits > d->conseq_squelch) { 928 | d->agc->gain_num = d->agc->gain_den; 929 | } 930 | d->mode_demod(d); /* lowpassed -> lowpassed */ 931 | if (d->mode_demod == &raw_demod) { 932 | return;} 933 | if (d->dc_block) { 934 | dc_block_filter(d);} 935 | if (d->agc_mode != agc_off) { 936 | software_agc(d);} 937 | /* todo, fm noise squelch */ 938 | // use nicer filter here too? 939 | if (d->post_downsample > 1) { 940 | d->lp_len = low_pass_simple(d->lowpassed, d->lp_len, d->post_downsample);} 941 | if (d->deemph) { 942 | deemph_filter(d);} 943 | if (d->rate_out2 > 0) { 944 | low_pass_real(d); 945 | //arbitrary_resample(d->lowpassed, d->lowpassed, d->lp_len, d->lp_len * d->rate_out2 / d->rate_out); 946 | } 947 | } 948 | 949 | int16_t* mark_shared_buffer(void) 950 | { 951 | int i = 0; 952 | for (i=0; itargets[0]; 987 | d2 = s->targets[1]; 988 | if (s->mute) { 989 | for (i=0; imute; i++) { 990 | buf[i] = 127;} 991 | s->mute = 0; 992 | } 993 | if (s->pre_rotate) { 994 | rotate_90(buf, len);} 995 | for (i=0; i<(int)len; i++) { 996 | s->buf16[i] = (int16_t)buf[i] - 127;} 997 | if (d2 != NULL) { 998 | pthread_rwlock_wrlock(&d2->rw); 999 | d2->lowpassed = mark_shared_buffer(); 1000 | memcpy(d2->lowpassed, s->buf16, 2*len); 1001 | d2->lp_len = len; 1002 | pthread_rwlock_unlock(&d2->rw); 1003 | safe_cond_signal(&d2->ready, &d2->ready_m); 1004 | } 1005 | pthread_rwlock_wrlock(&d->rw); 1006 | d->lowpassed = s->buf16; 1007 | d->lp_len = len; 1008 | pthread_rwlock_unlock(&d->rw); 1009 | safe_cond_signal(&d->ready, &d->ready_m); 1010 | s->buf16 = mark_shared_buffer(); 1011 | } 1012 | 1013 | static void *dongle_thread_fn(void *arg) 1014 | { 1015 | struct dongle_state *s = arg; 1016 | rtlsdr_read_async(s->dev, rtlsdr_callback, s, 0, s->buf_len); 1017 | return 0; 1018 | } 1019 | 1020 | static void *demod_thread_fn(void *arg) 1021 | { 1022 | struct demod_state *d = arg; 1023 | struct buffer_bucket *o = d->output_target; 1024 | while (!do_exit) { 1025 | safe_cond_wait(&d->ready, &d->ready_m); 1026 | pthread_rwlock_wrlock(&d->rw); 1027 | full_demod(d); 1028 | pthread_rwlock_unlock(&d->rw); 1029 | if (d->exit_flag) { 1030 | do_exit = 1; 1031 | } 1032 | pthread_rwlock_wrlock(&o->rw); 1033 | o->buf = d->lowpassed; 1034 | o->len = d->lp_len; 1035 | { 1036 | pthread_mutex_lock(&d->rack_mutex); 1037 | long size =sizeof(d->rack_buffer[0]); 1038 | if(d->rack_buffer_pos + d->lp_len > MAXIMUM_BUF_LENGTH/size) { 1039 | printf("📻 Rack buffer overrun\n"); 1040 | } else { 1041 | void* dst = &d->rack_buffer; 1042 | dst += size * d->rack_buffer_pos; 1043 | //printf("📻 Rack memcpy %ld bytes at sample offset %ld -- to %ld %ld\n", d->lp_len*size, d->rack_buffer_pos, &d->rack_buffer, dst); 1044 | memcpy(dst, d->lowpassed, d->lp_len*size ); 1045 | d->rack_buffer_pos += d->lp_len; 1046 | } 1047 | pthread_mutex_unlock(&d->rack_mutex); 1048 | } 1049 | pthread_rwlock_unlock(&o->rw); 1050 | if (controller.freq_len > 1 && d->squelch_level && \ 1051 | d->squelch_hits > d->conseq_squelch) { 1052 | unmark_shared_buffer(d->lowpassed); 1053 | d->squelch_hits = d->conseq_squelch + 1; /* hair trigger */ 1054 | safe_cond_signal(&controller.hop, &controller.hop_m); 1055 | continue; 1056 | } 1057 | safe_cond_signal(&o->ready, &o->ready_m); 1058 | pthread_mutex_lock(&o->trycond_m); 1059 | o->trycond = 0; 1060 | pthread_mutex_unlock(&o->trycond_m); 1061 | } 1062 | return 0; 1063 | } 1064 | 1065 | #ifndef _WIN32 1066 | static int get_nanotime(struct timespec *ts) 1067 | { 1068 | int rv = ENOSYS; 1069 | #ifdef __unix__ 1070 | rv = clock_gettime(CLOCK_MONOTONIC, ts); 1071 | #elif __APPLE__ 1072 | struct timeval tv; 1073 | 1074 | rv = gettimeofday(&tv, NULL); 1075 | ts->tv_sec = tv.tv_sec; 1076 | ts->tv_nsec = tv.tv_usec * 1000L; 1077 | #endif 1078 | return rv; 1079 | } 1080 | #endif 1081 | 1082 | static void *output_thread_fn(void *arg) 1083 | { 1084 | int j, r = 0; 1085 | struct output_state *s = arg; 1086 | struct buffer_bucket *b0 = &s->results[0]; 1087 | struct buffer_bucket *b1 = &s->results[1]; 1088 | int64_t i, duration, samples = 0LL, samples_now; 1089 | #ifdef _WIN32 1090 | LARGE_INTEGER perfFrequency; 1091 | LARGE_INTEGER start_time; 1092 | LARGE_INTEGER now_time; 1093 | 1094 | QueryPerformanceFrequency(&perfFrequency); 1095 | QueryPerformanceCounter(&start_time); 1096 | #else 1097 | struct timespec start_time; 1098 | struct timespec now_time; 1099 | 1100 | get_nanotime(&start_time); 1101 | #endif 1102 | while (!do_exit) { 1103 | if (s->lrmix) { 1104 | safe_cond_wait(&b0->ready, &b0->ready_m); 1105 | pthread_rwlock_rdlock(&b0->rw); 1106 | safe_cond_wait(&b1->ready, &b1->ready_m); 1107 | pthread_rwlock_rdlock(&b1->rw); 1108 | for(j=0; j < b0->len; j++) { 1109 | fwrite(b0->buf+j, 2, 1, s->file); 1110 | fwrite(b1->buf+j, 2, 1, s->file); 1111 | } 1112 | unmark_shared_buffer(b1->buf); 1113 | pthread_rwlock_unlock(&b1->rw); 1114 | unmark_shared_buffer(b0->buf); 1115 | pthread_rwlock_unlock(&b0->rw); 1116 | continue; 1117 | } 1118 | if (!s->padded) { 1119 | safe_cond_wait(&b0->ready, &b0->ready_m); 1120 | pthread_rwlock_rdlock(&b0->rw); 1121 | fwrite(b0->buf, 2, b0->len, s->file); 1122 | unmark_shared_buffer(b0->buf); 1123 | pthread_rwlock_unlock(&b0->rw); 1124 | continue; 1125 | } 1126 | 1127 | /* padding requires output at constant rate */ 1128 | /* pthread_cond_timedwait is terrible, roll our own trycond */ 1129 | usleep(2000); 1130 | pthread_mutex_lock(&b0->trycond_m); 1131 | r = b0->trycond; 1132 | b0->trycond = 1; 1133 | pthread_mutex_unlock(&b0->trycond_m); 1134 | if (r == 0) { 1135 | pthread_rwlock_rdlock(&b0->rw); 1136 | fwrite(b0->buf, 2, b0->len, s->file); 1137 | unmark_shared_buffer(b0->buf); 1138 | samples += (int64_t)b0->len; 1139 | pthread_rwlock_unlock(&b0->rw); 1140 | continue; 1141 | } 1142 | #ifdef _WIN32 1143 | QueryPerformanceCounter(&now_time); 1144 | duration = now_time.QuadPart - start_time.QuadPart; 1145 | samples_now = (duration * s->rate) / perfFrequency.QuadPart; 1146 | #else 1147 | get_nanotime(&now_time); 1148 | duration = now_time.tv_sec - start_time.tv_sec; 1149 | duration *= 1000000000L; 1150 | duration += (now_time.tv_nsec - start_time.tv_nsec); 1151 | samples_now = (duration * s->rate) / 1000000000UL; 1152 | #endif 1153 | if (samples_now < samples) { 1154 | continue;} 1155 | for (i=samples; ifile); 1157 | fputc(0, s->file); 1158 | } 1159 | samples = samples_now; 1160 | } 1161 | return 0; 1162 | } 1163 | 1164 | static void optimal_settings(int freq, int rate) 1165 | { 1166 | // giant ball of hacks 1167 | // seems unable to do a single pass, 2:1 1168 | int capture_freq, capture_rate; 1169 | struct dongle_state *d = &dongle; 1170 | struct demod_state *dm = &demod; 1171 | struct controller_state *cs = &controller; 1172 | dm->downsample = (MINIMUM_RATE / dm->rate_in) + 1; 1173 | if (dm->downsample_passes) { 1174 | dm->downsample_passes = (int)log2(dm->downsample) + 1; 1175 | dm->downsample = 1 << dm->downsample_passes; 1176 | } 1177 | capture_freq = freq; 1178 | capture_rate = dm->downsample * dm->rate_in; 1179 | if (d->pre_rotate) { 1180 | capture_freq = freq + capture_rate/4;} 1181 | capture_freq += cs->edge * dm->rate_in / 2; 1182 | dm->output_scale = (1<<15) / (128 * dm->downsample); 1183 | if (dm->output_scale < 1) { 1184 | dm->output_scale = 1;} 1185 | if (dm->mode_demod == &fm_demod) { 1186 | dm->output_scale = 1;} 1187 | d->freq = (uint32_t)capture_freq; 1188 | d->rate = (uint32_t)capture_rate; 1189 | //d->pre_rotate = 0; 1190 | //demod.rotate_enable = 1; 1191 | //demod.rotate.angle = -0.25 * 2 * M_PI; 1192 | //translate_init(&demod.rotate); 1193 | } 1194 | 1195 | void clone_demod(struct demod_state *d2, struct demod_state *d1) 1196 | /* copy from d1 to d2 */ 1197 | { 1198 | d2->rate_in = d1->rate_in; 1199 | d2->rate_out = d1->rate_out; 1200 | d2->rate_out2 = d1->rate_out2; 1201 | d2->downsample = d1->downsample; 1202 | d2->downsample_passes = d1->downsample_passes; 1203 | d2->post_downsample = d1->post_downsample; 1204 | d2->output_scale = d1->output_scale; 1205 | d2->squelch_level = d1->squelch_level; 1206 | d2->conseq_squelch = d1->conseq_squelch; 1207 | d2->squelch_hits = d1->squelch_hits; 1208 | d2->terminate_on_squelch = d1->terminate_on_squelch; 1209 | d2->comp_fir_size = d1->comp_fir_size; 1210 | d2->custom_atan = d1->custom_atan; 1211 | d2->deemph = d1->deemph; 1212 | d2->deemph_a = d1->deemph_a; 1213 | d2->dc_block = d1->dc_block; 1214 | d2->rotate_enable = d1->rotate_enable; 1215 | d2->agc_mode = d1->agc_mode; 1216 | d2->mode_demod = d1->mode_demod; 1217 | } 1218 | 1219 | void optimal_lrmix(void) 1220 | { 1221 | double angle1, angle2; 1222 | uint32_t freq, freq1, freq2, bw, dongle_bw, mr; 1223 | if (controller.freq_len != 2) { 1224 | fprintf(stderr, "error: lrmix requires two frequencies\n"); 1225 | do_exit = 1; 1226 | exit(1); 1227 | } 1228 | if (output.padded) { 1229 | fprintf(stderr, "warning: lrmix does not support padding\n"); 1230 | } 1231 | freq1 = controller.freqs[0]; 1232 | freq2 = controller.freqs[1]; 1233 | bw = demod.rate_out; 1234 | freq = freq1 / 2 + freq2 / 2 + bw; 1235 | mr = (uint32_t)abs((int64_t)freq1 - (int64_t)freq2) + bw; 1236 | if (mr > MINIMUM_RATE) { 1237 | MINIMUM_RATE = mr;} 1238 | dongle.pre_rotate = 0; 1239 | optimal_settings(freq, bw); 1240 | output.padded = 0; 1241 | clone_demod(&demod2, &demod); 1242 | //demod2 = demod; 1243 | demod2.output_target = &output.results[1]; 1244 | dongle.targets[1] = &demod2; 1245 | dongle_bw = dongle.rate; 1246 | if (dongle_bw > MAXIMUM_RATE) { 1247 | fprintf(stderr, "error: unable to find optimal settings\n"); 1248 | do_exit = 1; 1249 | exit(1); 1250 | } 1251 | angle1 = ((double)freq1 - (double)freq) / (double)dongle_bw; 1252 | demod.rotate.angle = angle1 * 2 * M_PI; 1253 | angle2 = ((double)freq2 - (double)freq) / (double)dongle_bw; 1254 | demod2.rotate.angle = angle2 * 2 * M_PI; 1255 | translate_init(&demod.rotate); 1256 | translate_init(&demod2.rotate); 1257 | //fprintf(stderr, "a1 %f, a2 %f\n", angle1, angle2); 1258 | } 1259 | 1260 | static void *controller_thread_fn(void *arg) 1261 | { 1262 | // thoughts for multiple dongles 1263 | // might be no good using a controller thread if retune/rate blocks 1264 | int i; 1265 | struct controller_state *s = arg; 1266 | 1267 | if (s->wb_mode) { 1268 | for (i=0; i < s->freq_len; i++) { 1269 | s->freqs[i] += 16000;} 1270 | } 1271 | 1272 | /* set up primary channel */ 1273 | optimal_settings(s->freqs[0], demod.rate_in); 1274 | demod.squelch_level = squelch_to_rms(demod.squelch_level, &dongle, &demod); 1275 | if (dongle.direct_sampling) { 1276 | verbose_direct_sampling(dongle.dev, dongle.direct_sampling);} 1277 | if (dongle.offset_tuning) { 1278 | verbose_offset_tuning(dongle.dev);} 1279 | 1280 | /* set up lrmix */ 1281 | if (output.lrmix) { 1282 | optimal_lrmix(); 1283 | } 1284 | 1285 | /* Set the frequency */ 1286 | verbose_set_frequency(dongle.dev, dongle.freq); 1287 | fprintf(stderr, "Oversampling input by: %ix.\n", demod.downsample); 1288 | fprintf(stderr, "Oversampling output by: %ix.\n", demod.post_downsample); 1289 | fprintf(stderr, "Buffer size: %0.2fms\n", 1290 | 1000 * 0.5 * (float)ACTUAL_BUF_LENGTH / (float)dongle.rate); 1291 | 1292 | /* Set the sample rate */ 1293 | verbose_set_sample_rate(dongle.dev, dongle.rate); 1294 | fprintf(stderr, "Output at %u Hz.\n", demod.rate_in/demod.post_downsample); 1295 | 1296 | while (!do_exit) { 1297 | safe_cond_wait(&s->hop, &s->hop_m); 1298 | if (s->freq_len <= 1) { 1299 | continue;} 1300 | if (output.lrmix) { 1301 | continue;} 1302 | /* hacky hopping */ 1303 | s->freq_now = (s->freq_now + 1) % s->freq_len; 1304 | struct timeval tod; 1305 | gettimeofday(&tod, NULL); 1306 | //printf("%lu hopping to idx %ld -> %ld\n", tod.tv_usec, s->freq_now, s->freqs[s->freq_now]); 1307 | optimal_settings(s->freqs[s->freq_now], demod.rate_in); 1308 | rtlsdr_set_center_freq(dongle.dev, dongle.freq); 1309 | //rtlsdr_set_center_freq(dongle.dev, s->freqs[s->freq_now]); 1310 | 1311 | //dongle.mute = BUFFER_DUMP; 1312 | } 1313 | return 0; 1314 | } 1315 | 1316 | void frequency_range(struct controller_state *s, char *arg) 1317 | { 1318 | char *start, *stop, *step; 1319 | int i; 1320 | start = arg; 1321 | stop = strchr(start, ':') + 1; 1322 | stop[-1] = '\0'; 1323 | step = strchr(stop, ':') + 1; 1324 | step[-1] = '\0'; 1325 | for(i=(int)atofs(start); i<=(int)atofs(stop); i+=(int)atofs(step)) 1326 | { 1327 | s->freqs[s->freq_len] = (uint32_t)i; 1328 | s->freq_len++; 1329 | if (s->freq_len >= FREQUENCIES_LIMIT) { 1330 | break;} 1331 | } 1332 | stop[-1] = ':'; 1333 | step[-1] = ':'; 1334 | } 1335 | 1336 | void dongle_init(struct dongle_state *s) 1337 | { 1338 | s->rate = DEFAULT_SAMPLE_RATE; 1339 | s->gain = AUTO_GAIN; // tenths of a dB 1340 | s->mute = 0; 1341 | s->direct_sampling = 0; 1342 | s->offset_tuning = 0; 1343 | s->pre_rotate = 1; 1344 | s->targets[0] = &demod; 1345 | s->targets[1] = NULL; 1346 | s->buf16 = mark_shared_buffer(); 1347 | } 1348 | 1349 | void demod_init(struct demod_state *s) 1350 | { 1351 | s->rate_in = DEFAULT_SAMPLE_RATE; 1352 | s->rate_out = DEFAULT_SAMPLE_RATE; 1353 | s->squelch_level = 0; 1354 | s->conseq_squelch = 10; 1355 | s->terminate_on_squelch = 0; 1356 | s->squelch_hits = 11; 1357 | s->downsample_passes = 0; 1358 | s->comp_fir_size = 0; 1359 | s->prev_index = 0; 1360 | s->post_downsample = 1; // once this works, default = 4 1361 | s->custom_atan = 0; 1362 | s->deemph = 0; 1363 | s->agc_mode = agc_off; 1364 | s->rotate_enable = 0; 1365 | s->rotate.len = 0; 1366 | s->rotate.sincos = NULL; 1367 | s->rate_out2 = -1; // flag for disabled 1368 | s->mode_demod = &fm_demod; 1369 | s->pre_j = s->pre_r = s->now_r = s->now_j = 0; 1370 | s->prev_lpr_index = 0; 1371 | s->deemph_a = 0; 1372 | s->now_lpr = 0; 1373 | s->dc_block = 1; 1374 | s->dc_avg = 0; 1375 | pthread_rwlock_init(&s->rw, NULL); 1376 | pthread_cond_init(&s->ready, NULL); 1377 | pthread_mutex_init(&s->ready_m, NULL); 1378 | pthread_mutex_init(&s->rack_mutex, NULL); 1379 | s->rack_buffer_pos = 0; 1380 | s->output_target = &output.results[0]; 1381 | s->lowpassed = NULL; 1382 | } 1383 | 1384 | void demod_cleanup(struct demod_state *s) 1385 | { 1386 | pthread_rwlock_destroy(&s->rw); 1387 | pthread_cond_destroy(&s->ready); 1388 | pthread_mutex_destroy(&s->ready_m); 1389 | pthread_mutex_destroy(&s->rack_mutex); 1390 | } 1391 | 1392 | void output_init(struct output_state *s) 1393 | { 1394 | int i; 1395 | //s->rate = DEFAULT_SAMPLE_RATE; 1396 | for (i=0; i<2; i++) { 1397 | pthread_rwlock_init(&s->results[i].rw, NULL); 1398 | pthread_cond_init(&s->results[i].ready, NULL); 1399 | pthread_mutex_init(&s->results[i].ready_m, NULL); 1400 | pthread_mutex_init(&s->results[i].trycond_m, NULL); 1401 | s->results[i].trycond = 1; 1402 | s->results[i].buf = NULL; 1403 | } 1404 | } 1405 | 1406 | void output_cleanup(struct output_state *s) 1407 | { 1408 | int i; 1409 | for (i=0; i<2; i++) { 1410 | pthread_rwlock_destroy(&s->results[i].rw); 1411 | pthread_cond_destroy(&s->results[i].ready); 1412 | pthread_mutex_destroy(&s->results[i].ready_m); 1413 | pthread_mutex_destroy(&s->results[i].trycond_m); 1414 | } 1415 | } 1416 | 1417 | void controller_init(struct controller_state *s) 1418 | { 1419 | s->freqs[0] = 100000000; 1420 | s->freq_len = 0; 1421 | s->edge = 0; 1422 | s->wb_mode = 0; 1423 | pthread_cond_init(&s->hop, NULL); 1424 | pthread_mutex_init(&s->hop_m, NULL); 1425 | } 1426 | 1427 | void controller_cleanup(struct controller_state *s) 1428 | { 1429 | pthread_cond_destroy(&s->hop); 1430 | pthread_mutex_destroy(&s->hop_m); 1431 | } 1432 | 1433 | void sanity_checks(void) 1434 | { 1435 | if (controller.freq_len == 0) { 1436 | fprintf(stderr, "Please specify a frequency.\n"); 1437 | exit(1); 1438 | } 1439 | 1440 | if (controller.freq_len >= FREQUENCIES_LIMIT) { 1441 | fprintf(stderr, "Too many channels, maximum %i.\n", FREQUENCIES_LIMIT); 1442 | exit(1); 1443 | } 1444 | 1445 | if (!output.lrmix && controller.freq_len > 1 && demod.squelch_level == 0) { 1446 | fprintf(stderr, "Please specify a squelch level. Required for scanning multiple frequencies.\n"); 1447 | exit(1); 1448 | } 1449 | 1450 | if (demod.mode_demod == &raw_demod && output.lrmix) { 1451 | fprintf(stderr, "'raw' is not a supported demodulator for lrmix\n"); 1452 | exit(1); 1453 | } 1454 | 1455 | } 1456 | 1457 | int agc_init(struct demod_state *s) 1458 | { 1459 | struct agc_state *agc; 1460 | 1461 | agc = malloc(sizeof(struct agc_state)); 1462 | s->agc = agc; 1463 | 1464 | agc->gain_den = 1<<15; 1465 | agc->peak_target = 1<<14; 1466 | agc->gain_max = 256 * agc->gain_den; 1467 | agc->gain_num = agc->gain_den; 1468 | agc->decay_step = 1; 1469 | agc->attack_step = 2; 1470 | if (s->agc_mode == agc_aggressive) { 1471 | agc->decay_step = agc->decay_step * 4; 1472 | agc->attack_step = agc->attack_step * 5; 1473 | } 1474 | 1475 | return 0; 1476 | } 1477 | 1478 | int generate_header(struct demod_state *d, struct output_state *o) 1479 | { 1480 | int i, s_rate, b_rate; 1481 | char *channels = "\1\0"; 1482 | char *align = "\2\0"; 1483 | uint8_t samp_rate[4] = {0, 0, 0, 0}; 1484 | uint8_t byte_rate[4] = {0, 0, 0, 0}; 1485 | s_rate = o->rate; 1486 | b_rate = o->rate * 2; 1487 | if (d->mode_demod == &raw_demod || o->lrmix) { 1488 | channels = "\2\0"; 1489 | align = "\4\0"; 1490 | b_rate *= 2; 1491 | } 1492 | for (i=0; i<4; i++) { 1493 | samp_rate[i] = (uint8_t)((s_rate >> (8*i)) & 0xFF); 1494 | byte_rate[i] = (uint8_t)((b_rate >> (8*i)) & 0xFF); 1495 | } 1496 | fwrite("RIFF", 1, 4, o->file); 1497 | fwrite("\xFF\xFF\xFF\xFF", 1, 4, o->file); /* size */ 1498 | fwrite("WAVE", 1, 4, o->file); 1499 | fwrite("fmt ", 1, 4, o->file); 1500 | fwrite("\x10\0\0\0", 1, 4, o->file); /* size */ 1501 | fwrite("\1\0", 1, 2, o->file); /* pcm */ 1502 | fwrite(channels, 1, 2, o->file); 1503 | fwrite(samp_rate, 1, 4, o->file); 1504 | fwrite(byte_rate, 1, 4, o->file); 1505 | fwrite(align, 1, 2, o->file); 1506 | fwrite("\x10\0", 1, 2, o->file); /* bits per channel */ 1507 | fwrite("data", 1, 4, o->file); 1508 | fwrite("\xFF\xFF\xFF\xFF", 1, 4, o->file); /* size */ 1509 | return 0; 1510 | } 1511 | 1512 | int main(int argc, char **argv) 1513 | { 1514 | #ifndef _WIN32 1515 | struct sigaction sigact; 1516 | #endif 1517 | int r, opt; 1518 | int dev_given = 0; 1519 | int custom_ppm = 0; 1520 | 1521 | dongle_init(&dongle); 1522 | demod_init(&demod); 1523 | demod_init(&demod2); 1524 | output_init(&output); 1525 | controller_init(&controller); 1526 | 1527 | while ((opt = getopt(argc, argv, "d:f:g:s:b:l:o:t:r:p:E:F:A:M:h")) != -1) { 1528 | switch (opt) { 1529 | case 'd': 1530 | dongle.dev_index = verbose_device_search(optarg); 1531 | dev_given = 1; 1532 | break; 1533 | case 'f': 1534 | if (controller.freq_len >= FREQUENCIES_LIMIT) { 1535 | break;} 1536 | if (strchr(optarg, ':')) 1537 | {frequency_range(&controller, optarg);} 1538 | else 1539 | { 1540 | controller.freqs[controller.freq_len] = (uint32_t)atofs(optarg); 1541 | controller.freq_len++; 1542 | } 1543 | break; 1544 | case 'g': 1545 | dongle.gain = (int)(atof(optarg) * 10); 1546 | break; 1547 | case 'l': 1548 | demod.squelch_level = (int)atof(optarg); 1549 | break; 1550 | case 's': 1551 | demod.rate_in = (uint32_t)atofs(optarg); 1552 | demod.rate_out = (uint32_t)atofs(optarg); 1553 | break; 1554 | case 'r': 1555 | output.rate = (int)atofs(optarg); 1556 | demod.rate_out2 = (int)atofs(optarg); 1557 | break; 1558 | case 'o': 1559 | fprintf(stderr, "Warning: -o is very buggy\n"); 1560 | demod.post_downsample = (int)atof(optarg); 1561 | if (demod.post_downsample < 1 || demod.post_downsample > MAXIMUM_OVERSAMPLE) { 1562 | fprintf(stderr, "Oversample must be between 1 and %i\n", MAXIMUM_OVERSAMPLE);} 1563 | break; 1564 | case 't': 1565 | demod.conseq_squelch = (int)atof(optarg); 1566 | if (demod.conseq_squelch < 0) { 1567 | demod.conseq_squelch = -demod.conseq_squelch; 1568 | demod.terminate_on_squelch = 1; 1569 | } 1570 | break; 1571 | case 'p': 1572 | dongle.ppm_error = atoi(optarg); 1573 | custom_ppm = 1; 1574 | break; 1575 | case 'E': 1576 | if (strcmp("edge", optarg) == 0) { 1577 | controller.edge = 1;} 1578 | if (strcmp("no-dc", optarg) == 0) { 1579 | demod.dc_block = 0;} 1580 | if (strcmp("deemp", optarg) == 0) { 1581 | demod.deemph = 1;} 1582 | if (strcmp("swagc", optarg) == 0) { 1583 | demod.agc_mode = agc_normal;} 1584 | if (strcmp("swagc-aggressive", optarg) == 0) { 1585 | demod.agc_mode = agc_aggressive;} 1586 | if (strcmp("direct", optarg) == 0) { 1587 | dongle.direct_sampling = 1;} 1588 | if (strcmp("no-mod", optarg) == 0) { 1589 | dongle.direct_sampling = 3;} 1590 | if (strcmp("offset", optarg) == 0) { 1591 | dongle.offset_tuning = 1; 1592 | dongle.pre_rotate = 0;} 1593 | if (strcmp("wav", optarg) == 0) { 1594 | output.wav_format = 1;} 1595 | if (strcmp("pad", optarg) == 0) { 1596 | output.padded = 1;} 1597 | if (strcmp("lrmix", optarg) == 0) { 1598 | output.lrmix = 1;} 1599 | break; 1600 | case 'F': 1601 | demod.downsample_passes = 1; /* truthy placeholder */ 1602 | demod.comp_fir_size = atoi(optarg); 1603 | break; 1604 | case 'A': 1605 | if (strcmp("std", optarg) == 0) { 1606 | demod.custom_atan = 0;} 1607 | if (strcmp("fast", optarg) == 0) { 1608 | demod.custom_atan = 1;} 1609 | if (strcmp("lut", optarg) == 0) { 1610 | atan_lut_init(); 1611 | demod.custom_atan = 2;} 1612 | if (strcmp("ale", optarg) == 0) { 1613 | demod.custom_atan = 3;} 1614 | break; 1615 | case 'M': 1616 | if (strcmp("fm", optarg) == 0) { 1617 | demod.mode_demod = &fm_demod;} 1618 | if (strcmp("raw", optarg) == 0) { 1619 | demod.mode_demod = &raw_demod;} 1620 | if (strcmp("am", optarg) == 0) { 1621 | demod.mode_demod = &am_demod;} 1622 | if (strcmp("usb", optarg) == 0) { 1623 | demod.mode_demod = &usb_demod;} 1624 | if (strcmp("lsb", optarg) == 0) { 1625 | demod.mode_demod = &lsb_demod;} 1626 | if (strcmp("wbfm", optarg) == 0) { 1627 | controller.wb_mode = 1; 1628 | demod.mode_demod = &fm_demod; 1629 | demod.rate_in = 170000; 1630 | demod.rate_out = 170000; 1631 | demod.rate_out2 = 32000; 1632 | output.rate = 32000; 1633 | demod.custom_atan = 1; 1634 | //demod.post_downsample = 4; 1635 | demod.deemph = 1; 1636 | demod.squelch_level = 0;} 1637 | break; 1638 | case 'h': 1639 | default: 1640 | usage(); 1641 | break; 1642 | } 1643 | } 1644 | 1645 | agc_init(&demod); 1646 | 1647 | /* quadruple sample_rate to limit to Δθ to ±π/2 */ 1648 | demod.rate_in *= demod.post_downsample; 1649 | 1650 | if (!output.rate) { 1651 | output.rate = demod.rate_out;} 1652 | 1653 | sanity_checks(); 1654 | 1655 | if (controller.freq_len > 1) { 1656 | demod.terminate_on_squelch = 0;} 1657 | 1658 | if (argc <= optind) { 1659 | output.filename = "-"; 1660 | } else { 1661 | output.filename = argv[optind]; 1662 | } 1663 | 1664 | ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH; 1665 | 1666 | if (!dev_given) { 1667 | dongle.dev_index = verbose_device_search("0"); 1668 | } 1669 | 1670 | if (dongle.dev_index < 0) { 1671 | exit(1); 1672 | } 1673 | 1674 | r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index); 1675 | if (r < 0) { 1676 | fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dongle.dev_index); 1677 | exit(1); 1678 | } 1679 | #ifndef _WIN32 1680 | sigact.sa_handler = sighandler; 1681 | sigemptyset(&sigact.sa_mask); 1682 | sigact.sa_flags = 0; 1683 | sigaction(SIGINT, &sigact, NULL); 1684 | sigaction(SIGTERM, &sigact, NULL); 1685 | sigaction(SIGQUIT, &sigact, NULL); 1686 | sigaction(SIGPIPE, &sigact, NULL); 1687 | signal(SIGPIPE, SIG_IGN); 1688 | #else 1689 | SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE ); 1690 | output.padded = 0; 1691 | #endif 1692 | 1693 | if (demod.deemph) { 1694 | demod.deemph_a = (int)round(1.0/((1.0-exp(-1.0/(demod.rate_out * 75e-6))))); 1695 | } 1696 | 1697 | /* Set the tuner gain */ 1698 | if (dongle.gain == AUTO_GAIN) { 1699 | verbose_auto_gain(dongle.dev); 1700 | } else { 1701 | dongle.gain = nearest_gain(dongle.dev, dongle.gain); 1702 | verbose_gain_set(dongle.dev, dongle.gain); 1703 | } 1704 | 1705 | if (!custom_ppm) { 1706 | verbose_ppm_eeprom(dongle.dev, &(dongle.ppm_error)); 1707 | } 1708 | verbose_ppm_set(dongle.dev, dongle.ppm_error); 1709 | 1710 | if (strcmp(output.filename, "-") == 0) { /* Write samples to stdout */ 1711 | output.file = stdout; 1712 | #ifdef _WIN32 1713 | _setmode(_fileno(output.file), _O_BINARY); 1714 | #endif 1715 | } else { 1716 | //output.file = fopen(output.filename, "wb"); 1717 | if (!output.file) { 1718 | ///fprintf(stderr, "Failed to open %s\n", output.filename); 1719 | //exit(1); 1720 | } 1721 | } 1722 | 1723 | if (output.wav_format) { 1724 | generate_header(&demod, &output); 1725 | } 1726 | 1727 | //r = rtlsdr_set_testmode(dongle.dev, 1); 1728 | 1729 | /* Reset endpoint before we start reading from it (mandatory) */ 1730 | verbose_reset_buffer(dongle.dev); 1731 | 1732 | pthread_attr_t attr; 1733 | pthread_attr_init (&attr); 1734 | struct sched_param p1; 1735 | pthread_attr_setinheritsched (&attr, PTHREAD_INHERIT_SCHED); 1736 | pthread_attr_setschedpolicy (&attr, SCHED_FIFO); 1737 | p1.sched_priority = 20; 1738 | pthread_attr_setschedparam (&attr, &p1); 1739 | 1740 | pthread_create(&controller.thread, &attr, controller_thread_fn, (void *)(&controller)); 1741 | usleep(100000); 1742 | pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output)); 1743 | pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod)); 1744 | if (output.lrmix) { 1745 | pthread_create(&demod2.thread, NULL, demod_thread_fn, (void *)(&demod2)); 1746 | } 1747 | pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle)); 1748 | 1749 | while (!do_exit) { 1750 | usleep(100000); 1751 | } 1752 | 1753 | if (do_exit) { 1754 | fprintf(stderr, "\nUser cancel, exiting...\n");} 1755 | else { 1756 | fprintf(stderr, "\nLibrary error %d, exiting...\n", r);} 1757 | 1758 | rtlsdr_cancel_async(dongle.dev); 1759 | pthread_join(dongle.thread, NULL); 1760 | safe_cond_signal(&demod.ready, &demod.ready_m); 1761 | pthread_join(demod.thread, NULL); 1762 | if (output.lrmix) { 1763 | safe_cond_signal(&demod2.ready, &demod2.ready_m); 1764 | pthread_join(demod2.thread, NULL); 1765 | } 1766 | safe_cond_signal(&output.results[0].ready, &output.results[0].ready_m); 1767 | safe_cond_signal(&output.results[1].ready, &output.results[1].ready_m); 1768 | pthread_join(output.thread, NULL); 1769 | safe_cond_signal(&controller.hop, &controller.hop_m); 1770 | pthread_join(controller.thread, NULL); 1771 | 1772 | //dongle_cleanup(&dongle); 1773 | demod_cleanup(&demod); 1774 | output_cleanup(&output); 1775 | controller_cleanup(&controller); 1776 | 1777 | if (output.file != stdout) { 1778 | fclose(output.file);} 1779 | 1780 | rtlsdr_close(dongle.dev); 1781 | return r >= 0 ? r : -r; 1782 | } 1783 | 1784 | /* 1785 | 1786 | here be ponies 1787 | 1788 | */ 1789 | 1790 | int RtlSdr_init(struct RtlSdr* radio, int engineSampleRate) { 1791 | printf("📻 RtlSdr_init at sample rate %d\n", engineSampleRate); 1792 | 1793 | 1794 | radio->rack_buffer_pos = 0; 1795 | radio->rack_buffer = NULL; 1796 | radio->rack_mutex = NULL; 1797 | 1798 | dongle_init(&dongle); 1799 | demod_init(&demod); 1800 | demod_init(&demod2); 1801 | output_init(&output); 1802 | 1803 | controller_init(&controller); 1804 | char* freq = "99.5M"; 1805 | controller.freqs[controller.freq_len] = 100300000; 1806 | controller.freq_len++; 1807 | controller.freqs[controller.freq_len] = 102700000; 1808 | controller.freq_len++; 1809 | controller.wb_mode = 1; 1810 | agc_init(&demod); 1811 | 1812 | demod.mode_demod = &fm_demod; 1813 | demod.rate_in = 170000; 1814 | demod.rate_out = 170000; 1815 | demod.rate_out2 = engineSampleRate; 1816 | output.rate = engineSampleRate; 1817 | demod.custom_atan = 1; 1818 | //demod.post_downsample = 4; 1819 | demod.deemph = 1; 1820 | demod.squelch_level = 1; 1821 | if (demod.deemph) { 1822 | demod.deemph_a = (int)round(1.0/((1.0-exp(-1.0/(demod.rate_out * 75e-6))))); 1823 | } 1824 | 1825 | sanity_checks(); 1826 | ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH; 1827 | dongle.dev_index = verbose_device_search("0"); 1828 | 1829 | if (dongle.dev_index < 0) { 1830 | fprintf(stderr, "Skipping rtl-sdr initialization since we dont have a device"); 1831 | return -1; 1832 | } 1833 | int r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index); 1834 | if (r < 0) { 1835 | fprintf(stderr, "Failed to open rtlsdr device fff #%d.\n", dongle.dev_index); 1836 | return r; 1837 | } 1838 | verbose_auto_gain(dongle.dev); 1839 | 1840 | /* Reset endpoint before we start reading from it (mandatory) */ 1841 | verbose_reset_buffer(dongle.dev); 1842 | 1843 | pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller)); 1844 | //usleep(100000); 1845 | //pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output)); 1846 | pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod)); 1847 | if (output.lrmix) { 1848 | pthread_create(&demod2.thread, NULL, demod_thread_fn, (void *)(&demod2)); 1849 | } 1850 | pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle)); 1851 | 1852 | radio->rack_buffer_pos = &demod.rack_buffer_pos; 1853 | radio->rack_buffer = &demod.rack_buffer; 1854 | radio->rack_mutex = &demod.rack_mutex; 1855 | return 0; 1856 | } 1857 | 1858 | void RtlSdr_end(struct RtlSdr* radio) { 1859 | printf("📻 RtlSdr_end\n"); 1860 | sighandler(0); 1861 | 1862 | rtlsdr_cancel_async(dongle.dev); 1863 | pthread_join(dongle.thread, NULL); 1864 | safe_cond_signal(&demod.ready, &demod.ready_m); 1865 | pthread_join(demod.thread, NULL); 1866 | if (output.lrmix) { 1867 | safe_cond_signal(&demod2.ready, &demod2.ready_m); 1868 | pthread_join(demod2.thread, NULL); 1869 | } 1870 | safe_cond_signal(&controller.hop, &controller.hop_m); 1871 | pthread_join(controller.thread, NULL); 1872 | 1873 | //dongle_cleanup(&dongle); 1874 | demod_cleanup(&demod); 1875 | controller_cleanup(&controller); 1876 | 1877 | rtlsdr_close(dongle.dev); 1878 | } 1879 | 1880 | void RtlSdr_tune_thread_fn(long freq) { 1881 | rtlsdr_set_center_freq(dongle.dev, freq); 1882 | printf("📻 RtlSdr_tune done %ld\n", freq); 1883 | } 1884 | 1885 | void RtlSdr_tune(struct RtlSdr* radio, long freq) { 1886 | controller.freqs[0] = freq; 1887 | controller.freqs[1] = freq; 1888 | safe_cond_signal(&controller.hop, &controller.hop_m); 1889 | sched_yield(); 1890 | //pthread_t thread; 1891 | //pthread_create(&thread, NULL, RtlSdr_tune_thread_fn, freq); 1892 | } 1893 | 1894 | #ifdef __cplusplus 1895 | } 1896 | #endif 1897 | 1898 | // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab 1899 | --------------------------------------------------------------------------------