Compare commits

...

21 Commits

Author SHA1 Message Date
Hannes Matuschek 7cfba39a23 Fixed FM demod and http server. 11 years ago
Hannes Matuschek 589f2e5d7c Fixed compile issues under linux. 11 years ago
Hannes Matuschek 5ded7135d0 Added SHA1 implementation. 11 years ago
Hannes Matuschek 23d2e122bd Fixed build scripts to generate resource header files. 11 years ago
Hannes Matuschek 4d79c7e1e4 Fixed minors. 11 years ago
Hannes Matuschek a3a6165ede Added a trivial HTTP server class. 11 years ago
Hannes Matuschek 7c5cfb2019 Implemented simple cmd app. 11 years ago
Hannes Matuschek 28b907dd9a Fixed 8-bit issue with WavSource. 11 years ago
Hannes Matuschek 907527a7f9 Cleanup 11 years ago
Hannes Matuschek 36ac8d3cc1 Implemented trivial APRS decoder. 11 years ago
Hannes Matuschek acee3dc318 Fixed docs. 11 years ago
Hannes Matuschek 40e384772a Fixed docs. 11 years ago
Hannes Matuschek 00c21e6b6e Fixed POCSAG decoding 11 years ago
Hannes Matuschek 0ad4ec8634 Implemented POCSAG decoder. 11 years ago
Hannes Matuschek 0b7bc758d5 Fixed comments. 11 years ago
Hannes Matuschek 71e3d29d69 Added RTTY example program. 11 years ago
Hannes Matuschek 6dde3c11c7 Merge branch 'master' of https://github.com/hmatuschek/libsdr
Conflicts:
	src/autocast.hh
	src/baseband.hh
	src/freqshift.hh
	src/rtlsource.hh
	src/utils.hh
11 years ago
Hannes Matuschek fca04605f3 Fixed. 11 years ago
Hannes Matuschek 97e42901c4 Added missing dist file. 11 years ago
Hannes Matuschek 0cf7570624 Fixed comments. 11 years ago
Hannes Matuschek 2afdd5060c Fixed autocast. 11 years ago

@ -3,9 +3,11 @@ project(libsdr)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
include(InstallHeadersWithDirectory)
include(LinkResources)
OPTION(BUILD_EXAMPLES "Build examples" OFF)
OPTION(BUILD_UNIT_TESTS "Build unit tests" OFF)
OPTION(BUILD_COMMANDLINETOOL "Build command line tool" ON)
SET(libsdr_VERSION_MAJOR "0")
SET(libsdr_VERSION_MINOR "1")
@ -19,6 +21,7 @@ find_package(RTLSDR)
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src)
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}/src)
INCLUDE_DIRECTORIES(${PORTAUDIO_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${GETOPT_INCLUDE_DIRS})
# Set some variables for the configuration file
IF(FFTW_FOUND)
@ -41,7 +44,8 @@ ELSE(TRLSDR_FOUND)
ENDIF(RTLSDR_FOUND)
set(LIBS ${FFTW_LIBRARIES} ${FFTWSingle_LIBRARIES} ${PORTAUDIO_LIBRARIES} ${RTLSDR_LIBRARIES} "pthread")
set(LIBS ${FFTW_LIBRARIES} ${FFTWSingle_LIBRARIES} ${PORTAUDIO_LIBRARIES} ${RTLSDR_LIBRARIES}
"pthread")
# Set compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC")
@ -82,13 +86,19 @@ ENDIF(UNIX AND APPLE)
# Add core library, and unit tests
add_subdirectory(src)
IF(BUILD_UNIT_TESTS)
add_subdirectory(test)
ENDIF(BUILD_UNIT_TESTS)
IF(BUILD_EXAMPLES)
add_subdirectory(examples)
ENDIF(BUILD_EXAMPLES)
IF(BUILD_COMMANDLINETOOL)
add_subdirectory(cmd)
endif(BUILD_COMMANDLINETOOL)
# Source distribution packages:
set(CPACK_PACKAGE_VERSION_MAJOR ${libsdr_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${libsdr_VERSION_MINOR})

@ -1,22 +1,34 @@
# libsdr - A simple software defined radio (SDR) library
**First of all:** I assembled this library for my one entertainment and to learn something about software defined radio. If you are interested into a full-featured, performant SDR framework, consider using GNU radio (http://gnuradio.org).
**First of all:** I assembled this library for my one entertainment and to learn something about
software defined radio. If you are interested into a full-featured, performant SDR framework,
consider using GNU radio (http://gnuradio.org).
<a href="http://de.tinypic.com?ref=2jb2qfb" target="_blank"><img src="http://i61.tinypic.com/2jb2qfb.png" border="0" alt="SRD-RX"></a>
<a href="http://de.tinypic.com?ref=2jb2qfb" target="_blank">
<img src="http://i61.tinypic.com/2jb2qfb.png" border="0" alt="SRD-RX">
</a>
Although being simple, libsdr is sufficient to write a simple SDR receiver application (http://github.com/hmatuschek/sdr-rx, above). This RX application supports several input sources (i.e. sound card, files, RTL2382 dongles etc.) and modes (i.e. AM, FM, SSB, CW, etc.).
Although being simple, libsdr is sufficient to write a simple SDR receiver application
(http://github.com/hmatuschek/sdr-rx, above). This RX application supports several input sources
(i.e. sound card, files, RTL2382 dongles etc.) and modes (i.e. AM, FM, SSB, CW, etc.).
## Build
The only required run-time dependency of `libsdr` is `libpthread`, which is available on all Unix-like OSs like Linux and MacOS X. It is also available for windows if `mingw` is used (http://www.mingw.org) of compilation. There are also some optional dependencies, which allow for the usage of some additional features of the library.
The only required run-time dependency of `libsdr` is `libpthread`, which is available on all
Unix-like OSs like Linux and MacOS X. It is also available for windows if `mingw` is used
(http://www.mingw.org) of compilation. There are also some optional dependencies, which allow for
the usage of some additional features of the library.
* `Qt5` (http://qt-project.org) - Enables the `libsdr-gui` library implementing some graphical user interface elements like a spectrum view.
* `fftw3` (http://www.fftw.org) - Also required by the GUI library and allows for FFT-convolution filters.
* `Qt5` (http://qt-project.org) - Enables the `libsdr-gui` library implementing some graphical user
interface elements like a spectrum view.
* `fftw3` (http://www.fftw.org) - Also required by the GUI library and allows for FFT-convolution
filters.
* `PortAudio` (http://www.portaudio.com) - Allows for sound-card input and output.
* `librtlsdr` (http://rtlsdr.org) - Allows to interface RTL2382U based USB dongles.
For the compilation of the library, `cmake` (http://www.cmake.org) is also required (as well as a compiler like gcc or clang of cause).
For the compilation of the library, `cmake` (http://www.cmake.org) is also required (as well as a
compiler like gcc or clang of cause).
Compiling the library is the canonical cmake path:

@ -0,0 +1,7 @@
macro(link_resources HEADER_NAME)
#create_resources("${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME}.hh" ${ARGN})
add_custom_target(${HEADER_NAME} ALL
COMMAND ${CMAKE_COMMAND} -DOUTPUT="${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME}.hh" -DRESOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}" -DFILES="${ARGN}" -P "${PROJECT_SOURCE_DIR}/cmake/create_resources.cmake"
DEPENDS ${ARGN} SOURCES ${ARGN})
endmacro(link_resources)

@ -0,0 +1,22 @@
#
# Implements the packing of resource files into a header file
#
get_filename_component(OUTPUT_FILE ${OUTPUT} NAME)
message(STATUS "Generate resource file '${OUTPUT_FILE}' from: ${FILES}")
# Create empty file
file(WRITE ${OUTPUT} "")
# For each resource file
foreach(file ${FILES})
# Normalize filename
string(REGEX MATCH "([^/]+)$" filename ${file})
string(REGEX REPLACE "\\.| " "_" filename ${filename})
# Read and convert file content
file(READ "${RESOURCE_PATH}/${file}" filedata HEX)
string(REGEX REPLACE "([0-9a-fA-F][0-9a-fA-F])" "0x\\1," filedata ${filedata})
# Update output file
file(APPEND ${OUTPUT}
"extern \"C\" {\n"
" static char ${filename}[] = {${filedata}};\n"
" static unsigned ${filename}_size = sizeof(${filename});\n"
"}\n")
endforeach()

@ -0,0 +1,8 @@
# Creates a header file including the shared resources
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
link_resources(sdr_cmd_resources shared/index.html)
add_executable(sdr_cmd main.cc aprsapplication.cc)
add_dependencies(sdr_cmd sdr_cmd_resources)
target_link_libraries(sdr_cmd ${LIBS} libsdr )

@ -0,0 +1,87 @@
#include "aprsapplication.hh"
// This file will be generated at build time
// and contains the static content served.
#include "sdr_cmd_resources.hh"
using namespace sdr;
APRSApplication::APRSApplication(http::Server &server)
: APRS(), _server(server), _messages()
{
// Register callbacks
server.addStatic("/", std::string(index_html, index_html_size), "text/html");
server.addJSON("/spots", this, &APRSApplication::spots);
server.addHandler("/update", this, &APRSApplication::update);
}
APRSApplication::~APRSApplication() {
// pass...
}
bool
APRSApplication::spots(const http::JSON &request, http::JSON &response) {
std::list<http::JSON> msg_list;
for (std::list<Message>::iterator msg = _messages.begin(); msg != _messages.end(); msg++) {
std::map<std::string, http::JSON> message;
message["call"] = http::JSON(msg->from().call());
if (msg->hasLocation()) {
message["lat"] = http::JSON(msg->latitude());
message["lon"] = http::JSON(msg->longitude());
}
time_t time = msg->time();
message["time"] = http::JSON(ctime(&time));
msg_list.push_back(message);
}
response = http::JSON(msg_list);
return true;
}
void
APRSApplication::handleAPRSMessage(const Message &message) {
_messages.push_back(message);
// Serialize JSON message
std::string json_text;
if (_clients.size()) {
std::map<std::string, http::JSON> msg;
msg["call"] = http::JSON(message.from().call());
if (message.hasLocation()) {
msg["lat"] = http::JSON(message.latitude());
msg["lon"] = http::JSON(message.longitude());
}
time_t time = message.time();
msg["time"] = http::JSON(ctime(&time));
http::JSON(msg).serialize(json_text);
}
// a list collecting the closed
// signal all clients connected
std::list<http::Connection>::iterator client = _clients.begin();
while (client != _clients.end()) {
if (client->isClosed()) {
// remove client from list
client = _clients.erase(client);
} else {
// send event
client->send("data: ");
client->send(json_text);
client->send("\n\n");
}
}
}
void
APRSApplication::update(const http::Request &request, http::Response &response) {
// This call back implements a server side event stream, means the response will
// be blocked until the connection is closed
response.setHeader("Content-Type", "text/event-stream");
response.setHeader("Cache-Control", "no-cache");
response.setStatus(http::Response::STATUS_OK);
response.sendHeaders();
// Signal connection thread to exit without closing the connection
response.connection().setProtocolUpgrade();
// Store connection
_clients.push_back(response.connection());
}

@ -0,0 +1,29 @@
#ifndef __SDR_APRS_APRSAPPLICATION_HH__
#define __SDR_APRS_APRSAPPLICATION_HH__
#include "http.hh"
#include "aprs.hh"
namespace sdr {
class APRSApplication: public APRS
{
public:
APRSApplication(http::Server &server);
~APRSApplication();
bool spots(const http::JSON &request, http::JSON &response);
void update(const http::Request &request, http::Response &response);
void handleAPRSMessage(const Message &message);
protected:
http::Server &_server;
std::list<Message> _messages;
std::list<http::Connection> _clients;
};
}
#endif // APRSAPPLICATION_HH

@ -0,0 +1,36 @@
#include <stdio.h>
#include "queue.hh"
#include "logger.hh"
#include <iostream>
#include <csignal>
#include "aprsapplication.hh"
using namespace sdr;
static http::Server *server = 0;
static void __sigint_handler(int signo) {
if (server) { server->stop(true); }
}
int main(int argc, char *argv[]) {
server = new http::Server(8080);
APRSApplication app(*server);
// Install log handler
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Register signal handler:
signal(SIGINT, __sigint_handler);
// start server
server->start(true);
return 0;
}

@ -0,0 +1,74 @@
<html>
<head>
<title>sdr-aprs &mdash; An APRS receiver using libsdr.</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<script>
var map = null;
var spots = {};
var source = null;
function onAddSpot (callsign, lon, lat) {
// Check if connection exists
if (callsign in spots) {
// If connection exists, update SNR
connections[spots].marker.setPosition(new google.maps.LatLng(lat, lon));
} else {
// otherwise, add marker at location with label callsign
var marker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lon), title: callsign, map: map});
spots[callsign] = { marker: marker };
}
}
function updateHandler(e) {
var msg = JSON.parse(e.data);
console.log("RX: " + msg)
onAddSpot(msg.call, msg.lon, msg.lat);
}
function initialize() {
// Create Map object
map = new google.maps.Map(
document.getElementById('map-canvas'),
{zoom:2, center: new google.maps.LatLng(0,0), streetViewControl:false});
// Request all spots
var xmlhttp = new XMLHttpRequest();
var url = "spots";
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState != 4 && xmlhttp.status != 200) { return; }
var spots = JSON.parse(xmlhttp.responseText);
for (spot in spots) { onAddSpot(spot); }
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
// Now, connect to event source for updates
if (!!window.EventSource) {
source = new window.EventSource("update");
source.addEventListener('message', updateHandler);
} else {
alert("Your browser does not support the EventSource -> no live update.")
}
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map-canvas">
<p>Can not load Google Maps view. Check your internet conenction!</p>
</div>
</body>
</html>

@ -1,21 +1,25 @@
Source: libsdr
Priority: extra
Maintainer: Hannes Matuschek <hmatuschek@gmail.com>
Build-Depends: cdbs (>= 0.4.51), dh-exec, debhelper (>= 8.0.0), cmake, qtbase5-dev (>= 5.0), portaudio19-dev, librtlsdr-dev, fftw3-dev
Standards-Version: 3.9.2
Build-Depends: cdbs (>= 0.4.51), dh-exec, debhelper (>= 9.0.0), cmake, portaudio19-dev, librtlsdr-dev, fftw3-dev
Standards-Version: 3.9.5
Section: hamradio
Homepage: http://github.com/hmatuschek/libsdr
Package: libsdr
Package: libsdr1
Section: hamradio
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Multi-Arch: same
Pre-Depends: multiarch-support, ${misc:Pre-Depends}
Depends: ${shlibs:Depends}, ${misc:Depends}, libportaudio2
Description: libsdr
A C++ library for software defined radio.
Package: libsdr-dev
Section: hamradio
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, libsdr
Multi-Arch: same
Pre-Depends: multiarch-support, ${misc:Pre-Depends}
Depends: libsdr1 (= ${binary:Version}), ${misc:Depends}, portaudio19-dev, librtlsdr-dev, fftw3-dev, libsdr1
Description: libsdr
A C++ library for software defined radio. Development files.

@ -1,4 +1,3 @@
#! /usr/bin/dh-exec
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr.so
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr-gui.so
/usr/include/*
usr/lib/*/libsdr.so
usr/include/*

@ -1,3 +0,0 @@
#! /usr/bin/dh-exec
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr.so.*
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr-gui.so.*

@ -0,0 +1,2 @@
#! /usr/bin/dh-exec
usr/lib/*/libsdr.so.*

@ -8,8 +8,15 @@ IF(SDR_WITH_PORTAUDIO)
add_executable(sdr_rec sdr_rec.cc)
target_link_libraries(sdr_rec ${LIBS} libsdr)
add_executable(sdr_afsk1200 sdr_afsk1200.cc)
target_link_libraries(sdr_afsk1200 ${LIBS} libsdr)
add_executable(sdr_rtty sdr_rtty.cc)
target_link_libraries(sdr_rtty ${LIBS} libsdr)
add_executable(sdr_pocsag sdr_pocsag.cc)
target_link_libraries(sdr_pocsag ${LIBS} libsdr)
add_executable(sdr_ax25 sdr_ax25.cc)
target_link_libraries(sdr_ax25 ${LIBS} libsdr)
ENDIF(SDR_WITH_PORTAUDIO)

@ -1,358 +0,0 @@
#include "wavfile.hh"
#include "autocast.hh"
#include "interpolate.hh"
using namespace sdr;
static const uint16_t crc_ccitt_table[] = {
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};
static inline bool check_crc_ccitt(const uint8_t *buf, int cnt)
{
uint32_t crc = 0xffff;
for (; cnt > 0; cnt--, buf++) {
crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ (*buf)) & 0xff];
}
return (crc & 0xffff) == 0xf0b8;
}
class AFSK: public Sink<int16_t>, public Source {
public:
AFSK(double baud=1200.0, double Fmark=1200.0, double Fspace=2200.0)
: Sink<int16_t>(), Source(), _baud(baud), _Fmark(Fmark), _Fspace(Fspace)
{
// pass...
}
virtual ~AFSK() {
// pass...
}
virtual void config(const Config &src_cfg) {
// Check if config is complete
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Check if buffer type matches
if (Config::typeId<int16_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AFSK1200: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
throw err;
}
// The input sample rate
_sampleRate = src_cfg.sampleRate();
// Samples per bit
_corrLen = int(_sampleRate/_baud);
// Compute symbol rate:
_symbolRate = std::min(10*_baud, _baud*_corrLen);
// Samples per symbol (fractional):
_mu = 0.0; _muIncr = _sampleRate/_symbolRate;
// Delayline for interpolating sub-sampler
_dl = Buffer<float>(2*8);
for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; }
_dl_idx = 0;
// Assemble phase LUT:
_markLUT = Buffer< std::complex<float> >(_corrLen);
_spaceLUT = Buffer< std::complex<float> >(_corrLen);
// Allocate ring-buffers for mark and space symbols
_markHist = Buffer< std::complex<float> >(_corrLen);
_spaceHist = Buffer< std::complex<float> >(_corrLen);
// Initialize LUTs and ring-buffers
double phiMark=0, phiSpace=0;
for (size_t i=0; i<_corrLen; i++) {
_markLUT[i] = std::exp(std::complex<float>(0.0, phiMark));
_spaceLUT[i] = std::exp(std::complex<float>(0.0, phiSpace));
phiMark += (2.*M_PI*_Fmark)/_sampleRate;
phiSpace += (2.*M_PI*_Fspace)/_sampleRate;
_markHist[i] = 0; _spaceHist[i] = 0;
}
_lutIdx = 0;
// Get phase increment per symbol
_phase = 0; _omega = _baud/_symbolRate;
_omegaMin = _omega - 0.01*_omega;
_omegaMax = _omega + 0.01*_omega;
_gainOmega = 0.01;
// Allocate output buffer:
_buffer = Buffer<uint8_t>(src_cfg.bufferSize()/_corrLen + 1);
LogMessage msg(LOG_DEBUG);
msg << "Config AFSK1200 node: " << std::endl
<< " input sample rate: " << _sampleRate << "Hz" << std::endl
<< " samples per symbol: " << _muIncr << std::endl
<< " symbols per bit: " << _corrLen << std::endl
<< " symbol rate: " << _symbolRate << "Hz" << std::endl
<< " Phase incr/symbol: " << float(_omega);
Logger::get().log(msg);
this->setConfig(Config(Traits<uint8_t>::scalarId, _baud, _buffer.size(), 1));
}
virtual void process(const Buffer<int16_t> &buffer, bool allow_overwrite) {
size_t i=0, o=0;
while (i<buffer.size()) {
// Update sub-sampler
while ((_mu>1) && (i<buffer.size())) {
_markHist[_lutIdx] = float(buffer[i])*_markLUT[_lutIdx];
_spaceHist[_lutIdx] = float(buffer[i])*_spaceLUT[_lutIdx];
// Modulo LUT length
_lutIdx++; if (_lutIdx==_corrLen) { _lutIdx=0; }
float symbol = _getSymbol();
// Put symbol into delay line
_dl[_dl_idx] = symbol; _dl[_dl_idx+8] = symbol;
_dl_idx = (_dl_idx+1)%8; _mu -= 1; i++;
}
if (i<buffer.size()) {
// Get interpolated symbol
float sample = interpolate(_dl.sub(_dl_idx, 8), _mu); _mu += _muIncr;
// Get symbol
_symbols <<= 1; _symbols |= (sample>0);
// Advance phase
_phase += _omega;
// Sample bit
if (_phase >= 1) {
// Modulo "2 pi"
_phase = fmodf(_phase, 1.0);
// Store bit
_lastBits <<= 1; _lastBits |= (_symbols & 1);
// Put decoded bit in output buffer
// transition -> 0; no transition -> 1
_buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 1) & 1);
}
// If transition
if ((_symbols ^ (_symbols >> 1)) & 1) {
// Phase correction
/*std::cerr << "Transition at phi=" << _phase << std::endl
<< " update omega from " << _omega << " to "; */
if (_phase < 0.5) { _omega -= _gainOmega*(_phase); }
else { _omega += _gainOmega*(1-_phase); }
// Limit omega
_omega = std::min(_omegaMax, std::max(_omegaMin, _omega));
//std::cerr << _omega << std::endl;
}
}
}
this->send(_buffer.head(o));
}
protected:
inline double _getSymbol() {
std::complex<double> markSum(0), spaceSum(0);
for (size_t i=0; i<_corrLen; i++) {
markSum += _markHist[i];
spaceSum += _spaceHist[i];
}
double f = markSum.real()*markSum.real() +
markSum.imag()*markSum.imag() -
spaceSum.real()*spaceSum.real() -
spaceSum.imag()*spaceSum.imag();
return f;
}
protected:
float _sampleRate, _symbolRate;
float _baud;
float _Fmark, _Fspace;
uint32_t _corrLen;
uint32_t _lutIdx;
Buffer< std::complex<float> > _markLUT;
Buffer< std::complex<float> > _spaceLUT;
float _mu, _muIncr;
Buffer< float > _dl;
size_t _dl_idx;
Buffer< std::complex<float> > _markHist;
Buffer< std::complex<float> > _spaceHist;
uint32_t _symbols;
uint32_t _lastBits;
float _phase;
float _omega, _omegaMin, _omegaMax;
float _gainOmega;
static const uint32_t _phasePeriod = 0x10000u;
static const uint32_t _phaseMask = 0x0ffffu;
/** Output buffer. */
Buffer<uint8_t> _buffer;
};
class AX25: public Sink<uint8_t>, public Source
{
public:
AX25()
: Sink<uint8_t>(), Source()
{
// pass...
}
virtual ~AX25() {
// pass...
}
virtual void config(const Config &src_cfg) {
if (! src_cfg.hasType()) { return; }
// Check if buffer type matches
if (Config::typeId<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AX25: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<uint8_t>();
throw err;
}
_bitstream = 0;
_bitbuffer = 0;
_state = 0;
_ptr = _rxbuffer;
// Allocate output buffer
_buffer = Buffer<uint8_t>(512);
// propergate config
this->setConfig(Config(Traits<uint8_t>::scalarId, 0, 512, 1));
}
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
// Store bit in stream
_bitstream <<= 1; _bitstream |= !!buffer[i];
// Check for sync byte
if ((_bitstream & 0xff) == 0x7e) {
if (_state && ((_ptr - _rxbuffer) > 2)) {
*_ptr = 0;
if (! check_crc_ccitt(_rxbuffer, _ptr-_rxbuffer)) {
std::cerr << "Got invalid buffer: " << _rxbuffer << std::endl;
} else {
std::cerr << "GOT: " << _rxbuffer << std::endl;
memcpy(_buffer.ptr(), _rxbuffer, _ptr-_rxbuffer);
this->send(_buffer.head(_ptr-_rxbuffer));
}
}
_state = 1;
_ptr = _rxbuffer;
_bitbuffer = 0x80;
continue;
}
// If 7 ones are received in a row -> error, wait or sync byte
if ((_bitstream & 0x7f) == 0x7f) { _state = 0; continue; }
// If state == wait for sync byte -> receive next bit
if (!_state) { continue; }
/* stuffed bit */
if ((_bitstream & 0x3f) == 0x3e) { continue; }
// prepend bit to bitbuffer
_bitbuffer |= ((_bitstream & 1) << 8);
// If 8 bits have been received (stored in b8-b1 of _bitbuffer)
if (_bitbuffer & 1) {
// Check for buffer overrun
if ((_ptr-_rxbuffer) >= 512) {
Logger::get().log(LogMessage(LOG_ERROR, "AX.25 packet too long."));
// Wait for next sync byte
_state = 0;
continue;
}
// Store received byte and ...
*_ptr++ = (_bitbuffer >> 1);
// reset bit buffer
_bitbuffer = 0x80;
continue;
}
// Shift bitbuffer one to the left
_bitbuffer >>= 1;
}
}
protected:
uint32_t _bitstream;
uint32_t _bitbuffer;
uint32_t _state;
uint8_t _rxbuffer[512];
uint8_t *_ptr;
Buffer<uint8_t> _buffer;
};
int main(int argc, char *argv[]) {
if (2 > argc) { std::cout << "USAGE: sdr_afsk1200 FILENAME" << std::endl; return -1; }
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
WavSource src(argv[1], 1024);
if (! src.isOpen()) { std::cout << "Can not open file " << argv[1] << std::endl; return -1; }
AutoCast< int16_t > cast;
AFSK demod;
AX25 decode;
src.connect(&cast);
cast.connect(&demod);
demod.connect(&decode);
Queue::get().addIdle(&src, &WavSource::next);
Queue::get().start();
Queue::get().wait();
return 0;
}

@ -0,0 +1,192 @@
/*
* sdr_ax25 -- A AX.25 and APRS receiver using libsdr.
*
* (c) 2015 Hannes Matuschek <hmatuschek at gmail dot com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "options.hh"
#include "autocast.hh"
#include "rtlsource.hh"
#include "baseband.hh"
#include "demod.hh"
#include "portaudio.hh"
#include "wavfile.hh"
#include "fsk.hh"
#include "utils.hh"
#include "aprs.hh"
#include <iostream>
#include <cmath>
#include <csignal>
using namespace sdr;
// On SIGINT -> stop queue properly
static void __sigint_handler(int signo) {
Queue::get().stop();
}
// Command line options
static Options::Definition options[] = {
{"frequency", 'F', Options::FLOAT,
"Selects a RTL2832 as the source and specifies the frequency in Hz."},
{"correction", 0, Options::FLOAT,
"Specifies the frequency correction for the RTL2832 device in parts-per-million (ppm)."},
{"audio", 'a', Options::FLAG, "Selects the system audio as the source."},
{"file", 'f', Options::ANY, "Selects a WAV file as the source."},
{"monitor", 'M', Options::FLAG, "Enable sound monitor."},
{"help", 0, Options::FLAG, "Prints this help message."},
{0,0,Options::FLAG,0}
};
void print_help() {
std::cerr << "USAGE: sdr_ax25 SOURCE [OPTIONS]" << std::endl << std::endl;
Options::print_help(std::cerr, options);
}
int main(int argc, char *argv[])
{
// Install log handler
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Register signal handler:
signal(SIGINT, __sigint_handler);
// Parse command line options.
Options opts;
if (! Options::parse(options, argc, argv, opts)) {
print_help(); return -1;
}
// If help flag is present -> print and done.
if (opts.has("help")) { print_help(); return 0; }
// If no source has been selected
if (! (opts.has("frequency")|opts.has("audio")|opts.has("file"))) {
print_help(); return -1;
}
// Init audio system
PortAudio::init();
// Get the global queue
Queue &queue = Queue::get();
// pointer to the selected source
Source *src = 0;
// nodes for WAV file input
WavSource *wav_src=0;
AutoCast<int16_t> *wav_cast=0;
// nodes for PortAudio input
PortSource<int16_t> *audio_src=0;
// nodes for RTL2832 input
RTLSource *rtl_source=0;
AutoCast< std::complex<int16_t> > *rtl_cast=0;
IQBaseBand<int16_t> *rtl_baseband=0;
FMDemod<int16_t> *rtl_demod=0;
FMDeemph<int16_t> *rtl_deemph=0;
if (opts.has("frequency")) {
// Assemble processing chain for the RTL2832 intput
rtl_source = new RTLSource(opts.get("frequency").toFloat());
if (opts.has("correction")) {
// Apply specified frequency correction.
rtl_source->setFreqCorrection(opts.get("correction").toFloat());
}
rtl_cast = new AutoCast< std::complex<int16_t> >();
rtl_baseband = new IQBaseBand<int16_t>(0, 15.0e3, 21, 0, 22050.0);
rtl_demod = new FMDemod<int16_t>();
rtl_deemph = new FMDeemph<int16_t>();
// Connect nodes
rtl_source->connect(rtl_cast);
rtl_cast->connect(rtl_baseband, true);
rtl_baseband->connect(rtl_demod);
rtl_demod->connect(rtl_deemph);
// FM deemph. is source for decoder
src = rtl_deemph;
// On queue start, start RTL source
Queue::get().addStart(rtl_source, &RTLSource::start);
// On queue stop, stop RTL source
Queue::get().addStop(rtl_source, &RTLSource::stop);
} else if (opts.has("audio")) {
// Configure audio source
audio_src = new PortSource<int16_t>(22010., 1024);
src = audio_src;
// On queue idle, read next chunk from audio source
Queue::get().addIdle(audio_src, &PortSource<int16_t>::next);
} else if (opts.has("file")) {
// Assemble processing chain for WAV file input
wav_src = new WavSource(opts.get("file").toString());
wav_cast = new AutoCast<int16_t>();
wav_src->connect(wav_cast);
src = wav_cast;
// On queue idle, read next chunk from file
Queue::get().addIdle(wav_src, &WavSource::next);
// On end of file, stop queue
wav_src->addEOS(&(Queue::get()), &Queue::stop);
}
/* Common demodulation nodes. */
// (A)FSK detector
FSKDetector detector(1200, 1200, 2200);
// Bit decoder
BitStream bits(1200, BitStream::TRANSITION);
// APRS decoder
APRS aprs;
// Audio sink for monitor
PortSink sink;
// connect source ASK detector
src->connect(&detector);
// detector to bit decoder
detector.connect(&bits);
// and bit decoder to APRS decoder and print
bits.connect(&aprs);
// If monitor is enabled -> connect to sink
if (opts.has("monitor")) {
src->connect(&sink);
}
// Start queue
queue.start();
// wait for queue to exit
queue.wait();
// Free allocated nodes
if (rtl_source) { delete rtl_source; }
if (rtl_cast) { delete rtl_cast; }
if (rtl_baseband) { delete rtl_baseband; }
if (rtl_demod) { delete rtl_demod; }
if (rtl_deemph) { delete rtl_deemph; }
if (audio_src) { delete audio_src; }
if (wav_src) { delete wav_src; }
if (wav_cast) { delete wav_cast; }
// terminate port audio system properly
PortAudio::terminate();
// quit.
return 0;
}

@ -0,0 +1,191 @@
/*
* sdr_pocsag -- A POCSAG receiver using libsdr.
*
* (c) 2015 Hannes Matuschek <hmatuschek at gmail dot com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "autocast.hh"
#include "portaudio.hh"
#include "wavfile.hh"
#include "fsk.hh"
#include "utils.hh"
#include "pocsag.hh"
#include "options.hh"
#include "rtlsource.hh"
#include "demod.hh"
#include "baseband.hh"
#include <iostream>
#include <cmath>
#include <csignal>
using namespace sdr;
// On SIGINT -> stop queue properly
static void __sigint_handler(int signo) {
Queue::get().stop();
}
// Command line options
static Options::Definition options[] = {
{"frequency", 'F', Options::FLOAT,
"Selects a RTL2832 as the source and specifies the frequency in Hz."},
{"correction", 0, Options::FLOAT,
"Specifies the frequency correction for the RTL2832 device in parts-per-million (ppm)."},
{"audio", 'a', Options::FLAG, "Selects the system audio as the source."},
{"file", 'f', Options::ANY, "Selects a WAV file as the source."},
{"monitor", 'M', Options::FLAG, "Enable sound monitor."},
{"invert", 0, Options::FLAG, "Inverts mark/space logic."},
{"help", 0, Options::FLAG, "Prints this help message."},
{0,0,Options::FLAG,0}
};
void print_help() {
std::cerr << "USAGE: sdr_pocsag SOURCE [OPTIONS]" << std::endl << std::endl;
Options::print_help(std::cerr, options);
}
int main(int argc, char *argv[])
{
// Install log handler
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Register signal handler:
signal(SIGINT, __sigint_handler);
// Parse command line options.
Options opts;
if (! Options::parse(options, argc, argv, opts)) {
print_help(); return -1;
}
if (opts.has("help")) {
print_help(); return 0;
}
// If no source has been selected
if (! (opts.has("frequency")|opts.has("audio")|opts.has("file"))) {
print_help(); return -1;
}
// Init audio system
PortAudio::init();
// Get the global queue
Queue &queue = Queue::get();
// pointer to the selected source
Source *src = 0;
// Nodes for WAV file input
WavSource *wav_src=0;
AutoCast<int16_t> *wav_cast=0;
// Nodes for PortAudio input
PortSource<int16_t> *audio_src=0;
// Nodes for RTL2832 input
RTLSource *rtl_source=0;
AutoCast< std::complex<int16_t> > *rtl_cast=0;
IQBaseBand<int16_t> *rtl_baseband=0;
FMDemod<int16_t> *rtl_demod=0;
FMDeemph<int16_t> *rtl_deemph=0;
if (opts.has("frequency")) {
// Assemble processing chain for the RTL2832 intput
rtl_source = new RTLSource(opts.get("frequency").toFloat());
if (opts.has("correction")) {
rtl_source->setFreqCorrection(opts.get("correction").toFloat());
}
rtl_cast = new AutoCast< std::complex<int16_t> >();
rtl_baseband = new IQBaseBand<int16_t>(0, 12.5e3, 21, 0, 22050.0);
rtl_demod = new FMDemod<int16_t>();
rtl_deemph = new FMDeemph<int16_t>();
rtl_source->connect(rtl_cast);
rtl_cast->connect(rtl_baseband, true);
rtl_baseband->connect(rtl_demod);
rtl_demod->connect(rtl_deemph);
// FM deemph. is source for decoder
src = rtl_deemph;
// On queue start, start RTL source
Queue::get().addStart(rtl_source, &RTLSource::start);
// On queue stop, stop RTL source
Queue::get().addStop(rtl_source, &RTLSource::stop);
} else if (opts.has("audio")) {
// Configure audio source
audio_src = new PortSource<int16_t>(22010., 1024);
src = audio_src;
// On queue idle, read next chunk from audio source
Queue::get().addIdle(audio_src, &PortSource<int16_t>::next);
} else if (opts.has("file")) {
// Assemble processing chain for WAV file input
wav_src = new WavSource(opts.get("file").toString());
wav_cast = new AutoCast<int16_t>();
wav_src->connect(wav_cast);
src = wav_cast;
// On queue idle, read next chunk from file
Queue::get().addIdle(wav_src, &WavSource::next);
// On end of file, stop queue
wav_src->addEOS(&(Queue::get()), &Queue::stop);
}
/* Common demodulation nodes. */
// amplitude detector
ASKDetector<int16_t> detector(opts.has("invert"));
// Bit decoder
BitStream bits(1200, BitStream::NORMAL);
// POCSAG decoder
POCSAGDump pocsag(std::cout);
// Audio sink for monitor
PortSink sink;
// connect source ASK detector
src->connect(&detector);
// detector to bit decoder
detector.connect(&bits);
// and bit decoder to POCSAG decoder and print
bits.connect(&pocsag);
// If monitor is enabled -> connect to sink
if (opts.has("monitor")) {
src->connect(&sink);
}
// Start queue
queue.start();
// wait for queue to exit
queue.wait();
// Free allocated nodes
if (rtl_source) { delete rtl_source; }
if (rtl_cast) { delete rtl_cast; }
if (rtl_baseband) { delete rtl_baseband; }
if (rtl_demod) { delete rtl_demod; }
if (rtl_deemph) { delete rtl_deemph; }
if (audio_src) { delete audio_src; }
if (wav_src) { delete wav_src; }
if (wav_cast) { delete wav_cast; }
// terminate port audio system properly
PortAudio::terminate();
// quit.
return 0;
}

@ -1,81 +0,0 @@
#include "baseband.hh"
#include "autocast.hh"
#include "demod.hh"
#include "portaudio.hh"
#include "wavfile.hh"
#include "gui/gui.hh"
#include "psk31.hh"
#include <iostream>
#include <cmath>
#include <csignal>
#include <QApplication>
#include <QMainWindow>
#include <QThread>
using namespace sdr;
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
Queue::get().stop();
}
int main(int argc, char *argv[]) {
if (2 != argc) {
std::cerr << "Usage: sdr_psk31 FILENAME" << std::endl;
return -1;
}
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Register handler:
signal(SIGINT, __sigint_handler);
QApplication app(argc, argv);
QMainWindow *win = new QMainWindow();
gui::Spectrum *spec = new gui::Spectrum(2, 1024, 5);
gui::SpectrumView *spec_view = new gui::SpectrumView(spec);
spec_view->setMindB(-60);
win->setCentralWidget(spec_view);
win->setMinimumSize(640, 240);
win->show();
PortAudio::init();
Queue &queue = Queue::get();
WavSource src(argv[1]);
AutoCast< std::complex<int16_t> > cast;
IQBaseBand<int16_t> baseband(0, 2144., 200.0, 63, 1);
baseband.setCenterFrequency(2144.0);
BPSK31<int16_t> demod;
PortSink sink;
Varicode decode;
DebugDump<uint8_t> dump;
src.connect(&cast, true);
cast.connect(&baseband, true);
baseband.connect(spec);
baseband.connect(&sink);
baseband.connect(&demod);
demod.connect(&decode, true);
decode.connect(&dump, true);
queue.addIdle(&src, &WavSource::next);
//queue.addIdle(&src, &IQSigGen<double>::next);
queue.start();
app.exec();
queue.stop();
queue.wait();
PortAudio::terminate();
return 0;
}

@ -1,82 +0,0 @@
#include "sdr.hh"
#include "rtlsource.hh"
#include "baseband.hh"
#include "autocast.hh"
#include "gui/gui.hh"
#include "logger.hh"
#include <signal.h>
#include <QApplication>
#include <QMainWindow>
#include <QThread>
using namespace sdr;
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
Queue::get().stop();
}
int main(int argc, char *argv[])
{
if (argc < 2) {
std::cerr << "USAGE: sdr_rds FREQUENCY" << std::endl;
return -1;
}
double freq = atof(argv[1]);
PortAudio::init();
Queue &queue = Queue::get();
// Register handler:
signal(SIGINT, __sigint_handler);
QApplication app(argc, argv);
QMainWindow *win = new QMainWindow();
gui::Spectrum *spec = new gui::Spectrum(2, 1024, 5);
gui::SpectrumView *spec_view = new gui::SpectrumView(spec);
spec_view->setMindB(-200);
win->setCentralWidget(spec_view);
win->setMinimumSize(640, 240);
win->show();
sdr::Logger::get().addHandler(new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Assemble processing chain
//RTLSource src(freq, 1e6);
WavSource src(argv[1]);
AutoCast< std::complex<int16_t> > cast;
IQBaseBand<int16_t> baseband(0, 200e3, 16, 5);
FMDemod<int16_t, int16_t> demod;
BaseBand<int16_t> mono(0, 15e3, 16, 6);
BaseBand<int16_t> pilot(19e3, 5e2, 16, 84);
BaseBand<int16_t> rds(57e3, 3e3, 16, 84);
PortSink sink;
src.connect(&cast, true);
cast.connect(&baseband, true);
//src.connect(&baseband, true);
baseband.connect(&demod, true);
demod.connect(&mono);
demod.connect(&pilot);
demod.connect(&rds);
mono.connect(&sink);
mono.connect(spec);
//queue.addStart(&src, &RTLSource::start);
//queue.addStop(&src, &RTLSource::stop);
queue.addIdle(&src, &WavSource::next);
queue.start();
app.exec();
queue.stop();
queue.wait();
PortAudio::terminate();
return 0;
}

@ -0,0 +1,72 @@
#include "autocast.hh"
#include "portaudio.hh"
#include "wavfile.hh"
#include "fsk.hh"
#include "baudot.hh"
#include "utils.hh"
#include <iostream>
#include <cmath>
#include <csignal>
using namespace sdr;
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
Queue::get().stop();
}
int main(int argc, char *argv[])
{
if (2 != argc) {
std::cerr << "Usage: sdr_rtty FILENAME" << std::endl;
return -1;
}
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Register handler:
signal(SIGINT, __sigint_handler);
PortAudio::init();
Queue &queue = Queue::get();
WavSource src(argv[1]);
PortSink sink;
AutoCast<int16_t> cast;
FSKDetector fsk(90.90, 930., 1100.);
BitStream bits(90.90, BitStream::NORMAL);
Baudot decoder;
TextDump dump;
// Playback
src.connect(&sink);
// Cast to int16
src.connect(&cast);
// FSK demod
cast.connect(&fsk);
fsk.connect(&bits);
// Baudot decoder
bits.connect(&decoder);
// dump to std::cerr
decoder.connect(&dump);
// on idle -> read next buffer from input file
queue.addIdle(&src, &WavSource::next);
// on end-of-file -> stop queue
src.addEOS(&queue, &Queue::stop);
// Start queue
queue.start();
// wait for queue to exit
queue.wait();
// terminate port audio system properly
PortAudio::terminate();
// quit.
return 0;
}

@ -1,64 +0,0 @@
#include "sdr.hh"
#include "rtlsource.hh"
#include "baseband.hh"
#include "utils.hh"
#include "gui/gui.hh"
#include <signal.h>
#include "portaudio.hh"
#include <QApplication>
#include <QMainWindow>
#include <QThread>
using namespace sdr;
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
Queue::get().stop();
}
int main(int argc, char *argv[])
{
Queue &queue = Queue::get();
// Register handler:
signal(SIGINT, __sigint_handler);
PortAudio::init();
QApplication app(argc, argv);
QMainWindow *win = new QMainWindow();
gui::Spectrum *spec = new gui::Spectrum(2, 1024, 5);
gui::WaterFallView *spec_view = new gui::WaterFallView(spec);
win->setCentralWidget(spec_view);
win->setMinimumSize(640, 240);
win->show();
// Assemble processing chain
PortSource< int16_t > src(44100.0, 2048);
AGC<int16_t> agc;
//IQBaseBand<int16_t> baseband(0, 500e3, 8, 5);
//AMDemod<int16_t> demod;
PortSink sink;
src.connect(&agc, true);
agc.connect(&sink, true);
//baseband.connect(&demod);
//demod.connect(&sink, true);
src.connect(spec);
queue.addIdle(&src, &PortSource< int16_t >::next);
queue.start();
app.exec();
queue.stop();
queue.wait();
PortAudio::terminate();
return 0;
}

@ -1,13 +1,13 @@
# Sources of libsdr
set(LIBSDR_SOURCES
buffer.cc node.cc queue.cc traits.cc
portaudio.cc utils.cc wavfile.cc
exception.cc logger.cc psk31.cc options.cc)
buffer.cc node.cc queue.cc traits.cc portaudio.cc utils.cc wavfile.cc exception.cc logger.cc
psk31.cc options.cc fsk.cc ax25.cc aprs.cc baudot.cc pocsag.cc bch31_21.cc http.cc sha1.cc)
set(LIBSDR_HEADERS sdr.hh math.hh
buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.hh
siggen.hh portaudio.hh utils.hh wavfile.hh demod.hh firfilter.hh
fftplan.hh fftplan_native.hh exception.hh baseband.hh freqshift.hh subsample.hh
combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh)
combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh fsk.hh ax25.hh
aprs.hh baudot.hh pocsag.hh bch31_21.hh http.hh sha1.hh)
if(SDR_WITH_PORTAUDIO)
set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc)

@ -0,0 +1,344 @@
#include "aprs.hh"
#include "logger.hh"
using namespace sdr;
/* ******************************************************************************************** *
* Implementation of APRS
* ******************************************************************************************** */
APRS::APRS()
: AX25()
{
// pass...
}
void
APRS::handleAX25Message(const AX25::Message &message) {
// Skip non-UI frames
if (0x03 != uint8_t(message.payload()[0])) {
LogMessage msg(LOG_DEBUG);
msg << "APRS: Skip non-UI frame (type="
<< std::hex << int(uint8_t(message.payload()[0]))
<< std::dec << "): " << message;
Logger::get().log(msg);
return;
}
// Skip frames woth level-3 protocol
if (0xf0 != uint8_t(message.payload()[1])) {
LogMessage msg(LOG_DEBUG);
msg << "APRS: Skip invalid UI (pid="
<< std::hex << int(uint8_t(message.payload()[1]))
<< std::dec << "): " << message;
Logger::get().log(msg);
return;
}
// Construct APRS message
Message msg(message);
// Handle message
this->handleAPRSMessage(msg);
}
void
APRS::handleAPRSMessage(const Message &message) {
std::cerr << message << std::endl;
}
/* ******************************************************************************************** *
* Implementation of APRS::Message
* ******************************************************************************************** */
bool __is_number(char c) {
return (('0'<=c) && ('9'>=c));
}
APRS::Message::Symbol
__toSymbol(char table, char sym) {
if ('/' == table) {
switch (sym) {
case 'P':
case '!': return APRS::Message::POLICE;
case '%':
case '&':
case '(':
case 'B':
case 'n':
case '#': return APRS::Message::DIGI;
case '[':
case 'e':
case '$': return APRS::Message::JOGGER;
case 'X':
case '^':
case 'g':
case '\'': return APRS::Message::AIRCRAFT;
case '-': return APRS::Message::HOUSE;
case 'b':
case '<': return APRS::Message::MOTORCYCLE;
case '=':
case '*':
case 'U':
case 'j':
case 'k':
case 'u':
case 'v':
case '>': return APRS::Message::CAR;
case 'Y':
case 's':
case 'C': return APRS::Message::BOAT;
case 'O': return APRS::Message::BALLOON;
case '_': return APRS::Message::WX;
default: break;
}
} else if ('\\' == table) {
switch (sym) {
default: break;
}
}
return APRS::Message::NONE;
}
APRS::Message::Message()
: AX25::Message(), _hasLocation(false), _latitude(0), _longitude(0), _symbol(NONE),
_hasTime(false), _time(::time(0))
{
// pass...
}
APRS::Message::Message(const AX25::Message &msg)
: AX25::Message(msg),
_hasLocation(false), _latitude(0), _longitude(0), _symbol(NONE),
_hasTime(false), _time(::time(0))
{
size_t offset = 2;
// Dispatch by message type
switch (_payload[offset]) {
case '=':
case '!':
_hasLocation = true;
offset++;
break;
case '/':
case '@':
_hasTime = true;
_hasLocation = true;
offset++;
break;
case ';':
_hasTime = true;
_hasLocation = true;
// Skip ';', 9 char of object identifier and delimiter ('*' or '_').
offset += 11;
break;
default:
// On unknown message type -> store complete message as comment
if (offset < _payload.size()) {
_comment = _payload.substr(offset);
offset += _comment.size();
}
return;
}
if (_hasTime && (! _readTime(offset))) { _hasTime = false; _hasLocation = false; return; }
if (_hasLocation && (! _readLocation(offset))) { _hasLocation = false; return; }
// Remaining text is comment
if (offset < _payload.size()) {
_comment = _payload.substr(offset);
offset += _comment.size();
}
}
APRS::Message::Message(const Message &msg)
: AX25::Message(msg), _hasLocation(msg._hasLocation), _latitude(msg._latitude),
_longitude(msg._longitude), _symbol(msg._symbol), _hasTime(msg._hasTime),
_time(msg._hasTime)
{
// pass...
}
bool
APRS::Message::_readLocation(size_t &offset) {
// Read latitude
if(! _readLatitude(offset)) { return false; }
// Read symbol table
char symbolTable = _payload[offset]; offset++;
// Read longitude
if (! _readLongitude(offset)) { return false; }
// Read symbol
_symbol = __toSymbol(symbolTable, _payload[offset]); offset++;
return true;
}
bool
APRS::Message::_readLatitude(size_t &offset)
{
// read degree
if (! __is_number(_payload[offset])) { return false; }
_latitude = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
_latitude = _latitude*10 + int(_payload[offset]-0x30); offset++;
// read mintues
if (! __is_number(_payload[offset])) { return false; }
double min = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
min = min*10 + int(_payload[offset]-0x30); offset++;
// check for '.'
if ('.' != _payload[offset]) { return false; } offset++;
// read minutes decimal
if (! __is_number(_payload[offset])) { return false; }
double mindec = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
mindec = mindec*10 + int(_payload[offset]-0x30); offset++;
min += mindec/100;
// Update degree
_latitude += min/60;
// Process north/south indicator
switch (_payload[offset]) {
case 'N': offset++; break;
case 'S': _latitude = -_latitude; offset++; break;
default: return false; // <- on invalid indicator.
}
// done.
return true;
}
bool
APRS::Message::_readLongitude(size_t &offset) {
// read degree
if (! __is_number(_payload[offset])) { return false; }
_longitude = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
_longitude = _longitude*10 + int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
_longitude = _longitude*10 + int(_payload[offset]-0x30); offset++;
// read minutes
if (! __is_number(_payload[offset])) { return false; }
double min = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
min = min*10 + int(_payload[offset]-0x30); offset++;
// skip '.'
if ('.' != _payload[offset]) { return false; } offset++;
// Read minutes decimals
if (! __is_number(_payload[offset])) { return false; }
double mindec = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
mindec = mindec*10 + int(_payload[offset]-0x30); offset++;
min += mindec/100;
// Update longitude
_longitude += min/60;
// read east/west indicator
switch (_payload[offset]) {
case 'E': offset++; break;
case 'W': offset++; _longitude = -_longitude; break;
default: return false;
}
return true;
}
bool
APRS::Message::_readTime(size_t &offset) {
if (! __is_number(_payload[offset])) { return false; }
int a = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
a = a*10 + int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
int b = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
b = b*10 + int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
int c = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
c = c*10 + int(_payload[offset]-0x30); offset++;
// Determine type of date-time
if ('z' == _payload[offset]) {
// Alter current time in terms of UTC
struct tm timeStruct = *gmtime(&_time);
timeStruct.tm_mday = a;
timeStruct.tm_hour = b;
timeStruct.tm_min = c;
// Update time-stamp
_time = mktime(&timeStruct);
offset++;
} else if ('/' == _payload[offset]) {
// Alter current time in terms of local time
struct tm timeStruct = *localtime(&_time);
timeStruct.tm_mday = a;
timeStruct.tm_hour = b;
timeStruct.tm_min = c;
// Update time-stamp
_time = mktime(&timeStruct);
offset++;
} else if ('h' == _payload[offset]) {
// Alter current time in terms of local time
struct tm timeStruct = *localtime(&_time);
timeStruct.tm_hour = a;
timeStruct.tm_min = b;
timeStruct.tm_sec = c;
// Update time-stamp
_time = mktime(&timeStruct);
offset++;
} else if (('0' <= _payload[offset]) && (('9' >= _payload[offset]))) {
if (! __is_number(_payload[offset])) { return false; }
int d = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
d = d*10 + int(_payload[offset]-0x30); offset++;
// Alter current time in terms of local time
struct tm timeStruct = *localtime(&_time);
timeStruct.tm_mon = a;
timeStruct.tm_mday = b;
timeStruct.tm_hour = c;
timeStruct.tm_min = d;
// Update time-stamp
_time = mktime(&timeStruct);
} else {
return false;
}
// done...
return true;
}
std::ostream &
sdr::operator <<(std::ostream &stream, const APRS::Message &message) {
std::cerr << "APRS: " << message.from() << " > " << message.to();
if (message.via().size()) {
std::cerr << " via " << message.via()[0];
for (size_t i=1; i<message.via().size(); i++) {
std::cerr << ", " << message.via()[i];
}
}
std::cerr << std::endl << " time: " << asctime(localtime(&message.time()));
if (message.hasLocation()) {
std::cerr << " location: " << message.latitude() << ", " << message.longitude()
<< std::endl << " symbol: " << message.symbol() << std::endl;
}
if (message.hasComment()) {
std::cerr << " comment: " << message.comment() << std::endl;
}
return stream;
}

@ -0,0 +1,66 @@
#ifndef __SDR_APRS_HH__
#define __SDR_APRS_HH__
#include "ax25.hh"
#include <ctime>
namespace sdr {
class APRS: public AX25
{
public:
class Message: public AX25::Message
{
public:
/** A small selection of possible symbols to display the station. */
typedef enum {
NONE, POLICE, DIGI, PHONE, AIRCRAFT, HOUSE, MOTORCYCLE, CAR, BBS, BALLOON, BUS,
BOAT, JOGGER, WX
} Symbol;
public:
Message();
Message(const AX25::Message &msg);
Message(const Message &msg);
Message &operator=(const Message &other);
inline bool hasLocation() const { return _hasLocation; }
inline double latitude() const { return _latitude; }
inline double longitude() const { return _longitude; }
inline Symbol symbol() const { return _symbol; }
inline const time_t &time() const { return _time; }
inline bool hasComment() const { return (0 != _comment.size()); }
inline const std::string &comment() const { return _comment; }
protected:
bool _readLocation(size_t &offset);
bool _readLatitude(size_t &offset);
bool _readLongitude(size_t &offset);
bool _readTime(size_t &offset);
protected:
bool _hasLocation;
double _latitude;
double _longitude;
Symbol _symbol;
bool _hasTime;
time_t _time;
std::string _comment;
};
public:
APRS();
void handleAX25Message(const AX25::Message &message);
virtual void handleAPRSMessage(const Message &message);
};
std::ostream& operator<<(std::ostream &stream, const APRS::Message &msg);
}
#endif // __SDR_APRS_HH__

@ -7,8 +7,9 @@
namespace sdr {
/** This class performs some automatic casts to a certain buffer type if possible specified by
* the template argument. Currently only integer casts are supported. */
/** This class performs some automatic casts to a certain buffer type (if possible) specified by
* the template argument. Currently only integer casts are supported.
* @ingroup datanodes */
template <class Scalar>
class AutoCast: public SinkBase, public Source
{
@ -28,9 +29,9 @@ public:
// Check type cast combination
if (Config::Type_s8 == Traits<Scalar>::scalarId) {
switch (src_cfg.type()) {
case Config::Type_u8:
case Config::Type_u8: _cast = _uint8_int8; break;
case Config::Type_s8: _cast = _identity; break;
case Config::Type_u16:
case Config::Type_u16: _cast = _uint16_int8; break;
case Config::Type_s16: _cast = _int16_int8; break;
default: break;
}
@ -38,17 +39,19 @@ public:
switch (src_cfg.type()) {
case Config::Type_u8: _cast = _uint8_cint8; break;
case Config::Type_s8: _cast = _int8_cint8; break;
case Config::Type_cu8: _cast = _cuint8_cint8; break;
case Config::Type_cu8: _cast = _uint8_int8; break;
case Config::Type_cs8: _cast = _identity; break;
case Config::Type_u16:
case Config::Type_u16: _cast = _uint16_cint8; break;
case Config::Type_s16: _cast = _int16_cint8; break;
case Config::Type_cu16: _cast = _uint16_int8; break;
case Config::Type_cs16: _cast = _int16_int8; break;
default: break;
}
} else if (Config::Type_s16 == Traits<Scalar>::scalarId) {
switch (src_cfg.type()) {
case Config::Type_u8:
case Config::Type_u8: _cast = _uint8_int16; break;
case Config::Type_s8: _cast = _int8_int16; break;
case Config::Type_u16:
case Config::Type_u16: _cast = _uint16_int16; break;
case Config::Type_s16: _cast = _identity; break;
default: break;
}
@ -56,11 +59,11 @@ public:
switch (src_cfg.type()) {
case Config::Type_u8: _cast = _uint8_cint16; break;
case Config::Type_s8: _cast = _int8_cint16; break;
case Config::Type_cu8: _cast = _cuint8_cint16; break;
case Config::Type_cs8: _cast = _cint8_cint16; break;
case Config::Type_cu8: _cast = _uint8_int16; break;
case Config::Type_cs8: _cast = _int8_int16; break;
case Config::Type_u16: _cast = _uint16_cint16; break;
case Config::Type_s16: _cast = _int16_cint16; break;
case Config::Type_cu16:
case Config::Type_cu16: _cast = _uint16_int16; break;
case Config::Type_cs16: _cast = _identity; break;
default: break;
}
@ -110,11 +113,32 @@ protected:
return in.bytesLen();
}
/** uint8_t -> int8_t */
static size_t _uint8_int8(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen();
for (size_t i=0; i<N; i++) {
reinterpret_cast<int8_t *>(out.data())[i] =
int16_t(reinterpret_cast<uint8_t *>(in.data())[i]) - 127;
}
return N;
}
/** uint16 -> int8 */
static size_t _uint16_int8(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;
for (size_t i=0; i<N; i++) {
reinterpret_cast<int8_t *>(out.data())[i] =
int16_t(reinterpret_cast<uint16_t *>(in.data())[i]>>8) - 127;
}
return N;
}
/** int16 -> int8 */
static size_t _int16_int8(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;
for (size_t i=0; i<N; i++) {
reinterpret_cast<int8_t *>(out.data())[i] = reinterpret_cast<int16_t *>(in.data())[i]>>8;
reinterpret_cast<int8_t *>(out.data())[i] =
reinterpret_cast<int16_t *>(in.data())[i]>>8;
}
return N;
}
@ -134,18 +158,18 @@ protected:
static size_t _int8_cint8(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen();
for (size_t i=0; i<N; i++) {
reinterpret_cast<std::complex<int8_t> *>(out.data())[i] = reinterpret_cast<int8_t *>(in.data())[i];
reinterpret_cast<std::complex<int8_t> *>(out.data())[i] =
reinterpret_cast<int8_t *>(in.data())[i];
}
return 2*N;
}
/** std::complex<uint8_t> -> std::complex<int8_t>. */
static size_t _cuint8_cint8(const RawBuffer &in, const RawBuffer &out) {
/** uint16 -> complex int 8. */
static size_t _uint16_cint8(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;
std::complex<uint8_t> *values = reinterpret_cast<std::complex<uint8_t> *>(in.data());
for (size_t i=0; i<N; i++) {
reinterpret_cast<std::complex<int8_t> *>(out.data())[i] =
std::complex<int8_t>(int16_t(values[i].real())-127, int16_t(values[i].imag())-127);
reinterpret_cast<std::complex<int8_t> *>(out.data())[i]
= int32_t(reinterpret_cast<int16_t *>(in.data())[i]>>8)-((2<<15)-1);
}
return 2*N;
}
@ -159,12 +183,12 @@ protected:
return 2*N;
}
/** complex int16 -> complex int 8. */
static size_t _cint16_cint8(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/4;
std::complex<int16_t> *values = reinterpret_cast<std::complex<int16_t> *>(in.data());
/** uint8 -> int16. */
static size_t _uint8_int16(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen();
int8_t *values = reinterpret_cast<int8_t *>(in.data());
for (size_t i=0; i<N; i++) {
reinterpret_cast<std::complex<int8_t> *>(out.data())[i] = std::complex<int8_t>(values[i].real()>>8, values[i].imag()>>8);
reinterpret_cast<int16_t *>(out.data())[i] = (int16_t(values[i])-127)<<8;
}
return 2*N;
}
@ -179,6 +203,16 @@ protected:
return 2*N;
}
/** uint16 -> int16. */
static size_t _uint16_int16(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;
uint16_t *values = reinterpret_cast<uint16_t *>(in.data());
for (size_t i=0; i<N; i++) {
reinterpret_cast<int16_t *>(out.data())[i] = int32_t(values[i])-((2<<15)-1);
}
return 2*N;
}
/** unsinged int8 -> complex int16. */
static size_t _uint8_cint16(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen();
@ -201,30 +235,6 @@ protected:
return 4*N;
}
/** complex unsigned int8 -> complex int16. */
static size_t _cuint8_cint16(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;
std::complex<uint8_t> *values = reinterpret_cast<std::complex<uint8_t> *>(in.data());
for (size_t i=0; i<N; i++) {
reinterpret_cast<std::complex<int16_t> *>(out.data())[i] =
std::complex<int16_t>((int16_t(values[i].real())-127)*(1<<8),
(int16_t(values[i].imag())-127)*(1<<8));
}
return 4*N;
}
/** complex int8 -> complex int16. */
static size_t _cint8_cint16(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;
std::complex<int8_t> *values = reinterpret_cast<std::complex<int8_t> *>(in.data());
for (size_t i=0; i<N; i++) {
reinterpret_cast<std::complex<int16_t> *>(out.data())[i] =
std::complex<int16_t>(int16_t(values[i].real())*(1<<8),
int16_t(values[i].imag())*(1<<8));
}
return 4*N;
}
/** uint16 -> complex int16. */
static size_t _uint16_cint16(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;

@ -0,0 +1,277 @@
#include "ax25.hh"
#include "logger.hh"
#include "traits.hh"
#include <ctime>
using namespace sdr;
static const uint16_t crc_ccitt_table[] = {
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};
static inline bool check_crc_ccitt(const uint8_t *buf, int cnt)
{
uint32_t crc = 0xffff;
for (; cnt > 0; cnt--, buf++) {
crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ (*buf)) & 0xff];
}
return (crc & 0xffff) == 0xf0b8;
}
void
unpackCall(const uint8_t *buffer, std::string &call, int &ssid, bool &addrExt) {
size_t length = 0; call.resize(6);
for (size_t i=0; i<6; i++) {
call.at(i) = char(buffer[i]>>1);
if (' ' != call.at(i)) { length++; }
}
call.resize(length);
ssid = int( (buffer[6] & 0x1f) >> 1);
addrExt = !bool(buffer[6] & 0x01);
}
/* ******************************************************************************************** *
* Implementation of AX25 decoder
* ******************************************************************************************** */
AX25::AX25() : Sink<uint8_t>()
{
// pass...
}
AX25::~AX25() {
// pass...
}
void
AX25::config(const Config &src_cfg) {
if (! src_cfg.hasType()) { return; }
// Check if buffer type matches
if (Config::typeId<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AX25: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<uint8_t>();
throw err;
}
_bitstream = 0;
_bitbuffer = 0;
_state = 0;
_ptr = _rxbuffer;
LogMessage msg(LOG_DEBUG);
msg << "Config AX.25 node.";
Logger::get().log(msg);
}
void
AX25::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
{
for (size_t i=0; i<buffer.size(); i++) {
// Store bit in stream
_bitstream = ((_bitstream << 1) | (buffer[i] & 0x01));
// Check for sync byte
if ((_bitstream & 0xff) == 0x7e) {
if ((1==_state) && ((_ptr - _rxbuffer) > 2)) {
*_ptr = 0;
if (! check_crc_ccitt(_rxbuffer, _ptr-_rxbuffer)) {
/*LogMessage msg(LOG_DEBUG);
msg << "AX.25: Received invalid buffer: " << _rxbuffer;
Logger::get().log(msg); */
} else {
// Assemble message
Message msg(_rxbuffer, _ptr-_rxbuffer-2);
this->handleAX25Message(msg);
}
}
// Receive data
_state = 1;
_ptr = _rxbuffer;
_bitbuffer = 0x80;
continue;
}
// If 7 ones are received in a row -> error, wait or sync byte
if ((_bitstream & 0x7f) == 0x7f) { _state = 0; continue; }
// If state == wait for sync byte -> receive next bit
if (!_state) { continue; }
/* stuffed bit */
if ((_bitstream & 0x3f) == 0x3e) { continue; }
// prepend bit to bitbuffer
/// @todo Double check this!!!
_bitbuffer |= ((_bitstream & 0x01) << 8);
// If 8 bits have been received (stored in b8-b1 of _bitbuffer)
if (_bitbuffer & 0x01) {
// Check for buffer overrun
if ((_ptr-_rxbuffer) >= 512) {
Logger::get().log(LogMessage(LOG_DEBUG, "AX.25 packet too long."));
// Wait for next sync byte
_state = 0;
continue;
}
// Store received byte and ...
*_ptr++ = (_bitbuffer >> 1);
// reset bit buffer
_bitbuffer = 0x80;
continue;
}
// Shift bitbuffer one to the left
_bitbuffer >>= 1;
}
}
void
AX25::handleAX25Message(const Message &message) {
// pass...
}
/* ******************************************************************************************** *
* Implementation of AX25Dump
* ******************************************************************************************** */
AX25Dump::AX25Dump(std::ostream &stream)
: AX25(), _stream(stream)
{
// pass...
}
void
AX25Dump::handleAX25Message(const Message &message) {
_stream << "AX25: " << message << std::endl;
}
/* ******************************************************************************************** *
* Implementation of AX25 Address
* ******************************************************************************************** */
AX25::Address::Address()
: _call(), _ssid(0)
{
// pass...
}
AX25::Address::Address(const std::string &call, size_t ssid)
: _call(call), _ssid(ssid)
{
// pass...
}
AX25::Address::Address(const Address &other)
: _call(other._call), _ssid(other._ssid)
{
// pass...
}
AX25::Address &
AX25::Address::operator =(const Address &other) {
_call = other._call;
_ssid = other._ssid;
return *this;
}
std::ostream &
sdr::operator <<(std::ostream &stream, const AX25::Address &addr) {
stream << addr.call() << "-" << addr.ssid();
return stream;
}
/* ******************************************************************************************** *
* Implementation of AX25 Message
* ******************************************************************************************** */
AX25::Message::Message()
: _from(), _to(), _via(), _payload()
{
// pass...
}
AX25::Message::Message(uint8_t *buffer, size_t length)
: _via(), _payload()
{
std::string call; int ssid; bool addrExt;
// Get destination address
unpackCall(buffer, call, ssid, addrExt); buffer+=7; length -= 7;
_to = Address(call, ssid);
// Get source address
unpackCall(buffer, call, ssid, addrExt); buffer+=7; length -= 7;
_from = Address(call, ssid);
while (addrExt) {
unpackCall(buffer, call, ssid, addrExt); buffer+=7; length -= 7;
_via.push_back(Address(call, ssid));
}
// Store payload
_payload.resize(length);
for (size_t i=0; i<length; i++) { _payload[i] = char(buffer[i]); }
}
AX25::Message::Message(const Message &other)
: _from(other._from), _to(other._to), _via(other._via),
_payload(other._payload)
{
// pass...
}
AX25::Message &
AX25::Message::operator =(const Message &other) {
_from = other._from;
_to = other._to;
_via = other._via;
_payload = other._payload;
return *this;
}
std::ostream &
sdr::operator <<(std::ostream &stream, const AX25::Message &msg) {
stream << msg.from() << " > " << msg.to();
if (0 < msg.via().size()) {
stream << " via " << msg.via()[0];
for (size_t i=1; i<msg.via().size(); i++) {
stream << ", " << msg.via()[i];
}
}
stream << " N=" << msg.payload().size() << std::endl;
stream << msg.payload();
return stream;
}

@ -0,0 +1,115 @@
#ifndef __SDR_AX25_HH__
#define __SDR_AX25_HH__
#include "node.hh"
namespace sdr {
/** Decodes AX25 (PacketRadio) messages from a bit stream.
*
* In conjecture with the (A)FSK demodulator, the AX25 can be used to receive packet radio or APRS
* messages. AX25 is usually transmitted as FSK in transition mode, means the bits aren't
* encoded by mark & space tones but rather as a transition from mark to space or in reverse. Hence
* the FSK node needs to be configured in transition mode.
*
* The node does not process the actual AX.25 packages, it only checks the frame check sequence and
* forwards the AX.25 datagram to all connected sinks on success. The receiving node is responsible
* for unpacking and handling the received datagram.
* @ingroup datanodes */
class AX25: public Sink<uint8_t>
{
public:
class Address
{
public:
Address();
Address(const std::string &call, size_t ssid);
Address(const Address &other);
Address &operator=(const Address &other);
inline bool isEmpty() const { return 0 == _call.size(); }
inline const std::string &call() const { return _call; }
inline size_t ssid() const { return _ssid; }
protected:
std::string _call;
size_t _ssid;
};
class Message
{
public:
Message();
Message(uint8_t *buffer, size_t length);
Message(const Message &other);
Message &operator=(const Message &other);
inline const Address &from() const { return _from; }
inline const Address &to() const { return _to; }
inline const std::vector<Address> &via() const { return _via; }
inline const std::string &payload() const { return _payload; }
protected:
Address _from;
Address _to;
std::vector<Address> _via;
std::string _payload;
};
public:
/** Constructor. */
AX25();
/** Destructor. */
virtual ~AX25();
/** Configures the node. */
virtual void config(const Config &src_cfg);
/** Processes the bit stream. */
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
virtual void handleAX25Message(const Message &message);
protected:
/** The last bits. */
uint32_t _bitstream;
/** A buffer of received bits. */
uint32_t _bitbuffer;
/** The current state. */
uint32_t _state;
/** Message buffer. */
uint8_t _rxbuffer[512];
/** Insert-pointer to the buffer. */
uint8_t *_ptr;
};
/** Prints received AX25 messages to the specified stream. */
class AX25Dump: public AX25
{
public:
/** Constructor.
* @param stream The output stream. */
AX25Dump(std::ostream &stream);
/** Implements AX25 interface. */
void handleAX25Message(const Message &message);
protected:
/** The output stream. */
std::ostream &_stream;
};
/** Serialization of AX25 address. */
std::ostream& operator<<(std::ostream &stream, const sdr::AX25::Address &addr);
/** Serialization of AX25 message. */
std::ostream& operator<<(std::ostream &stream, const sdr::AX25::Message &msg);
} // namespace sdr
#endif // __SDR_AX25_HH__

@ -14,9 +14,10 @@ namespace sdr {
/** This class performs several operations on the complex (integral) input stream, it first filters
* out some part of the input stream using a FIR band pass (band pass is centerred around @c Ff
* with width @c width) then shifts the center frequency @c Fc to 0 and finally sub-samples the
* resulting stream. This node can be used to select a portion of the input stream and reduce the
* rate of the stream, allowing for some more expensive operations to be performed on the output
* stream. */
* resulting stream. This node can be used to select a portion of the input spectrum and for the
* reduction of the stream rate, allowing for some more expensive operations to be performed on the
* output stream.
* @ingroup filters */
template <class Scalar>
class IQBaseBand: public Sink< std::complex<Scalar> >, public Source, public FreqShiftBase<Scalar>
{
@ -298,7 +299,8 @@ protected:
/** This class performs several operations on the real input stream,
* It first filters out some part of the input stream using a FIR band pass filter
* then shifts the center frequency to 0 and finally sub-samples the resulting stream such that
* the selected base-band is well represented. */
* the selected base-band is well represented.
* @ingroup filters */
template <class Scalar>
class BaseBand: public Sink<Scalar>, public Source, public FreqShiftBase<Scalar>
{

@ -0,0 +1,111 @@
#include "baudot.hh"
#include "traits.hh"
#include "logger.hh"
using namespace sdr;
// Baudot code tables
char Baudot::_letter[32] = { 0, 'E','\n', 'A', ' ', 'S', 'I', 'U','\n', 'D', 'R', 'J', 'N', 'F',
'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', 0,
'M', 'X', 'V', 0};
char Baudot::_figure[32] = { 0, '3','\n', '-', ' ','\a', '8', '7','\n', '?', '4','\'', ',', '!',
':', '(', '5', '"', ')', '2', '#', '6', '0', '1', '9', '?', '&', 0,
'.', '/', ';', 0};
// Some special codes
#define CHAR_NUL 0
#define CHAR_STF 27
#define CHAR_STL 31
#define CHAR_SPA 4
Baudot::Baudot(StopBits stopBits)
: Sink<uint8_t>(), Source(), _mode(LETTERS)
{
switch (stopBits) {
case STOP1:
// Pattern xx11 xxxx xxxx xx00
// Mask 0011 0000 0000 0011
_stopHBits = 2;
_bitsPerSymbol = 14;
_pattern = 0x3000;
_mask = 0x3003;
break;
case STOP15:
// Pattern x11x xxxx xxxx x000
// Mask 0110 0000 0000 0111
_stopHBits = 3;
_bitsPerSymbol = 15;
_pattern = 0x6000;
_mask = 0x6007;
break;
case STOP2:
// Pattern 11xx xxxx xxxx 0000
// Mask 1100 0000 0000 1111
_stopHBits = 4;
_bitsPerSymbol = 16;
_pattern = 0xC000;
_mask = 0xC00F;
break;
}
}
void
Baudot::config(const Config &src_cfg) {
if (! src_cfg.hasType()) { return; }
// Check if buffer type matches
if (Config::typeId<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure Baudot: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<uint8_t>();
throw err;
}
// Init (half) bit stream and counter
_bitstream = 0;
_bitcount = 0;
// Compute buffer size.
size_t buffer_size = (src_cfg.bufferSize()/(2*_bitsPerSymbol))+1;
_buffer = Buffer<uint8_t>(buffer_size);
LogMessage msg(LOG_DEBUG);
msg << "Config Baudot node: " << std::endl
<< " input sample rate: " << src_cfg.sampleRate() << " half-bits/s" << std::endl
<< " start bits: " << 1 << std::endl
<< " stop bits: " << float(_stopHBits)/2 << std::endl;
Logger::get().log(msg);
// propergate config
this->setConfig(Config(Traits<uint8_t>::scalarId, 0, buffer_size, 1));
}
void
Baudot::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
{
size_t o=0;
for (size_t i=0; i<buffer.size(); i++) {
_bitstream = (_bitstream << 1) | (buffer[i] & 0x1); _bitcount++;
// Check if symbol as received:
if ((_bitsPerSymbol <= _bitcount) && (_pattern == (_bitstream & _mask))) {
_bitcount = 0;
// Unpack 5bit baudot code
uint8_t code = 0;
for (int j=0; j<5; j++) {
int shift = _stopHBits + 2*j;
code |= (((_bitstream>>shift)&0x01)<<j);
}
// Decode to ASCII
if (CHAR_STL == code) { _mode = LETTERS; }
else if (CHAR_STF == code) { _mode = FIGURES; }
else {
if (CHAR_SPA == code) { _mode = LETTERS; }
if (LETTERS == _mode) { _buffer[o++] = _letter[code]; }
else { _buffer[o++] = _figure[code]; }
}
}
}
if (0 < o) { this->send(_buffer.head(o)); }
}

@ -0,0 +1,72 @@
#ifndef __SDR_BAUDOT_HH__
#define __SDR_BAUDOT_HH__
#include "node.hh"
#include <map>
namespace sdr {
/** Implements a Baudot decoder. Inconjecture with the (A)FSK demodulator, it enables the
* reception of radio teletype (RTTY) messages.
*
* Please note that a baudot encoded char is usually transmitted in a frame with one start bit and
* 1, 1.5 or 2 stop bits. Hence this node expects to receive two bits for one decoded bit in order
* to detect the 1.5 stop bits reliably.
*
* I.e. to receive a 45.45 baud RTTY signal, the (A)FSK demodulator need to be configured for
* 90.90 baud (= 2*45.45 baud).
* @ingroup datanodes */
class Baudot: public Sink<uint8_t>, public Source
{
public:
/** Specifies the current code-tables. */
typedef enum {
LETTERS, ///< Letters.
FIGURES ///< Numbers, symbols etc.
} Mode;
/** Specifies the number of stop bits. */
typedef enum {
STOP1, ///< 1 stop bit.
STOP15, ///< 1.5 stop bits.
STOP2 ///< 2 stop bits.
} StopBits;
public:
/** Constructor. */
Baudot(StopBits stopBits = STOP15);
/** Configures the node. */
virtual void config(const Config &src_cfg);
/** Processes the bit-stream. */
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
protected:
/** Code table for letters. */
static char _letter[32];
/** Code table for symbols or figure (i.e. numbers). */
static char _figure[32];
/** The last bits received. */
uint16_t _bitstream;
/** The number of bits received. */
size_t _bitcount;
/** The currently selected table. */
Mode _mode;
/** Specifies the number of half bits per symbol. */
size_t _bitsPerSymbol;
/** Specifies the frame pattern. */
uint16_t _pattern;
/** Specifies the frame mask. */
uint16_t _mask;
/** Number of half bits forming the stop bit. */
uint16_t _stopHBits;
/** The output buffer. */
Buffer<uint8_t> _buffer;
};
}
#endif // BAUDOT_HH

@ -0,0 +1,213 @@
#include "bch31_21.hh"
#include "stdlib.h"
#include "string.h"
using namespace sdr;
/*
* the code used by POCSAG is a (n=31,k=21) BCH Code with dmin=5,
* thus it could correct two bit errors in a 31-Bit codeword.
* It is a systematic code.
* The generator polynomial is:
* g(x) = x^10+x^9+x^8+x^6+x^5+x^3+1
* The parity check polynomial is:
* h(x) = x^21+x^20+x^18+x^16+x^14+x^13+x^12+x^11+x^8+x^5+x^3+1
* g(x) * h(x) = x^n+1
*/
#define BCH_POLY 03551 /* octal */
#define BCH_N 31
#define BCH_K 21
static inline unsigned char even_parity(uint32_t data)
{
unsigned int temp = data ^ (data >> 16);
temp = temp ^ (temp >> 8);
temp = temp ^ (temp >> 4);
temp = temp ^ (temp >> 2);
temp = temp ^ (temp >> 1);
return temp & 1;
}
static unsigned int
pocsag_syndrome(uint32_t data)
{
uint32_t shreg = data >> 1; /* throw away parity bit */
uint32_t mask = 1L << (BCH_N-1), coeff = BCH_POLY << (BCH_K-1);
int n = BCH_K;
for(; n > 0; mask >>= 1, coeff >>= 1, n--) {
if (shreg & mask) { shreg ^= coeff; }
}
if (even_parity(data)) {
shreg |= (1 << (BCH_N - BCH_K));
}
return shreg;
}
static void
bitslice_syndrome(uint32_t *slices)
{
const int firstBit = BCH_N - 1;
int i, n;
uint32_t paritymask = slices[0];
// do the parity and shift together
for (i = 1; i < 32; ++i) {
paritymask ^= slices[i];
slices[i-1] = slices[i];
}
slices[31] = 0;
// BCH_POLY << (BCH_K - 1) is
// 20 21 22 23
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ONE, 0, 0, ONE,
// 24 25 26 27 28 29 30 31
// 0, ONE, ONE, 0, ONE, ONE, ONE, 0
for (n = 0; n < BCH_K; ++n) {
// one line here for every '1' bit in coeff (above)
const int bit = firstBit - n;
slices[20 - n] ^= slices[bit];
slices[23 - n] ^= slices[bit];
slices[25 - n] ^= slices[bit];
slices[26 - n] ^= slices[bit];
slices[28 - n] ^= slices[bit];
slices[29 - n] ^= slices[bit];
slices[30 - n] ^= slices[bit];
slices[31 - n] ^= slices[bit];
}
// apply the parity mask we built up
slices[BCH_N - BCH_K] |= paritymask;
}
static uint32_t
transpose_n(int n, uint32_t *matrix)
{
uint32_t out = 0;
int j;
for (j = 0; j < 32; ++j) {
if (matrix[j] & (1<<n)) {
out |= (1<<j);
}
}
return out;
}
static uint32_t *
transpose_clone(uint32_t src, uint32_t *out)
{
int i;
if (!out) { out = (uint32_t *)malloc(sizeof(uint32_t)*32); }
for (i = 0; i < 32; ++i) {
if (src & (1<<i)) {
out[i] = 0xffffffff;
} else {
out[i] = 0;
}
}
return out;
}
// This might not be elegant, yet effective!
// Error correction via bruteforce ;)
//
// It's a pragmatic solution since this was much faster to implement
// than understanding the math to solve it while being as effective.
// Besides that the overhead is neglectable.
int
sdr::pocsag_repair(uint32_t &data)
{
// Check if data is correct
if (0 == pocsag_syndrome(data)) { return 0; }
int i, n, b1, b2;
uint32_t res;
uint32_t *xpose = 0, *in = 0;
// check for single bit errors
xpose = (uint32_t *) malloc(sizeof(uint32_t)*32);
in = (uint32_t *) malloc(sizeof(uint32_t)*32);
transpose_clone(data, xpose);
for (i = 0; i < 32; ++i) { xpose[i] ^= (1<<i); }
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
res = ~res;
if (res) {
int n = 0;
while (res) { ++n; res >>= 1; }
--n;
data ^= (1<<n);
goto returnfree;
}
//check for two bit errors
n = 0;
transpose_clone(data, xpose);
for (b1 = 0; b1 < 32; ++b1) {
for (b2 = b1; b2 < 32; ++b2) {
xpose[b1] ^= (1<<n);
xpose[b2] ^= (1<<n);
if (++n == 32) {
memcpy(in, xpose, sizeof(uint32_t)*32);
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
res = ~res;
if (res) {
int n = 0;
while (res) { ++n; res >>= 1; }
--n;
data = transpose_n(n, in);
goto returnfree;
}
transpose_clone(data, xpose);
n = 0;
}
}
}
if (n > 0) {
memcpy(in, xpose, sizeof(uint32_t)*32);
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
res = ~res;
if (res) {
int n = 0;
while (res) { ++n; res >>= 1; }
--n;
data = transpose_n(n, in);
goto returnfree;
}
}
if (xpose) { free(xpose); }
if (in) { free(in); }
return 1;
returnfree:
if (xpose)
free(xpose);
if (in)
free(in);
return 0;
}

@ -0,0 +1,14 @@
#ifndef __SDR_BCH31_21_HH__
#define __SDR_BCH31_21_HH__
#include <inttypes.h>
namespace sdr {
/** Checks and repairs a POCSAG message with its
* BCH(31,21) ECC. */
int pocsag_repair(uint32_t &data);
}
#endif // __SDR_BCH31_21_HH__

@ -25,7 +25,9 @@ RawBuffer::RawBuffer(size_t N, BufferOwner *owner)
_refcount((int *)malloc(sizeof(int))), _owner(owner)
{
// Check if data could be allocated
if ((0 == _ptr) && (0 != _refcount)) { free(_refcount); _refcount = 0; _storage_size = 0; return; }
if ((0 == _ptr) && (0 != _refcount)) {
free(_refcount); _refcount = 0; _storage_size = 0; return;
}
// Set refcount, done...
if (_refcount) { (*_refcount) = 1; }
}
@ -62,7 +64,7 @@ void RawBuffer::unref() {
// If empty -> skip...
if ((0 == _ptr) || (0 == _refcount)) { return; }
// Decrement refcount
(*_refcount) -= 1;
(*_refcount)--;
// If there is only one reference left and the buffer is owned -> notify owner, who holds the last
// reference.
if ((1 == (*_refcount)) && (_owner)) { _owner->bufferUnused(*this); }

@ -82,7 +82,7 @@ public:
void unref();
/** Returns the reference counter. */
inline int refCount() const { if (0 == _refcount) { return 0; } return (*_refcount); }
/** We assume here that buffers are owned by some object: A buffer is therefore "unused" if the
/** We assume here that buffers are owned by one object: A buffer is therefore "unused" if the
* owner holds the only reference to the buffer. */
inline bool isUnused() const {
if (0 == _refcount) { return true; }

@ -12,7 +12,8 @@
namespace sdr {
/** A simple buffering node, that ensures a fixed buffer size. This node is useful, expecially in
* front of a node that performs a FFT transform, which requires a certain buffer size. */
* front of a node that performs a FFT transform, which requires a certain buffer size.
* @ingroup datanodes */
template <class Scalar>
class BufferNode : public Sink<Scalar>, public Source
{

@ -12,7 +12,7 @@ namespace sdr {
template <class Scalar> class Combine;
/** A single sink of a Combine node. */
/** A single sink of a Combine node. Do not use this node explicitly, consider using @c Combine. */
template <class Scalar>
class CombineSink: public Sink<Scalar>
{
@ -60,7 +60,8 @@ protected:
};
/** A combine node. This node allows to combine several streams into one. */
/** A combine node. This node allows to combine several streams into one.
* @ingroup datanodes */
template <class Scalar>
class Combine
{
@ -78,9 +79,11 @@ public:
virtual ~Combine() {
// Unref all buffers and free sinks
for (size_t i=0; i<_sinks.size(); i++) {
_buffers[i].unref(); delete _sinks[i];
delete _sinks[i];
_buffers[i].unref();
}
_buffers.clear(); _sinks.clear();
_buffers.clear();
_sinks.clear();
}
/** Needs to be overridden. */
@ -147,7 +150,8 @@ protected:
};
/** Interleaves several input streams. */
/** Interleaves several input streams.
* @ingroup datanodes */
template <class Scalar>
class Interleave : public Combine<Scalar>, public Source
{

@ -12,7 +12,8 @@
namespace sdr {
/** Amplitude modulation (AM) demodulator from an I/Q signal. */
/** Amplitude modulation (AM) demodulator from an I/Q signal.
* @ingroup demods */
template <class Scalar>
class AMDemod
: public Sink< std::complex<Scalar> >, public Source
@ -26,7 +27,8 @@ public:
/** Destructor. */
virtual ~AMDemod() {
// pass...
// free buffers
_buffer.unref();
}
/** Configures the AM demod. */
@ -40,6 +42,9 @@ public:
<< ", expected " << Config::typeId< std::complex<Scalar> >();
throw err;
}
// Unreference previous buffer
_buffer.unref();
// Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
@ -59,16 +64,6 @@ public:
/** Handles the I/Q input buffer. */
virtual void process(const Buffer<std::complex<Scalar> > &buffer, bool allow_overwrite)
{
// Drop buffer if output buffer is still in use:
if (! _buffer.isUnused()) {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << __FILE__ << ": Output buffer still in use: Drop received buffer...";
Logger::get().log(msg);
return;
#endif
}
Buffer<Scalar> out_buffer;
// If source allow to overwrite the buffer, use it otherwise rely on own buffer
if (allow_overwrite) { out_buffer = Buffer<Scalar>(buffer); }
@ -91,7 +86,8 @@ protected:
};
/** SSB upper side band (USB) demodulator from an I/Q signal. */
/** SSB upper side band (USB) demodulator from an I/Q signal.
* @ingroup demods */
template <class Scalar>
class USBDemod
: public Sink< std::complex<Scalar> >, public Source
@ -111,7 +107,7 @@ public:
/** Destructor. */
virtual ~USBDemod() {
// pass...
_buffer.unref();
}
/** Configures the USB demodulator. */
@ -125,6 +121,9 @@ public:
<< ", expected " << Config::typeId<CScalar>();
throw err;
}
// Unreference previous buffer
_buffer.unref();
// Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
@ -146,16 +145,9 @@ public:
if (allow_overwrite) {
// Process in-place
_process(buffer, Buffer<Scalar>(buffer));
} else if (_buffer.isUnused()) {
} else {
// Store result in buffer
_process(buffer, _buffer);
} else {
// Drop buffer
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "SSBDemod: Drop buffer.";
Logger::get().log(msg);
#endif
}
}
@ -175,7 +167,10 @@ protected:
/** Demodulates FM from an I/Q signal. */
/** Demodulates FM from an I/Q signal.
* This node only implements the demodulation of the signal, the needed post-filtering (deemphasize)
* is implemented in a separate node, @c sdr::FMDeemph.
* @ingroup demods */
template <class iScalar, class oScalar=iScalar>
class FMDemod: public Sink< std::complex<iScalar> >, public Source
{
@ -193,7 +188,7 @@ public:
/** Destructor. */
virtual ~FMDemod() {
// pass...
_buffer.unref();
}
/** Configures the FM demodulator. */
@ -237,14 +232,8 @@ public:
if (allow_overwrite && _can_overwrite) {
_process(buffer, Buffer<oScalar>(buffer));
} else if (_buffer.isUnused()) {
_process(buffer, _buffer);
} else {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "FMDemod: Drop buffer: Output buffer still in use.";
Logger::get().log(msg);
#endif
_process(buffer, _buffer);
}
}
@ -252,34 +241,14 @@ protected:
/** The actual demodulation. */
void _process(const Buffer< std::complex<iScalar> > &in, const Buffer<oScalar> &out)
{
// The last input value
std::complex<iScalar> last_value = _last_value;
// calc first value
SScalar a = (SScalar(in[0].real())*SScalar(last_value.real()))/2
+ (SScalar(in[0].imag())*SScalar(last_value.imag()))/2;
SScalar b = (SScalar(in[0].imag())*SScalar(last_value.real()))/2
- (SScalar(in[0].real())*SScalar(last_value.imag()))/2;
a >>= Traits<iScalar>::shift; b >>= Traits<iScalar>::shift;
// update last value
last_value = in[0];
// calc output (prob. overwriting the last value)
out[0] = fast_atan2<iScalar, oScalar>(a, b);
//out[0] = (1<<12)*(std::atan2(float(a),float(b))/M_PI);
// Calc remaining values
for (size_t i=1; i<in.size(); i++) {
a = (SScalar(in[i].real())*SScalar(last_value.real()))/2
+ (SScalar(in[i].imag())*SScalar(last_value.imag()))/2;
b = (SScalar(in[i].imag())*SScalar(last_value.real()))/2
- (SScalar(in[i].real())*SScalar(last_value.imag()))/2;
a >>= Traits<iScalar>::shift; b >>= Traits<iScalar>::shift;
last_value = in[i];
out[i] = fast_atan2<iScalar,oScalar>(a, b);
//out[i] = (1<<12)*(std::atan2(float(a),float(b))/M_PI);
oScalar phi = fast_atan2<iScalar, oScalar>(in[i].real(), in[i].imag())/2;
// dphi
out[i] = (_last_value - phi);
_last_value = phi;
}
// Store last value
_last_value = last_value;
// propergate result
this->send(out.head(in.size()));
}
@ -288,8 +257,8 @@ protected:
protected:
/** Output rescaling. */
int _shift;
/** The last input value. */
std::complex<iScalar> _last_value;
/** The last angle. */
oScalar _last_value;
/** If true, in-place demodulation is poissible. */
bool _can_overwrite;
/** The output buffer, unused if demodulation is performed in-place. */
@ -297,7 +266,8 @@ protected:
};
/** A tiny node to de-emphasize the higher frequencies of a FM transmitted audio signal. */
/** A tiny node to de-emphasize the higher frequencies of a FM transmitted audio signal.
* @ingroup filters */
template <class Scalar>
class FMDeemph: public Sink<Scalar>, public Source
{
@ -311,7 +281,7 @@ public:
/** Destructor. */
virtual ~FMDeemph() {
// pass...
_buffer.unref();
}
/** Returns true if the filter node is enabled. */
@ -336,8 +306,17 @@ public:
1.0/( (1.0-exp(-1.0/(src_cfg.sampleRate() * 75e-6) )) ) );
// Reset average:
_avg = 0;
// Unreference previous buffer
_buffer.unref();
// Allocate buffer:
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Configured FMDDeemph node: " << this << std::endl
<< " sample-rate: " << src_cfg.sampleRate() << std::endl
<< " type: " << src_cfg.type();
Logger::get().log(msg);
// Propergate config:
this->setConfig(Config(src_cfg.type(), src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
}

@ -47,7 +47,10 @@ public:
}
/** Destructor. */
virtual ~FilterSink() { }
virtual ~FilterSink() {
_in_buffer.unref();
_out_buffer.unref();
}
/** Configures the node. */
virtual void config(const Config &src_cfg) {
@ -223,7 +226,8 @@ protected:
};
/** A FFT filter bank node wich consists of several filters. */
/** A FFT filter bank node wich consists of several filters.
* @ingroup filters */
template <class Scalar>
class FilterNode
{

@ -111,7 +111,8 @@ public:
/** Generic FIR filter class. Use one of the specializations below for a low-, high- or band-pass
* filter. */
* filter.
* @ingroup filters */
template <class Scalar, class FilterCoeffs>
class FIRFilter: public Sink<Scalar>, public Source
{
@ -269,7 +270,8 @@ protected:
};
/** Low-pass FIR filter specialization. */
/** Low-pass FIR filter specialization.
* @ingroup filters */
template <class Scalar>
class FIRLowPass: public FIRFilter<Scalar, FIRLowPassCoeffs>
{
@ -285,7 +287,8 @@ public:
inline void setFreq(double freq) { FIRFilter<Scalar, FIRLowPassCoeffs>::setUpperFreq(freq); }
};
/** High-pass FIR filter specialization. */
/** High-pass FIR filter specialization.
* @ingroup filters */
template <class Scalar>
class FIRHighPass: public FIRFilter<Scalar, FIRHighPassCoeffs>
{
@ -302,7 +305,8 @@ public:
};
/** Band-pass FIR filter specialization. */
/** Band-pass FIR filter specialization.
* @ingroup filters */
template <class Scalar>
class FIRBandPass: public FIRFilter<Scalar, FIRBandPassCoeffs>
{
@ -314,7 +318,8 @@ public:
virtual ~FIRBandPass() { }
};
/** Band-stop FIR filter specialization. */
/** Band-stop FIR filter specialization.
* @ingroup filters */
template <class Scalar>
class FIRBandStop: public FIRFilter<Scalar, FIRBandStopCoeffs>
{

@ -8,7 +8,8 @@
namespace sdr {
/** A performant implementation of a frequency shift operation on integer signals. */
/** A performant implementation of a frequency-shift operation on integer signals.
* @ingroup filters */
template <class Scalar>
class FreqShiftBase
{
@ -60,12 +61,15 @@ public:
if (0 == _lut_inc) { return value; }
// Get index, idx = (_lut_count/256)
size_t idx = (_lut_count>>8);
// Handle negative frequency shifts
if (0 > _freq_shift) { idx = _lut_size - idx - 1; }
// Apply
value = ((_lut[idx] * value) >> Traits<Scalar>::shift);
// Incement _lut_count
_lut_count += _lut_inc;
// _lut_count modulo (_lut_size*256)
while (_lut_count >= (_lut_size<<8)) { _lut_count -= (_lut_size<<8); }
// Done.
return value;
}
@ -75,7 +79,7 @@ protected:
// Every sample increments the LUT index by lut_inc/256.
// The multiple is needed as ratio between the frequency shift _Fc and the sample rate _Fs
// may not result in an integer increment. By simply flooring _lut_size*_Fc/_Fs, the actual
// down conversion may be much smaller than actual reuqired. Hence, the counter in therefore
// down conversion may be much smaller than actual reuqired. Hence, the counter is therefore
// incremented by the integer (256*_lut_size*_Fc/_Fs) and the index is then obtained by
// dividing _lut_count by 256 (right shift 8 bits).
_lut_inc = (_lut_size*(1<<8)*std::abs(_freq_shift))/_Fs;

@ -0,0 +1,236 @@
#include "fsk.hh"
#include "logger.hh"
#include "traits.hh"
#include "interpolate.hh"
using namespace sdr;
/* ******************************************************************************************** *
* Implementation of FSKDetector
* ******************************************************************************************** */
FSKDetector::FSKDetector(float baud, float Fmark, float Fspace)
: Sink<int16_t>(), Source(), _baud(baud), _corrLen(0), _Fmark(Fmark), _Fspace(Fspace)
{
// pass...
}
void
FSKDetector::config(const Config &src_cfg)
{
// Check if config is complete
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Check if buffer type matches
if (Config::typeId<int16_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure FSKBase: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
throw err;
}
_corrLen = int(src_cfg.sampleRate()/_baud);
_markLUT = Buffer< std::complex<float> >(_corrLen);
_spaceLUT = Buffer< std::complex<float> >(_corrLen);
_markHist = Buffer< std::complex<float> >(_corrLen);
_spaceHist = Buffer< std::complex<float> >(_corrLen);
// Initialize LUTs and ring-buffers
double phiMark=0, phiSpace=0;
for (size_t i=0; i<_corrLen; i++) {
_markLUT[i] = std::exp(std::complex<float>(0.0, phiMark));
_spaceLUT[i] = std::exp(std::complex<float>(0.0, phiSpace));
phiMark += (2.*M_PI*_Fmark)/src_cfg.sampleRate();
phiSpace += (2.*M_PI*_Fspace)/src_cfg.sampleRate();
// Apply Window functions
//_markLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen));
//_spaceLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen));
_markHist[i] = 0; _spaceHist[i] = 0;
}
// Ring buffer index
_lutIdx = 0;
// Allocate output buffer
_buffer = Buffer<int8_t>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Config FSKDetector node: " << std::endl
<< " sample/symbol rate: " << src_cfg.sampleRate() << " Hz" << std::endl
<< " target baud rate: " << _baud << std::endl
<< " approx. samples per bit: " << _corrLen;
Logger::get().log(msg);
// Forward config.
this->setConfig(Config(Traits<uint8_t>::scalarId, src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
}
uint8_t
FSKDetector::_process(int16_t sample) {
_markHist[_lutIdx] = float(sample)*_markLUT[_lutIdx];
_spaceHist[_lutIdx] = float(sample)*_spaceLUT[_lutIdx];
// inc _lutIdx, modulo LUT length
_lutIdx++; if (_lutIdx==_corrLen) { _lutIdx=0; }
std::complex<float> markSum(0), spaceSum(0);
for (size_t i=0; i<_corrLen; i++) {
markSum += _markHist[i];
spaceSum += _spaceHist[i];
}
float f = markSum.real()*markSum.real() +
markSum.imag()*markSum.imag() -
spaceSum.real()*spaceSum.real() -
spaceSum.imag()*spaceSum.imag();
return (f>0);
}
void
FSKDetector::process(const Buffer<int16_t> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = _process(buffer[i]);
}
this->send(_buffer.head(buffer.size()), false);
}
/* ******************************************************************************************** *
* Implementation of BitStream
* ******************************************************************************************** */
BitStream::BitStream(float baud, Mode mode)
: Sink<uint8_t>(), Source(), _baud(baud), _mode(mode), _corrLen(0)
{
// pass...
}
void
BitStream::config(const Config &src_cfg) {
// Check if config is complete
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Check if buffer type matches
if (Config::typeId<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure BitStream: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
throw err;
}
// # of symbols for each bit
_corrLen = int(src_cfg.sampleRate()/_baud);
// Config PLL for bit detection
_phase = 0;
// exact bits per sample (<< 1)
_omega = _baud/src_cfg.sampleRate();
// PLL limits +/- 10% around _omega
_omegaMin = _omega - 0.005*_omega;
_omegaMax = _omega + 0.005*_omega;
// PLL gain
_pllGain = 0.0005;
// symbol moving average
_symbols = Buffer<int8_t>(_corrLen);
for (size_t i=0; i<_corrLen; i++) { _symbols[i] = 0; }
_symIdx = 0; _symSum = 0; _lastSymSum = 0;
// Reset bit hist
_lastBits = 0;
// Allocate output buffer
_buffer = Buffer<uint8_t>(1+src_cfg.bufferSize()/_corrLen);
LogMessage msg(LOG_DEBUG);
msg << "Config BitStream node: " << std::endl
<< " symbol rate: " << src_cfg.sampleRate() << " Hz" << std::endl
<< " baud rate: " << _baud << std::endl
<< " symbols/bit: " << 1./_omega << std::endl
<< " bit mode: " << ( (NORMAL == _mode) ? "normal" : "transition" );
Logger::get().log(msg);
// Forward config.
this->setConfig(Config(Traits<uint8_t>::scalarId, _baud, _buffer.size(), 1));
}
void
BitStream::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
{
size_t o=0;
for (size_t i=0; i<buffer.size(); i++)
{
// store symbol & update _symSum and _lastSymSum
_lastSymSum = _symSum;
_symSum -= _symbols[_symIdx];
_symbols[_symIdx] = ( buffer[i] ? 1 : -1 );
_symSum += _symbols[_symIdx];
_symIdx = ((_symIdx+1) % _corrLen);
// Advance phase
_phase += _omega;
// Sample bit ...
if (_phase >= 1) {
// Modulo "2 pi", phase is defined on the interval [0,1)
while (_phase>=1) { _phase -= 1; }
// Estimate bit by majority vote on all symbols (_symSum)
_lastBits = ((_lastBits<<1) | (_symSum>0));
// Put decoded bit in output buffer
if (TRANSITION == _mode) {
// transition -> 0; no transition -> 1
_buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 0x1) & 0x1);
} else {
// mark -> 1, space -> 0
_buffer[o++] = _lastBits & 0x1;
}
}
// If there was a symbol transition
if (((_lastSymSum < 0) && (_symSum>=0)) || ((_lastSymSum >= 0) && (_symSum<0))) {
// Phase correction
// transition at [-pi,0] -> increase omega
if (_phase < 0.5) { _omega += _pllGain*(0.5-_phase); }
// transition at [0,pi] -> decrease omega
else { _omega -= _pllGain*(_phase-0.5); }
// Limit omega
_omega = std::min(_omegaMax, std::max(_omegaMin, _omega));
}
}
if (o>0) { this->send(_buffer.head(o)); }
}
/* ******************************************************************************************** *
* Implementation of BitDump
* ******************************************************************************************** */
BitDump::BitDump(std::ostream &stream)
: Sink<uint8_t>(), _stream(stream)
{
// pass...
}
void
BitDump::config(const Config &src_cfg) {
// Check if config is complete
if (!src_cfg.hasType()) { return; }
// Check if buffer type matches
if (Config::typeId<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure BitDump: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
throw err;
}
}
void
BitDump::process(const Buffer<uint8_t> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
_stream << int(buffer[i]) << " ";
}
_stream << std::endl;
}

@ -0,0 +1,192 @@
#ifndef __SDR_FSK_HH__
#define __SDR_FSK_HH__
#include "node.hh"
#include "traits.hh"
#include "logger.hh"
namespace sdr {
/** Implements the basic FSK/AFSK symbol detection.
* This node contains two FIR filters for the detection of the mark and space frequencies. The node
* returns a sequence of symbols (i.e. sub-bits) which need to be processed to obtain a sequenc of
* transmitted bits (i.e. by the @c BitStream node).
*
* @ingroup demods */
class FSKDetector: public Sink<int16_t>, public Source
{
public:
/** Constructor.
* @param baud Specifies the baud-rate of the signal.
* @param Fmark Specifies the mark frequency in Hz.
* @param Fspace Specifies the space frequency in Hz. */
FSKDetector(float baud, float Fmark, float Fspace);
void config(const Config &src_cfg);
void process(const Buffer<int16_t> &buffer, bool allow_overwrite);
protected:
/** Updates the mark/space FIR filter and returns the sampled symbol. */
uint8_t _process(int16_t sample);
protected:
/** Baudrate of the transmission. Needed to compute the filter length of the FIR mark/space
* filters. */
float _baud;
/** The filter lenght. */
size_t _corrLen;
/** The current FIR filter LUT index. */
size_t _lutIdx;
/** Mark "tone" frequency. */
float _Fmark;
/** Space "tone" frequency. */
float _Fspace;
/** Mark frequency FIR filter LUT. */
Buffer< std::complex<float> > _markLUT;
/** Space frequency FIR filter LUT. */
Buffer< std::complex<float> > _spaceLUT;
/** FIR filter buffer. */
Buffer< std::complex<float> > _markHist;
/** FIR filter buffer. */
Buffer< std::complex<float> > _spaceHist;
/** Output buffer. */
Buffer<int8_t> _buffer;
};
/** Rather trivial node to detect mark/space symbols by the amplitude.
* For low baud rates (i.e. <= 1200 baud) a FSK signal can be "demodulated" using a
* simple FM demodulator. The result will be a series of decaying exponentials. Hence the
* mark/space symbols can be determined by the means of the input amplitude (positive/negative).
*
* This node implements such a simple symbol detection by the means of the amplitude. The node
* returns a sequence of symbols (sub-bits) that need to be processed to obtain the sequence of
* received bits (i.e. @c BitStream).
*
* @ingroup demods */
template <class Scalar>
class ASKDetector: public Sink<Scalar>, public Source
{
public:
/** Constructor. */
ASKDetector(bool invert=false)
: Sink<Scalar>(), Source(), _invert(invert)
{
// pass...
}
void config(const Config &src_cfg) {
// Check if config is complete
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Check if buffer type matches
if (Config::typeId<int16_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure ASKDetector: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
throw err;
}
// Allocate output buffer
_buffer = Buffer<uint8_t>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Config ASKDetector node: " << std::endl
<< " threshold: " << 0 << std::endl
<< " invert: " << ( _invert ? "yes" : "no" ) << std::endl
<< " symbol rate: " << src_cfg.sampleRate() << " Hz";
Logger::get().log(msg);
// Forward config.
this->setConfig(Config(Traits<uint8_t>::scalarId, src_cfg.sampleRate(), _buffer.size(), 1));
}
void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = ((buffer[i]>0)^_invert);
}
this->send(_buffer.head(buffer.size()), false);
}
protected:
/** If true the symbol logic is inverted. */
bool _invert;
/** The output buffer. */
Buffer<uint8_t> _buffer;
};
/** Decodes a bitstream with the desired baud rate.
* This node implements a simple PLL to syncronize the bit sampling with the transitions
* of the input symbol sequence. */
class BitStream: public Sink<uint8_t>, public Source
{
public:
/** Possible bit decoding modes. */
typedef enum {
NORMAL, ///< Normal mode (i.e. mark -> 1, space -> 0).
TRANSITION ///< Transition mode (i.e. transition -> 0, no transition -> 1).
} Mode;
public:
/** Constructor.
* @param baud Specifies the baud-rate of the input signal.
* @param mode Specifies the bit detection mode. */
BitStream(float baud, Mode mode = TRANSITION);
void config(const Config &src_cfg);
void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
protected:
/** The baud rate. */
float _baud;
/** The bit detection mode. */
Mode _mode;
/** The approximative bit length in samples. */
size_t _corrLen;
/** Last received symbols. */
Buffer<int8_t> _symbols;
/** Insertion index for the next symbol. */
size_t _symIdx;
/** Sum over all received symbol (encoded as -1 & 1). */
int32_t _symSum;
/** Last sum over all received symbol (encoded as -1 & 1). */
int32_t _lastSymSum;
/** Current bit "phase". */
float _phase;
/** Phase velocity. */
float _omega;
/** Minimum phase velocity. */
float _omegaMin;
/** Maximum phase velocity. */
float _omegaMax;
/** PLL gain. */
float _pllGain;
/** The last decoded bits (needed for transition mode). */
uint8_t _lastBits;
/** Output buffer. */
Buffer<uint8_t> _buffer;
};
/** Trivial node to dump a bit-stream to a std::ostream.
* @ingroup sinks */
class BitDump : public Sink<uint8_t>
{
public:
/** Constructor.
* @param stream Specifies the output stream. */
BitDump(std::ostream &stream);
void config(const Config &src_cfg);
void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
protected:
/** The output stream. */
std::ostream &_stream;
};
}
#endif // __SDR_FSK_HH__

File diff suppressed because it is too large Load Diff

@ -0,0 +1,627 @@
/** @defgroup http A rather trivia HTTP daemon implementation.
*
* This module collects some classes allowing to implement a simple HTTP server to serve static
* and dynamic content. The central class is the @c Server class which dispatches incomming
* requests to the registered @c Handler instances.
*
* There are several specializations of the @c Handler class avaliable suited to perform specific
* tasks. I.e. the @c StaticHandler serves some static content (i.e. strings) while the
* @c DelegateJSONHandler allows to implement a REST api easily.
*
* An example of a server, serving a static index page and provides a trivial JSON echo method.
* \code
* #include "http.hh"
* #include "logger.hh"
*
* using namespace sdr;
*
* // Implements an application, a collection of methods being called by the http::Server.
* class Application {
* public:
* // contstructor.
* Application() {}
*
* // The callback to handle the JSON echo api.
* bool echo(const http::JSON &request, http::JSON &result) {
* // just echo
* result = request;
* // signal success
* return true;
* }
* };
*
* // Static content
* const char *index_html = "<html> ... </html>";
*
*
* int main(int argc, char *argv[]) {
* // install log handler
* sdr::Logger::get().addHandler(
* new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
*
* // serve on port 8080
* http::Server server(8080);
*
* // Instantiate application
* Application app;
*
* // Register static content handlers
* server.addStatic("/", index_html, "text/html");
* // Register JSON echo method
* server.addJSON("/echo", &app, &Application::echo);
*
* // Start server
* server.start(true);
*
* return 0;
* }
* \endcode
*/
#ifndef __SDR_HTTPD_HH__
#define __SDR_HTTPD_HH__
#include <string>
#include <sstream>
#include <map>
#include <set>
#include <list>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
namespace sdr {
namespace http {
// Forward declarations
class Server;
/** Represents a JSON object.
* JSON is a popular means to implement remote procedure calls (RPCs) using java script. JSON is
* valid java script code and allows to transfer numbers, string, lists and objects.
* @ingroup http */
class JSON
{
public:
/** Specifies the possible json types. */
typedef enum {
EMPTY, ///< null.
BOOLEAN, ///< A boolean value.
NUMBER, ///< A number.
STRING, ///< A string.
ARRAY, ///< An array or list.
TABLE ///< A table or object.
} Type;
public:
/** Empty constructor (null). */
JSON();
/** Constructs a boolean value. */
JSON(bool value);
/** Constructs a number. */
JSON(double value);
/** Constructs a string. */
JSON(const std::string &value);
/** Constructs a list. */
JSON(const std::list<JSON> &list);
/** Constructs a table. */
JSON(const std::map<std::string, JSON> &table);
/** Copy constructor (deep copy). */
JSON(const JSON &other);
/** Destructor. */
~JSON();
/** Assignment operator (deep copy). */
JSON &operator=(const JSON &other);
/** Retruns @c true for the null. */
inline bool isNull() const { return EMPTY == _type; }
/** Returns @c true if this is a boolean value. */
inline bool isBoolean() const { return BOOLEAN == _type; }
/** Returns the boolean value. */
inline const bool &asBoolean() const { return *_value.boolean; }
/** Returns @c true if this is a number. */
inline bool isNumber() const { return NUMBER == _type; }
/** Returns the number. */
inline const double &asNumber() const { return *_value.number; }
/** Returns @c true if this is a string. */
inline bool isString() const { return STRING == _type; }
/** Returns the string. */
inline const std::string &asString() const { return *_value.string; }
/** Returns @c true if this is an array. */
inline bool isArray() const { return ARRAY == _type; }
/** Retruns the array as a list. */
inline const std::list<JSON> &asArray() const { return *_value.list; }
/** Returns @c true if this is a table. */
inline bool isTable() const { return TABLE == _type; }
/** Returns the table. */
inline const std::map<std::string, JSON> &asTable() const { return *_value.table; }
/** Resets the JSON object to null. */
void clear();
/** Parses the given string. Retruns @c true on success. */
static bool parse(const std::string &text, JSON &obj);
/** Serializes the JSON object. */
void serialize(std::ostream &stream) const;
/** Serializes the JSON object. */
inline void serialize(std::string &text) const {
std::stringstream buffer; this->serialize(buffer);
text = buffer.str();
}
protected:
/** The type of the object. */
Type _type;
/** Union of value pointers. */
union {
/** The boolean value. */
bool *boolean;
/** The number. */
double *number;
/** The string. */
std::string *string;
/** The array. */
std::list<JSON> *list;
/** The table. */
std::map<std::string, JSON> *table;
} _value;
};
/** Lists the possible HTTP methods.
* @ingroup http */
typedef enum {
HTTP_UNKNOWN, ///< Unknown method. Results into an invalid request.
HTTP_GET, ///< The get method.
HTTP_HEAD, ///< The head method.
HTTP_POST ///< The post method.
} Method;
/** Lists the possible HTTP versions.
* @ingroup http */
typedef enum {
UNKNOWN_VERSION, ///< Unknown http version. Results into an invalid request.
HTTP_1_0, ///< HTTP/1.0
HTTP_1_1 ///< HTTP/1.1
} Version;
/** Represents a URL.
* @ingroup http */
class URL
{
public:
/** Empty constructor. */
URL();
/** Constructor from protocol, host and path. */
URL(const std::string &proto, const std::string &host, const std::string &path);
/** Copy constructor. */
URL(const URL &other);
/** Assignment operator. */
URL &operator=(const URL &other);
/** Parses a URL from the given string. */
static URL fromString(const std::string &url);
/** Serializes the URL into a string. */
std::string toString() const;
/** Encode a string. */
static std::string encode(const std::string &str);
/** Decodes a string. */
static std::string decode(const std::string &str);
/** Returns @c true if the URL specifies a protocol. */
inline bool hasProtocol() const { return (0 != _protocol.size()); }
/** Returns the protocol. */
inline const std::string protocol() const { return _protocol; }
/** Sets the protocol. */
inline void setProtocol(const std::string &proto) { _protocol = proto; }
/** Returns @c true if the URL specified a host name. */
inline bool hasHost() const { return (0 != _host.size()); }
/** Returns the host name. */
inline const std::string &host() const { return _host; }
/** Set the host name. */
inline void setHost(const std::string &host) { _host = host; }
/** Retruns the path of the URL. */
inline const std::string &path() const { return _path; }
/** Sets the path of the URL. */
inline void setPath(const std::string &path) { _path = path; }
/** Adds a query pair (key, value) to the URL. */
inline void addQuery(const std::string &name, const std::string &value) {
_query.push_back(std::pair<std::string, std::string>(name, value));
}
/** Returns the list of query (key, value) pairs. */
inline const std::list< std::pair<std::string, std::string> > &query() const {
return _query;
}
protected:
/** Holds the protocol. */
std::string _protocol;
/** Holds the host name. */
std::string _host;
/** Holds the path. */
std::string _path;
/** Holds the query pairs. */
std::list< std::pair<std::string, std::string> > _query;
};
struct ConnectionObj
{
public:
ConnectionObj(Server *server, int cli_socket);
~ConnectionObj();
ConnectionObj *ref();
void unref();
public:
/** A weak reference to the server instance. */
Server *server;
/** The connection socket. */
int socket;
/** If @c true (i.e. set by a handler), the http parser thread will exit without closing
* the connection. This allows to "take-over" the tcp connection to the client by the request
* handler. */
bool protocol_upgrade;
/** Reference counter. */
size_t refcount;
};
/** Implements a HTTP connection to a client.
* @c ingroup http */
class Connection
{
public:
/** Empty constructor. */
Connection();
/** Constructor. */
Connection(Server *server, int socket);
/** Copy constructor. Implements reference counting. */
Connection(const Connection &other);
/** Destructor. */
~Connection();
Connection &operator=(const Connection &other);
/** Closes the connection.
* If @c wait is @c true, the method will wait until the thread listening for incomming
* request joined. */
void close(bool wait=false);
/** Returns @c true if the connection is closed. */
bool isClosed() const;
/** Sets the protocol-update flag. */
inline void setProtocolUpgrade() const { _object->protocol_upgrade = true; }
inline bool protocolUpgrade() const { return _object->protocol_upgrade; }
inline ssize_t write(const void *data, size_t n) const {
if (0 == _object) { return -1; }
return ::write(_object->socket, data, n);
}
inline ssize_t read(void *data, size_t n) const {
if (0 == _object) { return -1; }
return ::read(_object->socket, data, n);
}
bool send(const std::string &data) const;
/** Main loop for incomming requests. */
void main();
protected:
ConnectionObj *_object;
};
/** Represents a HTTP request.
* @ingroup http */
class Request
{
public:
/** Constructor. */
Request(const Connection &connection);
/** Parses the HTTP request header, returns @c true on success. */
bool parse();
/** Return the connection to the client. */
inline const Connection &connection() const { return _connection; }
/** Returns @c true if the connection to the client is kept alive after the response
* has been send. */
bool isKeepAlive() const;
/** Returns @c true if the given header is present. */
bool hasHeader(const std::string &name) const;
/** Returns the value of the given header. */
std::string header(const std::string &name) const;
/** Returns @c true if the Content-Length header is present. */
inline bool hasContentLength() const { return hasHeader("Content-Length"); }
/** Returns the value of the Content-Length header. */
size_t contentLength() const { return atol(header("Content-Length").c_str()); }
/** Retruns the request method. */
inline Method method() const { return _method; }
/** Returns the request URL. */
inline const URL &url() const { return _url; }
/** Reads the complete body (if Content-Length header is present).
* Retruns @c true on success.*/
bool readBody(std::string &body) const;
protected:
/** The connection socket. */
Connection _connection;
/** The request method. */
Method _method;
/** The HTTP version. */
Version _version;
/** The request URL. */
URL _url;
/** The request headers. */
std::map<std::string, std::string> _headers;
};
/** Represents a HTTP response. */
class Response
{
public:
/** Defines all possible responses. */
typedef enum {
STATUS_OK = 200, ///< OK.
STATUS_BAD_REQUEST = 400, ///< Your fault.
STATUS_NOT_FOUND = 404, ///< Resource not found.
STATUS_SERVER_ERROR = 500 ///< My fault.
} Status;
public:
/** Constructor. */
Response(const Connection &connnection);
/** Return the connection to the client. */
inline const Connection &connection() const { return _connection; }
/** Specifies the response code. */
void setStatus(Status status);
/** Returns @c true if the response has the given header. */
bool hasHeader(const std::string &name) const;
/** Returns the value of the header. */
std::string header(const std::string &name) const;
/** Sets the header. */
void setHeader(const std::string &name, const std::string &value);
/** Helper function to set the content length. */
void setContentLength(size_t length);
/** Sends the response code and all defined headers. */
bool sendHeaders() const;
protected:
/** The socket over which the response will be send. */
Connection _connection;
/** The response code. */
Status _status;
/** The response headers. */
std::map<std::string, std::string> _headers;
/** If @c true, the connection will be closed after the response has been send. */
bool _close_connection;
};
/** Base class of all HTTP request handlers.
* @ingroup http */
class Handler
{
protected:
/** Hidden constructor. */
Handler();
public:
/** Destructor. */
virtual ~Handler();
/** Needs to be implemented to accept the given request.
* If this method returns @c true, the server will call the @c handle instance next
* in expectation that this instance will process the request. */
virtual bool match(const Request &request) = 0;
/** Needs to be implemented to process requests, which have been accepted by the @c match()
* method. */
virtual void handle(const Request &request, Response &response) = 0;
};
/** Serves some static content.
* @ingroup http */
class StaticHandler: public Handler
{
public:
/** Constructor.
* @param url Specifies the URL (path) of the static content.
* @param text Speciefies the content.
* @param mimeType Speficies the mime-type of the content. */
StaticHandler(const std::string &url, const std::string &text,
const std::string mimeType="text/text");
/** Destructor. */
virtual ~StaticHandler();
bool match(const Request &request);
void handle(const Request &request, Response &response);
protected:
/** Holds the URL (path) of the content. */
std::string _url;
/** Holds the mime-type of the content. */
std::string _mimeType;
/** Holds the content itself. */
std::string _text;
};
/** Utility class to provide a handler as a delegate.
* @ingroup http */
template <class T>
class DelegateHandler: public Handler
{
public:
/** Constructor.
* @param url Specifies the path of the handler.
* @param instance Specifies the instance of the delegate.
* @param func Specifies the method of the delegate instance being called. */
DelegateHandler(const std::string &url, T *instance, void (T::*func)(const Request &, Response &))
: Handler(), _url(url), _instance(instance), _callback(func)
{
// pass...
}
bool match(const Request &request) {
return this->_url == request.url().path();
}
void handle(const Request &request, Response &response) {
(this->_instance->*(this->_callback))(request, response);
}
protected:
/** Holds the path of the handler. */
std::string _url;
/** Holds the instance of the delegate. */
T *_instance;
/** Holds the method of the instance being called. */
void (T::*_callback)(const Request &, Response &);
};
/** Implements a specialized handler to ease the processing of JSON REST-APIs.
* @ingroup http */
class JSONHandler: public http::Handler
{
public:
/** Constructor.
* @param url Speficies the path of the method. */
JSONHandler(const std::string &url);
bool match(const http::Request &request);
void handle(const http::Request &request, http::Response &response);
/** Needs to be implemented to process the request and assemble the result.
* An error (400 BAD REQUEST) will be send if the function does not return @c true. */
virtual bool process(const JSON &request, JSON &result) = 0;
protected:
/** The URL of the method. */
std::string _url;
};
/** A delegate JSON handler.
* @ingroup http */
template <class T>
class DelegateJSONHandler: public JSONHandler
{
public:
/** Constructor. */
DelegateJSONHandler(const std::string &url, T *instance,
bool (T::*func)(const JSON &request, JSON &response))
: JSONHandler(url), _instance(instance), _callback(func)
{
// pass...
}
virtual bool process(const JSON &request, JSON &result) {
return (this->_instance->*(this->_callback))(request, result);
}
protected:
/** The delegate instance. */
T *_instance;
/** The method to be called from the delegate instance. */
bool (T::*_callback)(const JSON &request, JSON &response);
};
/** Implements a trivial HTTP/1.1 server.
* @ingroup http */
class Server
{
public:
/** Constructor.
* @param port Specifies the port number to listen on. */
Server(uint port);
/** Destructor. */
~Server();
/** Starts the server.
* If @c wait is @c true, the call to this method will bock until the server thread stops. */
void start(bool wait=false);
/** Stops the server.
* If @c wait is @c true, the call will block until the server thread stopped. */
void stop(bool wait=false);
/** Wait for the server thread to join. */
void wait();
/** Adds a generic handler to the dispatcher. */
void addHandler(Handler *handler);
/** Adds a delegate to the dispatcher. */
template <class T>
void addHandler(const std::string &url,
T *instance, void (T::*func)(const Request &, Response &)) {
addHandler(new DelegateHandler<T>(url, instance, func));
}
/** Adds a JSON delegate to the dispatcher. */
template <class T>
void addJSON(const std::string &url,
T *instance, bool (T::*func)(const JSON &request, JSON &result)) {
addHandler(new DelegateJSONHandler<T>(url, instance, func));
}
/** Adds some static content to the dispatcher. */
inline void addStatic(const std::string &url, const std::string &text) {
addHandler(new StaticHandler(url, text));
}
/** Adds a generic handler to the dispatcher. */
inline void addStatic(const std::string &url, const std::string &text, const std::string &mimeType) {
addHandler(new StaticHandler(url, text, mimeType));
}
protected:
/** Dispatches a request. */
void dispatch(const Request &request, Response &response);
/** The thread waiting for incomming connections. */
static void *_listen_main(void *ctx);
/** The thread handling connections. */
static void *_connection_main(void *ctx);
protected:
/** Port to bind to. */
uint _port;
/** The socket to listen on. */
int _socket;
/** While true, the server is listening on the port for incomming connections. */
bool _is_running;
/** The listen thread. */
pthread_t _thread;
/** All registered handler. */
std::list<Handler *> _handler;
/** The connection queue. */
std::list<Connection> _queue;
/** The queue lock. */
pthread_mutex_t _queue_lock;
/** The set of handler threads. */
std::set<pthread_t> _threads;
/* Allow Connection to access dispatch(). */
friend class Connection;
};
}
}
#endif // __SDR_HTTPD_HH__

@ -8,16 +8,17 @@
namespace sdr {
/** Specifies the possible log-level. */
/** Specifies the possible log levels. */
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR
LOG_DEBUG = 0, ///< Every thing that may be of interest.
LOG_INFO, ///< Messages about state changes.
LOG_WARNING, ///< Non critical errors (i.e. data loss).
LOG_ERROR ///< Critical errors.
} LogLevel;
/** A log message. */
/** A log message.
* Bundles a message with a level. */
class LogMessage: public std::stringstream
{
public:
@ -41,7 +42,7 @@ protected:
};
/** Base class of all log message handlers. */
/** Base class of all log-message handlers. */
class LogHandler
{
protected:
@ -68,6 +69,7 @@ public:
StreamLogHandler(std::ostream &stream, LogLevel level);
/** Destructor. */
virtual ~StreamLogHandler();
/** Handles the message. */
virtual void handle(const LogMessage &msg);
@ -96,6 +98,7 @@ public:
/** Logs a message. */
void log(const LogMessage &message);
/** Adds a message handler. The ownership of the hander is transferred to the logger
* instance. */
void addHandler(LogHandler *handler);

@ -20,6 +20,7 @@ template <> inline int16_t fast_atan2<int8_t, int16_t>(int8_t a, int8_t b) {
return (a >= 0) ? angle : -angle;
}
/** Implementation of atan2 approximation using integers. */
template <> inline int16_t fast_atan2<uint8_t, int16_t>(uint8_t ua, uint8_t ub) {
int8_t a = (int16_t(ua)-(1<<7));
int8_t b = (int16_t(ub)-(1<<7));
@ -28,7 +29,6 @@ template <> inline int16_t fast_atan2<uint8_t, int16_t>(uint8_t ua, uint8_t ub)
/** Implementation of atan2 approximation using integers. */
template <> inline int16_t fast_atan2<int16_t, int16_t>(int16_t a, int16_t b) {
//return (1<<15)*(std::atan2(float(a), float(b))/M_PI);
const int32_t pi4 = (1<<12);
const int32_t pi34 = 3*(1<<12);
int32_t aabs, angle;

@ -1,3 +1,19 @@
/** @defgroup sources Data sources.
* This module collects all data sources provided by libsdr.
*
* @defgroup sinks Data sinks.
* This module collects all data sinks provided by libsdr.
*
* @defgroup filters Filter and signal processing nodes.
* This group collects all filter and signal processing nodes provided by libsdr.
*
* @defgroup demods Demodulator nodes.
* This group collects all demodulation nodes.
*
* @defgroup datanodes Data handling nodes.
* This group collects all nodes related to data handling. I.e. type casts etc.
*/
#ifndef __SDR_NODE_HH__
#define __SDR_NODE_HH__
@ -294,7 +310,8 @@ private:
};
/** A NOP node. */
/** A NOP node.
* @ingroup filters */
class Proxy: public SinkBase, public Source
{
public:

@ -0,0 +1,373 @@
#include "pocsag.hh"
#include "bch31_21.hh"
#include "logger.hh"
using namespace sdr;
inline bool is_address(uint32_t word) {
return (0 == (0x80000000 & word));
}
/* ********************************************************************************************* *
* Implementation of POCSAG
* ********************************************************************************************* */
POCSAG::POCSAG()
: Sink<uint8_t>()
{
// pass...
}
void
POCSAG::config(const Config &src_cfg) {
if (! src_cfg.hasType()) { return; }
// Check if buffer type matches
if (Config::typeId<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure POCSAG: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<uint8_t>();
throw err;
}
LogMessage msg(LOG_DEBUG);
msg << "Config POCSAG node.";
Logger::get().log(msg);
_state = WAIT;
_bits = 0;
}
void
POCSAG::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
{
for (size_t i=0; i<buffer.size(); i++)
{
// put bit into shift register
_bits = ( (_bits<<1) | (buffer[i] & 0x01) );
// Dispatch by state
if (WAIT == _state) {
// Wait for the sync word to appear
uint32_t word = (_bits & 0xffffffff);
if ( (0 == pocsag_repair(word)) && (0x7cd215d8 == word)) {
// init messages
_reset_message();
_state = RECEIVE; _bitcount = 0; _slot = 0;
}
} else if (RECEIVE == _state) {
// Receive 64 bit (2 words)
_bitcount++;
if (64 == _bitcount) {
_bitcount=0;
// get and check 1st word bits
uint32_t word = ( (_bits>>32) & 0xffffffff );
if (0 == pocsag_repair(word)) { _process_word(word); }
// get and check 2nd word bits
word = ( _bits & 0xffffffff );
if (0 == pocsag_repair(word)) { _process_word(word); }
// Advance slot counter
_slot++;
if (8 == _slot) {
// If all slots (8) has been processed -> wait for continuation
_state = CHECK_CONTINUE;
}
}
} else if (CHECK_CONTINUE == _state) {
// Wait for an immediate sync word
_bitcount++;
if (32 == _bitcount) {
uint32_t word = (_bits&0xffffffff);
if ( (0 == pocsag_repair(word)) && (0x7cd215d8 == word)) {
// If a sync word has been received -> continue with reception of slot 0
_state = RECEIVE; _slot = 0; _bitcount = 0;
} else {
// Otherwise -> end of transmission, wait for next sync
_finish_message(); _state = WAIT;
// Process received messages
this->handleMessages();
}
}
}
}
}
void
POCSAG::_process_word(uint32_t word)
{
/*std::cerr << "POCSAG: RX " << std::hex << word
<< std::dec << " @ " << int(_slot) << std::endl; */
if (0x7A89C197 == word) {
// Skip
_finish_message();
} else if (is_address(word)) {
// If address word
_finish_message();
// Assemble address
uint32_t addr = ((((word>>13) & 0x03ffff)<<3) + _slot );
uint8_t func = ((word>>11) & 0x03);
// init new message
_message = Message(addr, func);
} else {
// on data word
if (_message.isEmpty()) {
LogMessage msg(LOG_DEBUG);
msg << "POCSAG: Payload w/o address in slot " << int(_slot)
<< " word: " << std::hex << word;
Logger::get().log(msg);
} else {
_message.addPayload(word);
}
}
}
void
POCSAG::_reset_message() {
_message = Message();
}
void
POCSAG::_finish_message() {
if (_message.isEmpty()) { return; }
_queue.push_back(_message);
_reset_message();
}
void
POCSAG::handleMessages() {
// pass...
}
/* ********************************************************************************************* *
* Implementation of POCSAGDump
* ********************************************************************************************* */
POCSAGDump::POCSAGDump(std::ostream &stream)
: POCSAG(), _stream(stream)
{
// pass...
}
void
POCSAGDump::handleMessages() {
// You may re-implement this virutal method to process the queued messages in _queue.
while (_queue.size()) {
Message msg = _queue.back(); _queue.pop_back();
std::cerr << "POCSAG: @" << msg.address()
<< ", F=" << int(msg.function())
<< ", bits=" << msg.bits();
if (0 == msg.bits()) {
std::cerr << " (alert)" << std::endl;
} else if (msg.estimateText() >= msg.estimateNumeric()) {
std::cerr << " (txt)" << std::endl << " " << msg.asText() << std::endl;
} else {
std::cerr << " (num)" << std::endl << " " << msg.asNumeric() << std::endl;
}
}
}
/* ********************************************************************************************* *
* Implementation of POCSAG::Message
* ********************************************************************************************* */
std::string
ascii2text(uint8_t byte) {
switch ( byte ) {
case 0: return "<NUL>";
case 1: return "<SOH>";
case 2: return "<STX>";
case 3: return "<ETX>";
case 4: return "<EOT>";
case 5: return "<ENQ>";
case 6: return "<ACK>";
case 7: return "<BEL>";
case 8: return "<BS>";
case 9: return "<HT>";
case 10: return "<LF>";
case 11: return "<VT>";
case 12: return "<FF>";
case 13: return "<CR>";
case 14: return "<SO>";
case 15: return "<SI>";
case 16: return "<DLE>";
case 17: return "<DC1>";
case 18: return "<DC2>";
case 19: return "<DC3>";
case 20: return "<DC4>";
case 21: return "<NAK>";
case 22: return "<SYN>";
case 23: return "<ETB>";
case 24: return "<CAN>";
case 25: return "<EM>";
case 26: return "<SUB>";
case 27: return "<ESC>";
case 28: return "<FS>";
case 29: return "<GS>";
case 30: return "<RS>";
case 31: return "<US>";
default: break;
}
std::string txt; txt.resize(1, char(byte));
return txt;
}
char
bcd2text(uint8_t bcd) {
static const char *conv_table = "084 2.6]195-3U7[";
return conv_table[bcd&0xf];
}
int
pocsag_text_weight(char c) {
if (c < 32 || c == 127) {
return -5; // Non printable characters are uncommon
}
if ( (c > 32 && c < 48)
|| (c > 57 && c < 65)
|| (c > 90 && c < 97)
|| (c > 122 && c < 127) ) {
return -2; // Penalize special characters
}
return 1;
}
int
pocsag_numeric_weight(char cp, size_t pos) {
if(cp == 'U')
return -10;
if(cp == '[' || cp == ']')
return -5;
if(cp == ' ' || cp == '.' || cp == '-')
return -2;
if(pos < 10) // Penalize long messages
return 5;
return 0;
}
POCSAG::Message::Message()
: _address(0), _function(0), _empty(true), _bits(0)
{
// pass...
}
POCSAG::Message::Message(uint32_t addr, uint8_t func)
: _address(addr), _function(func), _empty(false), _bits(0)
{
// pass...
}
POCSAG::Message::Message(const Message &other)
: _address(other._address), _function(other._function), _empty(other._empty),
_bits(other._bits), _payload(other._payload)
{
// pass...
}
POCSAG::Message &
POCSAG::Message::operator =(const Message &other) {
_address = other._address;
_function = other._function;
_empty = other._empty;
_bits = other._bits;
_payload = other._payload;
return *this;
}
void
POCSAG::Message::addPayload(uint32_t word) {
// Add data bits from data to payload vector
uint32_t mask = 0x40000000;
for (int i=19; i>=0; i--) {
// on new byte -> add empty byte to payload
if (0 == (_bits % 8)) { _payload.push_back(0x00); }
// add bit to last byte of payload
_payload.back() = ((_payload.back()<<1) | ((word & mask)>>(i+11)));
// Increment bit counter and update mask
_bits++; mask = (mask>>1);
}
}
std::string
POCSAG::Message::asText() const
{
uint8_t byte = 0;
// Decode message
std::stringstream buffer;
for (size_t i=0; i<_bits; i++) {
size_t byteIdx = i/8;
size_t bitIdx = (7-(i%8));
// add bit to byte (reverse order)
byte = ((byte>>1) | (((_payload[byteIdx]>>bitIdx) & 0x01) << 6));
if (6 == (i%7)) {
buffer << ascii2text(byte&0x7f);
}
//std::cerr << "byte " << byteIdx << " bit " << bitIdx << " ascii bit " << (i%7) << std::endl;
}
return buffer.str();
}
std::string
POCSAG::Message::asNumeric() const
{
// Get number of complete half-bytes in payload
size_t N = _bits/4;
// Decode message
std::stringstream buffer;
for (size_t i=0; i<(N/2); i++) {
buffer << bcd2text((_payload[i]>>4) & 0xf);
buffer << bcd2text((_payload[i]>>0) & 0xf);
}
if (N%2) {
buffer << bcd2text((_payload[N/2]>>0) & 0xf);
}
return buffer.str();
}
std::string
POCSAG::Message::asHex() const {
std::stringstream buffer;
buffer << std::hex;
for (size_t i=0; i<_payload.size(); i++) {
buffer << int(_payload[i]);
}
return buffer.str();
}
int
POCSAG::Message::estimateText() const {
int weight = 0;
uint8_t byte = 0;
for (size_t i=0; i<_bits; i++) {
size_t byteIdx = i/8;
size_t bitIdx = (7-i%8);
// add bit to byte
byte = ((byte>>1) | (((_payload[byteIdx]>>bitIdx) & 0x01) << 6));
if (6 == i%7) {
weight += pocsag_text_weight(byte&0x7f);
}
}
return weight;
}
int
POCSAG::Message::estimateNumeric() const {
int weight = 0;
// Get number of complete half-bytes in payload
size_t N = _bits/4;
for (size_t i=0; i<(N/2); i++) {
weight += pocsag_numeric_weight(bcd2text((_payload[i]>>4) & 0xf), i);
weight += pocsag_numeric_weight(bcd2text((_payload[i]>>0) & 0xf), i);
}
if (N%2) {
weight += pocsag_numeric_weight(bcd2text((_payload[N/2]>>0) & 0xf), N/2);
}
return weight;
}

@ -0,0 +1,154 @@
#ifndef __SDR_POSAG_HH__
#define __SDR_POSAG_HH__
#include "node.hh"
namespace sdr {
/** Implements a POCSAG decoder.
* In conjecture with the @c FSKDetector or @c AFSDetector and the @c BitStream nodes, this node
* can be used to receive and process POCSAG (pages) messages.
*
* The POCSAG protocol is defined as followig:
*
* 1. at least 576 bits of alternating value (1 0 1 0 ...)
* 2. a 32-bit sync word (0x7CD215D8)
* 3. 2x8 data words (each 32 bit)
* 4. If data left to send -> continue with step 2
*
* Unused data words are send as 0x7A89C197. Each dataword is either a address word (bit 31 = 0)
* or message word (bit 31 = 1).
*
* In order to process the received message you need to override the @c handleMessages() method
* which gets called once a batch of messages has been received.
*
* @ingroup datanodes */
class POCSAG: public Sink<uint8_t>
{
public:
/** A pocsag message.
* A pocsag message can be either a numeric message (i.e. phone numbers) or a text message.
* The transmitter knows which type a certain receiver expects, hence there is no information
* embedded into the actual message that determines the type. Hence a heuristic needs to be
* used in order to select the message type by the contents of the message. This is done using
* the @c estimateText and @c estimateNumeric functions. They return a weight in favor of one of
* the types. I.e. if estimateText() > estimateNumeric(), the message is likely to be a text
* message. Like any heuristic, this approach may fail.
* The message text is returned by @c asText() and the numeric data is returned by
* @c asNumeric(). Both methods return a @c std::string as the numeric message may also contain
* a very limited set of non-number symbols. */
class Message {
public:
/** Empty constructor. */
Message();
/** Constructor from address and function. */
Message(uint32_t addr, uint8_t func);
/** Copy constructor. */
Message(const Message &other);
/** Assignment operator. */
Message &operator=(const Message &other);
/** Retruns @c true if the message is empty (has no address). */
inline bool isEmpty() const { return _empty; }
/** Returns the address of the message. */
inline uint32_t address() const { return _address; }
/** Returns the function of the message. */
inline uint8_t function() const { return _function; }
/** Returns the number of data bits. */
inline uint32_t bits() const { return _bits; }
/** Adds some payload from the given POGSAC word. */
void addPayload(uint32_t word);
/** Retruns the "likelihood" that the message is a text message (actually a log-likelihood). */
int estimateText() const;
/** Retruns the "likelihood" that the message is a numeric message
* (actually a log-likelihood). */
int estimateNumeric() const;
/** Decodes the message as a text message. */
std::string asText() const;
/** Decodes the message as a numeric message. */
std::string asNumeric() const;
/** Dumps the payload. */
std::string asHex() const;
protected:
/** The address of the message. */
uint32_t _address;
/** The function of the message. */
uint8_t _function;
/** If @c true the message is empty. */
bool _empty;
/** The number of payload bits in the message. */
uint32_t _bits;
/** The actual payload. */
std::vector<uint8_t> _payload;
};
protected:
/** The possible states of the POGSAC receiver. */
typedef enum {
WAIT, ///< Wait for a sync word.
RECEIVE, ///< Receive data.
CHECK_CONTINUE ///< Wait for the sync word for continuation.
} State;
public:
/** Constructor. */
POCSAG();
void config(const Config &src_cfg);
void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
/** Can be overwritten by any other implementation to process the received messages
* stored in @c _queue. */
virtual void handleMessages();
protected:
/** Process a POGSAC word. */
void _process_word(uint32_t word);
/** Clear the message. */
void _reset_message();
/** Add the (non-empty) message to the queue. */
void _finish_message();
protected:
/** The current state. */
State _state;
/** The last received bits. */
uint64_t _bits;
/** The number of received bits. */
uint8_t _bitcount;
/** The current slot. */
uint8_t _slot;
/** The current message. */
Message _message;
/** The completed messages. */
std::list<Message> _queue;
};
/** A simple extention of the @c POCSAG node that prints the received messages to a
* @c std::ostream.
* @ingroup datanodes */
class POCSAGDump: public POCSAG
{
public:
/** Constructor.
* @param stream Specifies the stream, the received messages are serialized into. */
POCSAGDump(std::ostream &stream);
/** Dumps the received messages. */
void handleMessages();
protected:
/** The output stream. */
std::ostream &_stream;
};
}
#endif // __SDR_POSAG_HH__

@ -33,7 +33,8 @@ public:
};
/** PortAudio playback node. */
/** PortAudio playback node.
* @ingroup sinks */
class PortSink: public SinkBase
{
public:
@ -55,7 +56,8 @@ protected:
};
/** PortAudio input stream as a @c Source. */
/** PortAudio input stream as a @c Source.
* @ingroup sources */
template <class Scalar>
class PortSource: public Source
{
@ -70,7 +72,12 @@ public:
}
/** Destructor. */
virtual ~PortSource() { if (0 != _stream) { Pa_CloseStream(_stream); } }
virtual ~PortSource() {
// close stream
if (0 != _stream) { Pa_CloseStream(_stream); }
// unref buffer
_buffer.unref();
}
/** Reads (blocking) the next buffer from the PortAudio stream. This function can be
* connected to the idle event of the @c Queue. */

@ -8,10 +8,11 @@
namespace sdr {
/** A simple BPSK31 "demodulator". This node consumes a complex input stream with a sample-rate of
* at least 2000Hz and produces a bitstream with 31.25 Hz "sample-rate". Use the @c Varicode node
/** A simple BPSK31 "demodulator". This node consumes a complex input stream with a sample rate of
* at least 2000Hz and produces a bitstream with 31.25 Hz "sample rate". Use the @c Varicode node
* to decode this bitstream to ASCII chars. The BPSK31 signal should be centered around 0Hz. This
* node uses a simple PLL to adjust for small detunings. */
* node uses a simple PLL to adjust for small detunings.
* @ingroup demods */
template <class Scalar>
class BPSK31: public Sink< std::complex<Scalar> >, public Source
{
@ -62,7 +63,10 @@ public:
/** Destructor. */
virtual ~BPSK31() {
// pass...
// unreference buffers
_dl.unref();
_hist.unref();
_buffer.unref();
}
virtual void config(const Config &src_cfg)
@ -290,7 +294,8 @@ protected:
/** Simple varicode (Huffman code) decoder node. It consumes a bit-stream (uint8_t)
* and produces a uint8_t stream of ascii chars. Non-printable chars (except for new-line) are
* ignored. The output stream has no samplerate! */
* ignored. The output stream has no samplerate!
* @ingroup datanodes */
class Varicode: public Sink<uint8_t>, public Source
{
public:

@ -105,7 +105,7 @@ RTLSource::setGain(double gain) {
void
RTLSource::start() {
pthread_create(&_thread, 0, RTLSource::__rtl_srd_parallel_main, this);
pthread_create(&_thread, 0, RTLSource::__rtl_sdr_parallel_main, this);
}
void
@ -131,7 +131,7 @@ RTLSource::deviceName(size_t idx) {
void *
RTLSource::__rtl_srd_parallel_main(void *ctx) {
RTLSource::__rtl_sdr_parallel_main(void *ctx) {
RTLSource *self = reinterpret_cast<RTLSource *>(ctx);
rtlsdr_read_async(self->_device, &RTLSource::__rtl_sdr_callback, self,
15, self->_buffer_size*2);

@ -8,7 +8,11 @@
namespace sdr {
/** Implements a @c uint_8 I/Q source for RTL2832 based TV dongles. */
/** Implements a @c uint_8 I/Q source for RTL2832 based TV dongles.
* This source runs in its own thread, hence the user does not need to trigger the reception of
* the next data chunk explicitly. The reception is started by calling the @c start method and
* stopped by calling the @c stop method.
* @ingroup sources */
class RTLSource: public Source
{
public:
@ -18,14 +22,15 @@ public:
* @c enableAGC and @c setGain methods.
*
* @param frequency Specifies the tuner frequency.
* @param sample_rate Specifies the sample rate.
* @param device_idx Specifies the device to be used. */
* @param sample_rate Specifies the sample rate in Hz.
* @param device_idx Specifies the device to be used. The @c numDevices
* and @c deviceName static method can be used to select the desired device index. */
RTLSource(double frequency, double sample_rate=1e6, size_t device_idx=0);
/** Destructor. */
virtual ~RTLSource();
/** Returns the freuency of the tuner. */
/** Returns the tuner frequency. */
inline double frequency() const { return _frequency; }
/** (Re-) Sets the tuner frequency. */
void setFrequency(double frequency);
@ -64,8 +69,8 @@ public:
static std::string deviceName(size_t idx);
protected:
/** Prallel routine to receive some data from the device. */
static void *__rtl_srd_parallel_main(void *ctx);
/** Parallel routine to receive some data from the device. */
static void *__rtl_sdr_parallel_main(void *ctx);
/** Callback to process received data. */
static void __rtl_sdr_callback(unsigned char *buffer, uint32_t len, void *ctx);

@ -290,6 +290,10 @@
#include "demod.hh"
#include "psk31.hh"
#include "fsk.hh"
#include "baudot.hh"
#include "ax25.hh"
#include "pocsag.hh"
#include "fftplan.hh"

@ -0,0 +1,189 @@
/* This code is public-domain - it is based on libcrypt
* placed in the public domain by Wei Dai and other contributors.
*/
// gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test
#include "sha1.hh"
#include <stdint.h>
#include <string.h>
using namespace sdr;
#ifdef __BIG_ENDIAN__
# define SHA_BIG_ENDIAN
#elif defined __LITTLE_ENDIAN__
/* override */
#elif defined __BYTE_ORDER
# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define SHA_BIG_ENDIAN
# endif
#else // ! defined __LITTLE_ENDIAN__
# include <endian.h> // machine/endian.h
# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define SHA_BIG_ENDIAN
# endif
#endif
/* code */
#define SHA1_K0 0x5a827999
#define SHA1_K20 0x6ed9eba1
#define SHA1_K40 0x8f1bbcdc
#define SHA1_K60 0xca62c1d6
void
sdr::sha1_init(sha1 *s) {
s->state[0] = 0x67452301;
s->state[1] = 0xefcdab89;
s->state[2] = 0x98badcfe;
s->state[3] = 0x10325476;
s->state[4] = 0xc3d2e1f0;
s->byteCount = 0;
s->bufferOffset = 0;
}
uint32_t
sha1_rol32(uint32_t number, uint8_t bits) {
return ((number << bits) | (number >> (32-bits)));
}
void
sha1_hashBlock(sha1 *s) {
uint8_t i;
uint32_t a,b,c,d,e,t;
a=s->state[0];
b=s->state[1];
c=s->state[2];
d=s->state[3];
e=s->state[4];
for (i=0; i<80; i++) {
if (i>=16) {
t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15];
s->buffer[i&15] = sha1_rol32(t,1);
}
if (i<20) {
t = (d ^ (b & (c ^ d))) + SHA1_K0;
} else if (i<40) {
t = (b ^ c ^ d) + SHA1_K20;
} else if (i<60) {
t = ((b & c) | (d & (b | c))) + SHA1_K40;
} else {
t = (b ^ c ^ d) + SHA1_K60;
}
t+=sha1_rol32(a,5) + e + s->buffer[i&15];
e=d;
d=c;
c=sha1_rol32(b,30);
b=a;
a=t;
}
s->state[0] += a;
s->state[1] += b;
s->state[2] += c;
s->state[3] += d;
s->state[4] += e;
}
void
sha1_addUncounted(sha1 *s, uint8_t data) {
uint8_t * const b = (uint8_t*) s->buffer;
#ifdef SHA_BIG_ENDIAN
b[s->bufferOffset] = data;
#else
b[s->bufferOffset ^ 3] = data;
#endif
s->bufferOffset++;
if (s->bufferOffset == SDR_SHA1_BLOCK_LENGTH) {
sha1_hashBlock(s);
s->bufferOffset = 0;
}
}
void
sdr::sha1_writebyte(sha1 *s, uint8_t data) {
++s->byteCount;
sha1_addUncounted(s, data);
}
void
sdr::sha1_write(sha1 *s, const char *data, size_t len) {
for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
}
void
sha1_pad(sha1 *s) {
// Implement SHA-1 padding (fips180-2 §5.1.1)
// Pad with 0x80 followed by 0x00 until the end of the block
sha1_addUncounted(s, 0x80);
while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
// Append length in the last 8 bytes
sha1_addUncounted(s, 0); // We're only using 32 bit lengths
sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
sha1_addUncounted(s, 0); // So zero pad the top bits
sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
sha1_addUncounted(s, s->byteCount >> 13); // byte.
sha1_addUncounted(s, s->byteCount >> 5);
sha1_addUncounted(s, s->byteCount << 3);
}
uint8_t*
sdr::sha1_result(sha1 *s) {
// Pad to complete the last block
sha1_pad(s);
#ifndef SHA_BIG_ENDIAN
// Swap byte order back
int i;
for (i=0; i<5; i++) {
s->state[i]=
(((s->state[i])<<24)& 0xff000000)
| (((s->state[i])<<8) & 0x00ff0000)
| (((s->state[i])>>8) & 0x0000ff00)
| (((s->state[i])>>24)& 0x000000ff);
}
#endif
// Return pointer to hash (20 characters)
return (uint8_t*) s->state;
}
#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c
void
sdr::sha1_initHmac(sha1 *s, const uint8_t* key, int keyLength) {
uint8_t i;
memset(s->keyBuffer, 0,SDR_SHA1_BLOCK_LENGTH);
if (keyLength >SDR_SHA1_BLOCK_LENGTH) {
// Hash long keys
sha1_init(s);
for (;keyLength--;) sha1_writebyte(s, *key++);
memcpy(s->keyBuffer, sha1_result(s),SDR_SHA1_HASH_LENGTH);
} else {
// Block length keys are used as is
memcpy(s->keyBuffer, key, keyLength);
}
// Start inner hash
sha1_init(s);
for (i=0; i<SDR_SHA1_BLOCK_LENGTH; i++) {
sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD);
}
}
uint8_t*
sdr::sha1_resultHmac(sha1 *s) {
uint8_t i;
// Complete inner hash
memcpy(s->innerHash,sha1_result(s), SDR_SHA1_HASH_LENGTH);
// Calculate outer hash
sha1_init(s);
for (i=0; i<SDR_SHA1_BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD);
for (i=0; i<SDR_SHA1_HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]);
return sha1_result(s);
}

@ -0,0 +1,44 @@
#ifndef __SDR_SHA1_HH__
#define __SDR_SHA1_HH__
#include <memory.h>
#include <inttypes.h>
namespace sdr {
/* header */
#define SDR_SHA1_HASH_LENGTH 20
#define SDR_SHA1_BLOCK_LENGTH 64
typedef struct {
uint32_t buffer[SDR_SHA1_BLOCK_LENGTH/4];
uint32_t state[SDR_SHA1_HASH_LENGTH/4];
uint32_t byteCount;
uint8_t bufferOffset;
uint8_t keyBuffer[SDR_SHA1_BLOCK_LENGTH];
uint8_t innerHash[SDR_SHA1_HASH_LENGTH];
} sha1;
/**
*/
void sha1_init(sha1 *s);
/**
*/
void sha1_writebyte(sha1 *s, uint8_t data);
/**
*/
void sha1_write(sha1 *s, const char *data, size_t len);
/**
*/
uint8_t* sha1_result(sha1 *s);
/**
*/
void sha1_initHmac(sha1 *s, const uint8_t* key, int keyLength);
/**
*/
uint8_t* sha1_resultHmac(sha1 *s);
}
#endif // __SDR_SHA1_HH__

@ -6,7 +6,8 @@
namespace sdr {
/** Arbitrary function generator. */
/** Arbitrary function generator.
* @ingroup sources */
template <class Scalar>
class SigGen: public Source
{
@ -83,7 +84,8 @@ protected:
/** Arbitrary function generator. */
/** Arbitrary function generator.
* @ingroup sources */
template <class Scalar>
class IQSigGen: public Source
{

@ -10,7 +10,8 @@
namespace sdr {
/** Simple averaging sub-sampler. */
/** Simple averaging sub-sampler.
* @ingroup filters */
template <class Scalar>
class SubSample: public Sink<Scalar>, public Source
{
@ -188,7 +189,8 @@ protected:
/** An interpolating sub-sampler. This node uses an 8-tap FIR filter to interpolate between
* two values (given 8). Please do not use this node to subsample by a factor grater than 8,
* as this may result into artifacts unless the signal was filtered accordingly before
* subsampling it. */
* subsampling it.
* @ingroup filters */
template <class iScalar, class oScalar = iScalar>
class InpolSubSampler: public Sink<iScalar>, public Source
{

@ -185,3 +185,34 @@ SignedToUnsigned::_process_int16(const RawBuffer &in, const RawBuffer &out) {
}
this->send(RawBuffer(out, 0, num), true);
}
/* ********************************************************************************************* *
* Implementation of TextDump
* ********************************************************************************************* */
TextDump::TextDump(std::ostream &stream)
: Sink<uint8_t>(), _stream(stream)
{
// pass...
}
void
TextDump::config(const Config &src_cfg) {
// Requires type
if (!src_cfg.hasType()) { return; }
if (src_cfg.type() != Traits<uint8_t>::scalarId) {
ConfigError err;
err << "Can not configure TextDump node: Invalid input type " << src_cfg.type()
<< ", expected " << Config::Type_u8 << ".";
throw err;
}
// done
}
void
TextDump::process(const Buffer<uint8_t> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
_stream << char(buffer[i]);
}
}

@ -11,7 +11,8 @@
namespace sdr {
/** Extracts the real or imaginary part of a complex valued data stream. */
/** Extracts the real or imaginary part of a complex valued data stream.
* @ingroup datanodes */
template <class Scalar>
class RealImagPart: public Sink< std::complex<Scalar> >, public Source
{
@ -76,7 +77,8 @@ protected:
};
/** Selects the real part of a complex signal. */
/** Selects the real part of a complex signal.
* @ingroup datanodes */
template <class Scalar>
class RealPart: public RealImagPart<Scalar>
{
@ -90,7 +92,8 @@ public:
};
/** Selects the imaginary part of a complex signal. */
/** Selects the imaginary part of a complex signal.
* @ingroup datanodes */
template <class Scalar>
class ImagPart: public RealImagPart<Scalar>
{
@ -104,7 +107,8 @@ public:
};
/** A simple node, that allows to balance an IQ signal. */
/** A simple node, that allows to balance an IQ signal.
* @ingroup filters */
template <class Scalar>
class IQBalance: public Sink< std::complex<Scalar> >, public Source
{
@ -201,7 +205,8 @@ protected:
/** Tiny helper node to transform a real part into a complex, including
* a possible type-cast*/
* a possible type-cast
* @ingroup datanodes */
template <class iScalar, class oScalar=iScalar>
class ToComplex: public Sink<iScalar>, public Source
{
@ -256,7 +261,8 @@ protected:
/** Explicit type cast node. */
/** Explicit type cast node.
* @ingroup datanodes */
template <class iScalar, class oScalar>
class Cast : public Sink<iScalar>, public Source
{
@ -361,7 +367,8 @@ protected:
/** Performs a reinterprete cast from an unsinged value to a singed one. */
/** Performs a reinterprete cast from an unsinged value to a singed one.
* @ingroup datanodes */
class UnsignedToSigned: public SinkBase, public Source
{
public:
@ -392,7 +399,8 @@ protected:
/** Performs a reinterprete cast from an unsinged value to a singed one. */
/** Performs a reinterprete cast from an unsinged value to a singed one.
* @ingroup datanodes */
class SignedToUnsigned: public SinkBase, public Source
{
public:
@ -423,7 +431,8 @@ protected:
/** Performs a frequency shift on a complex input signal, by multiplying it with \f$e^{i\omega t}\f$.
* Please note, this node performs not optimal in cases, where the input scalar is an integer, as
* the computation is performed using double precision floating point numbers.
* @deprecated Implement a more efficient variant using FreqShiftBase. */
* @deprecated Implement a more efficient variant using FreqShiftBase.
* @ingroup filters */
template <class Scalar>
class FreqShift: public Sink< std::complex<Scalar> >, public Source
{
@ -510,7 +519,8 @@ protected:
};
/** Reads raw samples from an imput stream, (ie a file). */
/** Reads raw samples from an imput stream, (ie a file).
* @ingroup sources */
template<class Scalar>
class StreamSource: public Source
{
@ -541,7 +551,8 @@ protected:
};
/** Serializes the incomming buffers as raw data. */
/** Serializes the incomming buffers as raw data.
* @ingroup sinks */
template <class Scalar>
class StreamSink: public Sink<Scalar>
{
@ -579,7 +590,8 @@ protected:
/** Simple scaling node. */
/** Simple scaling node.
* @ingroup filters */
template <class Scalar>
class Scale : public Sink<Scalar>, public Source
{
@ -640,7 +652,8 @@ protected:
/** An automatic gain control node. */
/** An automatic gain control node.
* @ingroup filters */
template <class Scalar>
class AGC: public Sink<Scalar>, public Source
{
@ -780,7 +793,8 @@ protected:
};
/** Keeps a copy of the last buffer received. */
/** Keeps a copy of the last buffer received.
* @ingroup sinks */
template <class Scalar>
class DebugStore: public Sink<Scalar>
{
@ -827,7 +841,8 @@ public:
};
/** Dumps buffers in a human readable form. */
/** Dumps buffers in a human readable form.
* @ingroup sinks*/
template <class Scalar>
class DebugDump: public Sink<Scalar>
{
@ -868,8 +883,26 @@ protected:
std::ostream &_stream;
};
/** Dumps the received uin8_t byte-stream as (ASCII) text.
* @ingroup sink */
class TextDump : public Sink<uint8_t>
{
public:
/** Constructor. @c stream specifies the ouput stream. */
TextDump(std::ostream &stream=std::cerr);
/** Configures the sink. */
virtual void config(const Config &src_cfg);
/** Processes the input stream. */
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
/** A Gaussian White Noise source. */
protected:
/** The output stream. */
std::ostream &_stream;
};
/** A Gaussian White Noise source.
* @ingroup sources */
template <class Scalar>
class GWNSource: public Source
{

@ -135,7 +135,7 @@ WavSource::open(const std::string &filename)
_file.read((char *)&chunk_size, 4); // read frame count
// Configure source
_frame_count = chunk_size/(2*n_chanels);
_frame_count = chunk_size/(n_chanels*(bits_per_sample/8));
if ((1 == n_chanels) && (8 == bits_per_sample)) { _type = Config::Type_u8; }
else if ((1==n_chanels) && (16 == bits_per_sample)) { _type = Config::Type_s16; }
else if ((2==n_chanels) && ( 8 == bits_per_sample)) { _type = Config::Type_cu8; }
@ -150,7 +150,7 @@ WavSource::open(const std::string &filename)
LogMessage msg(LOG_DEBUG);
msg << "Configured WavSource:" << std::endl
<< " file: " << filename << std::endl
<< " type:" << _type << std::endl
<< " type: " << _type << std::endl
<< " sample-rate: " << _sample_rate << std::endl
<< " frame-count: " << _frame_count << std::endl
<< " duration: " << _frame_count/_sample_rate << "s" << std::endl

@ -6,7 +6,8 @@
namespace sdr {
/** Stores the received buffers into a WAV file. */
/** Stores the received buffers into a WAV file.
* @ingroup sinks */
template <class Scalar>
class WavSink: public Sink<Scalar>
{
@ -127,7 +128,8 @@ protected:
/** A simple imput source that reads from a wav file. Some data is read from the file on every call
* to @c next until the end of file is reached. */
* to @c next until the end of file is reached.
* @ingroup sources */
class WavSource: public Source
{
public:

Loading…
Cancel
Save