Initial import.

master
Hannes Matuschek 12 years ago
parent ba2f6fdce9
commit da64cd509b

@ -0,0 +1,98 @@
cmake_minimum_required(VERSION 2.8.8)
project(sdr)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
include(InstallHeadersWithDirectory)
find_package(Qt5Core REQUIRED)
find_package(Qt5Declarative REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5LinguistTools REQUIRED)
find_package(FFTW)
find_package(FFTWSingle)
find_package(PortAudio)
find_package(RTLSDR)
ADD_DEFINITIONS(${Qt5Widgets_DEFINITIONS})
INCLUDE_DIRECTORIES(${Qt5Core_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/sdr)
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}/sdr)
INCLUDE_DIRECTORIES(${PORTAUDIO_INCLUDE_DIRS})
# Set some variables for the configuration file
IF(FFTW_FOUND)
set(SDR_WITH_FFTW ON)
ELSE(FFTW_FOUND)
set(FFTW_LIBRARIES)
set(FFTWSingle_LIBRARIES)
ENDIF(FFTW_FOUND)
IF(PORTAUDIO_FOUND)
set(SDR_WITH_PORTAUDIO ON)
ELSE(PORTAUDIO_FOUND)
set(PORTAUDIO_LIBRARIES)
ENDIF(PORTAUDIO_FOUND)
IF(RTLSDR_FOUND)
set(SDR_WITH_RTLSDR ON)
ELSE(TRLSDR_FOUND)
set(RTLSDR_LIBRARIES)
ENDIF(RTLSDR_FOUND)
set(LIBS ${FFTW_LIBRARIES} ${FFTWSingle_LIBRARIES} ${PORTAUDIO_LIBRARIES} ${RTLSDR_LIBRARIES} "pthread")
# Set compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -ggdb")
LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/sdr)
LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/sdr/gui)
IF(CMAKE_BUILD_TYPE MATCHES DEBUG)
SET(SDR_DEBUG ON)
ENDIF(CMAKE_BUILD_TYPE MATCHES DEBUG)
# Create config.hh
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/sdr/config.hh.in
${CMAKE_CURRENT_BINARY_DIR}/sdr/config.hh)
# Add core library, and unit tests
add_subdirectory(sdr)
add_subdirectory(test)
add_subdirectory(rx)
# Add DSO application, iff portaudio is present
IF(SDR_WITH_PORTAUDIO)
add_subdirectory(dso)
ENDIF(SDR_WITH_PORTAUDIO)
# Add examples, if all libraries are present:
IF(SDR_WITH_PORTAUDIO AND SDR_WITH_FFTW AND SDR_WITH_RTLSDR)
add_subdirectory(examples)
ENDIF(SDR_WITH_PORTAUDIO AND SDR_WITH_FFTW AND SDR_WITH_RTLSDR)
# Source distribution packages:
set(CPACK_PACKAGE_VERSION_MAJOR "0")
set(CPACK_PACKAGE_VERSION_MINOR "1")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_SOURCE_GENERATOR "TGZ")
set(CPACK_SOURCE_PACKAGE_FILE_NAME
"${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
set(CPACK_SOURCE_IGNORE_FILES
"/build/;/doc/;/dist/;/.git/;.dat$;.wav$;~$;.qm$;${CPACK_SOURCE_IGNORE_FILES}")
# Configure NSIS installer for windows
set(CPACK_NSIS_PACKAGE_NAME "DSO")
set(CPACK_NSIS_DISPLAY_NAME "DSO")
set(CPACK_NSIS_CONTACT "hmatuschek@gmail.com")
set(CPACK_PACKAGE_EXECUTABLES dso "DSO")
include(CPack)

@ -1,4 +1,25 @@
libsdr
libsdr - A simple software defined radio (SDR) library
======
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).
License
=======
libsdr - A simple software defined radio (SDR) library
Copyright (C) 2014 Hannes Matuschek
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

@ -0,0 +1,22 @@
# - Find FFTW
# Find the native FFTW includes and library
#
# FFTW_INCLUDES - where to find fftw3.h
# FFTW_LIBRARIES - List of libraries when using FFTW.
# FFTW_FOUND - True if FFTW found.
if (FFTW_INCLUDES)
# Already in cache, be silent
set (FFTW_FIND_QUIETLY TRUE)
endif (FFTW_INCLUDES)
find_path (FFTW_INCLUDES fftw3.h)
find_library (FFTW_LIBRARIES NAMES fftw3)
# handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if
# all listed variables are TRUE
include (FindPackageHandleStandardArgs)
find_package_handle_standard_args (FFTW DEFAULT_MSG FFTW_LIBRARIES FFTW_INCLUDES)
mark_as_advanced (FFTW_LIBRARIES FFTW_INCLUDES)

@ -0,0 +1,22 @@
# - Find FFTW
# Find the native FFTW includes and library
#
# FFTWSingle_INCLUDES - where to find fftw3.h
# FFTWSingle_LIBRARIES - List of libraries when using FFTW.
# FFTWSingle_FOUND - True if FFTW found.
if (FFTWSingle_INCLUDES)
# Already in cache, be silent
set (FFTWSingle_FIND_QUIETLY TRUE)
endif (FFTWSingle_INCLUDES)
find_path (FFTWSingle_INCLUDES fftw3.h)
find_library (FFTWSingle_LIBRARIES NAMES fftw3f)
# handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if
# all listed variables are TRUE
include (FindPackageHandleStandardArgs)
find_package_handle_standard_args (FFTWSingle DEFAULT_MSG FFTWSingle_LIBRARIES FFTWSingle_INCLUDES)
mark_as_advanced (FFTWSingle_LIBRARIES FFTWSingle_INCLUDES)

@ -0,0 +1,112 @@
# - Try to find Portaudio
# Once done this will define
#
# PORTAUDIO_FOUND - system has Portaudio
# PORTAUDIO_INCLUDE_DIRS - the Portaudio include directory
# PORTAUDIO_LIBRARIES - Link these to use Portaudio
# PORTAUDIO_DEFINITIONS - Compiler switches required for using Portaudio
# PORTAUDIO_VERSION - Portaudio version
#
# Copyright (c) 2006 Andreas Schneider <mail@cynapses.org>
#
# Redistribution and use is allowed according to the terms of the New BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
if(PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS)
# in cache already
set(PORTAUDIO_FOUND TRUE)
else(PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS)
if (NOT WIN32)
include(FindPkgConfig)
pkg_check_modules(PORTAUDIO2 portaudio-2.0)
endif (NOT WIN32)
if (PORTAUDIO2_FOUND)
set(PORTAUDIO_INCLUDE_DIRS
${PORTAUDIO2_INCLUDE_DIRS}
)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(PORTAUDIO_LIBRARIES "${PORTAUDIO2_LIBRARY_DIRS}/lib${PORTAUDIO2_LIBRARIES}.dylib")
else (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(PORTAUDIO_LIBRARIES
${PORTAUDIO2_LIBRARIES}
)
endif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(PORTAUDIO_VERSION
19
)
set(PORTAUDIO_FOUND TRUE)
else (PORTAUDIO2_FOUND)
find_path(PORTAUDIO_INCLUDE_DIR
NAMES
portaudio.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
/sw/include
C:/MinGW/include
)
find_library(PORTAUDIO_LIBRARY
NAMES
portaudio
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
C:/MinGW/lib
)
find_path(PORTAUDIO_LIBRARY_DIR
NAMES
portaudio
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
C:/MinGW/lib
)
set(PORTAUDIO_INCLUDE_DIRS
${PORTAUDIO_INCLUDE_DIR}
)
set(PORTAUDIO_LIBRARIES
${PORTAUDIO_LIBRARY}
)
set(PORTAUDIO_LIBRARY_DIRS
${PORTAUDIO_LIBRARY_DIR}
)
set(PORTAUDIO_VERSION
18
)
if (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES)
set(PORTAUDIO_FOUND TRUE)
endif (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES)
if (PORTAUDIO_FOUND)
if (NOT Portaudio_FIND_QUIETLY)
message(STATUS "Found Portaudio: ${PORTAUDIO_LIBRARIES}")
endif (NOT Portaudio_FIND_QUIETLY)
else (PORTAUDIO_FOUND)
if (Portaudio_FIND_REQUIRED)
message(FATAL_ERROR "Could not find Portaudio")
endif (Portaudio_FIND_REQUIRED)
endif (PORTAUDIO_FOUND)
endif (PORTAUDIO2_FOUND)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PORTAUDIO DEFAULT_MSG PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES)
# show the PORTAUDIO_INCLUDE_DIRS and PORTAUDIO_LIBRARIES variables only in the advanced view
mark_as_advanced(PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES)
endif (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS)

@ -0,0 +1,22 @@
# - Find RTL-SDR
# Find the native RTL-SDR includes and library
#
# RTLSDR_INCLUDES - where to find rtl-sdr.h
# RTLSDR_LIBRARIES - List of libraries when using RTL-SDR.
# RTLSDR_FOUND - True if RTL-SDR found.
if (RTLSDR_INCLUDES)
# Already in cache, be silent
set (RTLSDR_FIND_QUIETLY TRUE)
endif (RTLSDR_INCLUDES)
find_path (RTLSDR_INCLUDES rtl-sdr.h)
find_library (RTLSDR_LIBRARIES NAMES rtlsdr)
# handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if
# all listed variables are TRUE
include (FindPackageHandleStandardArgs)
find_package_handle_standard_args (RTLSDR DEFAULT_MSG RTLSDR_LIBRARIES RTLSDR_INCLUDES)
mark_as_advanced (RTLSDR_LIBRARIES RTLSDR_INCLUDES)

@ -0,0 +1,13 @@
MACRO(INSTALL_HEADERS_WITH_DIRECTORY HEADER_LIST DESTINATION_DIR)
FOREACH(HEADER ${HEADER_LIST})
STRING(REGEX MATCH "(.*)[/\\]" DIR ${HEADER})
INSTALL(FILES ${HEADER} DESTINATION ${DESTINATION_DIR}/${DIR})
ENDFOREACH(HEADER)
ENDMACRO(INSTALL_HEADERS_WITH_DIRECTORY)
MACRO(COPY_HEADERS_WITH_DIRECTORY HEADER_LIST DESTINATION_DIR)
FOREACH(HEADER ${HEADER_LIST})
STRING(REGEX MATCH "(.*)[/\\]" DIR ${HEADER})
FILE(COPY ${HEADER} DESTINATION ${DESTINATION_DIR}/${DIR})
ENDFOREACH(HEADER)
ENDMACRO(COPY_HEADERS_WITH_DIRECTORY)

@ -0,0 +1,17 @@
# the name of the target operating system
SET(CMAKE_SYSTEM_NAME Windows)
# which compilers to use for C and C++
SET(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
SET(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
SET(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
# here is the target environment located
SET(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc /home/hannes/mingw-install )
# adjust the default behaviour of the FIND_XXX() commands:
# search headers and libraries in the target environment, search
# programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,53 @@
# Sources of libsdr
set(LIBSDR_SOURCES
buffer.cc node.cc queue.cc traits.cc
portaudio.cc utils.cc wavfile.cc
exception.cc logger.cc)
set(LIBSDR_HEADERS sdr.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 resample.hh
combine.hh logger.hh)
if(SDR_WITH_PORTAUDIO)
set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc)
set(LIBSDR_HEADERS ${LIBSDR_HEADERS} portaudio.hh)
endif(SDR_WITH_PORTAUDIO)
if(SDR_WITH_FFTW)
set(LIBSDR_SOURCES ${LIBSDR_SOURCES})
set(LIBSDR_HEADERS ${LIBSDR_HEADERS} fftplan_fftw3.hh)
endif(SDR_WITH_FFTW)
if(SDR_WITH_RTLSDR)
set(LIBSDR_SOURCES ${LIBSDR_SOURCES} rtlsource.cc)
set(LIBSDR_HEADERS ${LIBSDR_HEADERS} rtlsource.hh)
endif(SDR_WITH_RTLSDR)
add_custom_target(libsdr_hdrs SOURCES ${LIBSDR_HEADERS})
add_library(libsdr SHARED ${LIBSDR_SOURCES})
set_target_properties(libsdr PROPERTIES OUTPUT_NAME sdr)
set_target_properties(libsdr PROPERTIES DEPENDS libsdr_hdrs)
target_link_libraries(libsdr ${LIBS})
if(SDR_WITH_FFTW)
set(libsdr_gui_SOURCES gui/spectrum.cc gui/spectrumview.cc gui/waterfallview.cc)
set(libsdr_gui_MOC_HEADERS gui/spectrum.hh gui/spectrumview.hh gui/waterfallview.hh)
set(libsdr_gui_HEADERS ${libsdr_gui_MOC_HEADERS} gui/gui.hh)
qt5_wrap_cpp(libsdr_gui_MOC_SOURCES ${libsdr_gui_MOC_HEADERS})
add_library(libsdr-gui SHARED ${libsdr_gui_SOURCES} ${libsdr_gui_MOC_SOURCES})
set_target_properties(libsdr-gui PROPERTIES OUTPUT_NAME sdr-gui)
target_link_libraries(libsdr-gui libsdr ${Qt5Core_LIBRARIES} ${Qt5Widgets_LIBRARIES})
endif(SDR_WITH_FFTW)
IF(WIN32 OR WIN64)
install(TARGETS libsdr DESTINATION bin)
install(FILES "C:/MinGW/bin/pthreadGC2.dll" DESTINATION bin)
install(FILES "C:/MinGW/bin/libgcc_s_dw2-1.dll" DESTINATION bin)
install(FILES "C:/MinGW/bin/libportaudio-2.dll" DESTINATION bin)
install(FILES "C:/Qt/4.8.6/bin/QtCored4.dll" DESTINATION bin)
install(FILES "C:/Qt/4.8.6/bin/QtGuid4.dll" DESTINATION bin)
install(FILES "C:/Qt/4.8.6/bin/libwinpthread-1.dll" DESTINATION bin)
install(FILES "C:/Qt/4.8.6/bin/libstdc++-6.dll" DESTINATION bin)
ENDIF(WIN32 OR WIN64)

@ -0,0 +1,218 @@
#ifndef __SDR_AUTOCAST_HH__
#define __SDR_AUTOCAST_HH__
#include "node.hh"
#include "traits.hh"
#include "logger.hh"
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. */
template <class Scalar>
class AutoCast: public SinkBase, public Source
{
public:
/** Constructor. */
AutoCast()
: SinkBase(), Source(), _buffer(), _cast(0)
{
// pass...
}
/** Configures the auto cast node. */
virtual void config(const Config &src_cfg) {
// Requires buffer size, sample rate and type:
if ((Config::Type_UNDEFINED==src_cfg.type()) || (0==src_cfg.sampleRate()) || (0==src_cfg.bufferSize())) { return; }
// Check type cast combination
if (Config::Type_s8 == Traits<Scalar>::scalarId) {
switch (src_cfg.type()) {
case Config::Type_u8:
case Config::Type_s8: _cast = _identity; break;
case Config::Type_u16:
case Config::Type_s16: _cast = _int16_int8; break;
default: break;
}
} else if (Config::Type_cs8 == Traits<Scalar>::scalarId) {
switch (src_cfg.type()) {
case Config::Type_u8:
case Config::Type_s8: _cast = _int8_cint8; break;
case Config::Type_cu8:
case Config::Type_cs8: _cast = _identity; break;
case Config::Type_u16:
case Config::Type_s16: _cast = _int16_cint8; break;
default: break;
}
} else if (Config::Type_s16 == Traits<Scalar>::scalarId) {
switch (src_cfg.type()) {
case Config::Type_u8:
case Config::Type_s8: _cast = _int8_int16; break;
case Config::Type_u16:
case Config::Type_s16: _cast = _identity; break;
default: break;
}
} else if (Config::Type_cs16 == Traits<Scalar>::scalarId) {
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_u16:
case Config::Type_s16: _cast = _int16_cint16; break;
case Config::Type_cu16:
case Config::Type_cs16: _cast = _identity; break;
default: break;
}
}
// Check if there exists a cast to the required type
if (0 == _cast) {
ConfigError err;
err << "AutoCast: Can not cast from type " << src_cfg.type() << " to " << Traits<Scalar>::scalarId;
throw err;
}
// Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Configure AutoCast node:" << std::endl
<< " input type: " << src_cfg.type() << std::endl
<< " output type: " << Traits<Scalar>::scalarId;
// Propergate config
this->setConfig(Config(Config::typeId<Scalar>(), src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
}
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite) {
// If no conversion is selected
if (0 == _cast) { return; }
// If the identity conversion is selected -> forward buffer
if (_identity == _cast) { this->send(buffer, allow_overwrite); }
// Otherwise cast
size_t bytes = _cast(buffer, _buffer);
this->send(RawBuffer(_buffer, 0, bytes), true);
}
protected:
/** Output buffer. */
Buffer<Scalar> _buffer;
/** Cast function. */
size_t (*_cast)(const RawBuffer &in, const RawBuffer &out);
protected:
/** Performs no cast at all. */
static size_t _identity(const RawBuffer &in, const RawBuffer &out) {
memcpy(out.data(), in.data(), in.bytesLen());
return in.bytesLen();
}
/** 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;
}
return N;
}
/** int8 -> complex int 8. */
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];
}
return 2*N;
}
/** int16 -> complex int 8. */
static size_t _int16_cint8(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;
for (size_t i=0; i<N; i++) {
reinterpret_cast<std::complex<int8_t> *>(out.data())[i] = reinterpret_cast<int16_t *>(in.data())[i]>>8;
}
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());
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);
}
return 2*N;
}
/** int8 -> int16. */
static size_t _int8_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<int16_t *>(out.data())[i] = int16_t(values[i])<<8;
}
return 2*N;
}
/** unsinged int8 -> complex int16. */
static size_t _uint8_cint16(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen();
uint8_t *values = reinterpret_cast<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])-127)<<8);
}
return 4*N;
}
/** int8 -> complex int16. */
static size_t _int8_cint16(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<int16_t> *>(out.data())[i]
= std::complex<int16_t>(int16_t(values[i])<<8);
}
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)<<8,
(int16_t(values[i].imag())-127)<<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())<<8,
int16_t(values[i].imag())<<8);
}
return 4*N;
}
/** int16 -> complex int16. */
static size_t _int16_cint16(const RawBuffer &in, const RawBuffer &out) {
size_t N = in.bytesLen()/2;
int16_t *values = reinterpret_cast<int16_t *>(in.data());
for (size_t i=0; i<N; i++) {
reinterpret_cast<std::complex<int16_t> *>(out.data())[i] = std::complex<int16_t>(values[i]);
}
return 4*N;
}
};
}
#endif // __SDR_AUTOCAST_HH__

@ -0,0 +1,4 @@
#include "baseband.hh"
using namespace sdr;

@ -0,0 +1,530 @@
#ifndef __SDR_BASEBAND_HH__
#define __SDR_BASEBAND_HH__
#include "node.hh"
#include "config.hh"
#include "utils.hh"
#include "traits.hh"
#include "operators.hh"
#include "freqshift.hh"
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. */
template <class Scalar>
class IQBaseBand: public Sink< std::complex<Scalar> >, public Source, public FreqShiftBase<Scalar>
{
public:
/** The complex type of the input stream. */
typedef std::complex<Scalar> CScalar;
/** The (real) computation scalar type (super scalar), the computations are performed with this
* scalar type. */
typedef int32_t SScalar;
/** Complex @c SScalar type. */
typedef std::complex<SScalar> CSScalar;
public:
/** Constructor, the filter center frequency @c Ff equals the given center frequency @c Fc. */
IQBaseBand(double Fc, double width, size_t order, size_t sub_sample, double oFs=0.0)
: Sink<CScalar>(), Source(), FreqShiftBase<Scalar>(_Fc, 0),
_Fc(Fc), _Ff(Fc), _Fs(0), _width(width), _order(std::max(size_t(1), order)),
_sub_sample(sub_sample), _oFs(oFs), _ring_offset(0), _sample_count(0),
_last(0), _kernel(_order)
{
// Allocate and reset ring buffer:
_ring = Buffer<CSScalar>(_order);
for (size_t i=0; i<_order; i++) { _ring[i] = 0; }
}
/** Constructor. */
IQBaseBand(double Fc, double Ff, double width, size_t order, size_t sub_sample, double oFs=0.0)
: Sink<CScalar>(), Source(), FreqShiftBase<Scalar>(_Fc, 0),
_Fc(Fc), _Ff(Ff), _Fs(0), _width(width), _order(std::max(size_t(1), order)),
_sub_sample(sub_sample), _oFs(oFs), _ring_offset(0), _sample_count(0),
_last(0), _kernel(_order)
{
// Allocate and reset ring buffer:
_ring = Buffer<CSScalar>(_order);
for (size_t i=0; i<_order; i++) { _ring[i] = 0; }
}
/** Destructor. */
virtual ~IQBaseBand() {
// Free buffers
_buffer.unref();
_kernel.unref();
_ring.unref();
}
/** Returns the order of the band-pass filter. */
inline size_t order() const { return _order; }
/** (Re-) Sets the filter order. */
void setOrder(size_t o) {
// ensure filter order >= 1
o = (o<1) ? 1 : o;
// Reallocate ring buffer and kernel
if (! _kernel.isEmpty()) { _kernel.unref(); }
if (! _ring.isEmpty()) { _ring.unref(); }
_kernel = Buffer<CSScalar>(o);
_ring = Buffer<CSScalar>(o);
// Update filter kernel
_order = o; _update_filter_kernel();
}
/** Returns the center frequency. */
inline double centerFrequency() const { return _Fc; }
/** Resets the center frequency. */
void setCenterFrequency(double Fc) {
_Fc = Fc; this->setFrequencyShift(_Fc);
}
/** Returns the center frequency. */
inline double filterFrequency() const { return _Ff; }
/** (Re-) Sets the center frequency. */
void setFilterFrequency(double Ff) {
_Ff = Ff; _update_filter_kernel();
}
/** Returns the filter width. */
inline double filterWidth() const { return _width; }
/** Sets the filter width. */
void setFilterWidth(double width) {
_width = width; _update_filter_kernel();
}
/** Returns the sub sampling. */
size_t subSample() const { return _sub_sample; }
/** Resets the sub sampling. Please note that the Queue needs to be stopped before calling this function! */
void setSubsample(size_t sub_sample) {
_sub_sample = std::max(size_t(1), sub_sample); _reconfigure();
}
/** Resets the output sample rate. The sub-sampling will be adjusted accordingly. Please note the the resulting
* output sample rate will be rounded up to be an integral fraction of the input sample rate. */
void setOutputSampleRate(double Fs) {
_oFs = Fs; _reconfigure();
}
/** Configures the BaseBand node. */
virtual void config(const Config &src_cfg)
{
// Requires type, sample rate & buffer size
if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.hasBufferSize()) { return; }
// Check buffer type
if (Config::typeId<CScalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure IQBaseBand: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<CScalar>();
throw err;
}
// Store sample rate
_Fs = src_cfg.sampleRate();
// Store source buffer size
_sourceBs = src_cfg.bufferSize();
_reconfigure();
}
/** Processes the given buffer. */
virtual void process(const Buffer< std::complex<Scalar> > &buffer, bool allow_overwrite)
{
if (allow_overwrite) {
// Perform in-place if @c allow_overwrite
_process(buffer, buffer);
} else if (_buffer.isUnused()) {
// Otherwise store results into output buffer.
_process(buffer, _buffer);
} else {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "IQBaseBand: Drop buffer: Output buffer still in use.";
Logger::get().log(msg);
#endif
}
}
protected:
/** Reconfigures the node. */
void _reconfigure()
{
// If _oFs > 0 -> set _sub_sample to match the sample rate (approx).
if (_oFs > 0) {
_sub_sample = _Fs/_oFs;
if (_sub_sample < 1) { _sub_sample=1; }
}
// Update filter kernel
_update_filter_kernel();
// Update freq shift operator:
this->setSampleRate(_Fs);
// Calc output buffer size
size_t buffer_size = _sourceBs/_sub_sample;
if (_sourceBs%_sub_sample) { buffer_size += 1; }
// Allocate output buffer
_buffer = Buffer<CScalar>(buffer_size);
// Reset internal state
_last = 0;
_sample_count = 0;
_ring_offset = 0;
LogMessage msg(LOG_DEBUG);
msg << "Configured IQBaseBand node:" << std::endl
<< " type " << Traits< std::complex<Scalar> >::scalarId << std::endl
<< " sample-rate " << _Fs << "Hz" << std::endl
<< " center freq " << _Fc << "Hz" << std::endl
<< " width " << _width << "Hz" << std::endl
<< " kernel " << _kernel << std::endl
<< " in buffer size " << _sourceBs << std::endl
<< " sub-sample by " << _sub_sample << std::endl
<< " out buffer size " << buffer_size;
Logger::get().log(msg);
// Propergate config
this->setConfig(Config(Traits< std::complex<Scalar> >::scalarId,
_Fs/_sub_sample, buffer_size, 1));
}
/** Performs the base-band selection, frequency shift and sub-sampling. Stores the
* results into @c out. The input and output buffer may overlapp. */
inline void _process(const Buffer<CScalar> &in, const Buffer<CScalar> &out) {
size_t i=0, j=0;
for (; i<in.size(); i++, _sample_count++) {
// Store sample in ring buffer
_ring[_ring_offset] = in[i];
// Apply filter on ring-buffer and shift freq
_last += this->applyFrequencyShift(_filter_ring());
// _ring_offset modulo _order
_ring_offset++;
if (_order == _ring_offset) { _ring_offset = 0; }
// If _sample_count samples have been averaged:
if (_sub_sample == _sample_count) {
// Store average in output buffer
CSScalar value = _last/CSScalar(_sub_sample);
out[j] = value;
// reset average, sample count and increment output buffer index j
_last = 0; _sample_count=0; j++;
} else if (_sub_sample == 1) {
out[j] = _last; _last = 0; _sample_count = 0; j++;
}
}
this->send(out.head(j), true);
}
/** Applies the filter on the data stored in the ring buffer. */
inline CSScalar _filter_ring() const
{
CSScalar res = 0;
size_t idx = _ring_offset+1;
if (_order == idx) { idx = 0; }
for (size_t i=0; i<_order; i++, idx++) {
if (_order == idx) { idx = 0; }
res += _kernel[i]*_ring[idx];
}
return (res>>14);
}
/** Updates the band-pass filter kernel. */
void _update_filter_kernel() {
// First, create a filter kernel of a low-pass filter with _width/2 cut-off
Buffer< std::complex<double> > alpha(_order);
double w = (M_PI*_width)/(_Fs);
double M = double(_order)/2.;
double norm = 0;
//std::complex<double> phi(0.0, (2*M_PI*_Ff)/_Fs); phi = std::exp(phi);
for (size_t i=0; i<_order; i++) {
if (_order == 2*i) { alpha[i] = 4*(w/M_PI); }
else { alpha[i] = std::sin(w*(i-M))/(w*(i-M)); }
// Shift freq by +_Ff:
//alpha[i] = phi*alpha[i]; phi = phi*phi;
alpha[i] *= std::exp(std::complex<double>(0.0, (-2*M_PI*_Ff*i)/_Fs));
// apply window function
alpha[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_order) + 0.08*cos((4*M_PI*i)/_order));
// Calc norm
norm += std::abs(alpha[i]);
}
// Normalize filter coeffs and store in _kernel:
for (size_t i=0; i<_order; i++) {
_kernel[i] = (double(1<<14) * alpha[i]) / norm;
}
}
protected:
/** The frequency shift of the base band. The filter result will be shifted by @c -_Fc. */
int32_t _Fc;
/** The center frequency of the base band. */
int32_t _Ff;
/** The input sample rate. */
int32_t _Fs;
/** The filter width. */
int32_t _width;
/** The order of the filter. Must be greater that 0. */
size_t _order;
/** The number of sample averages for the sub sampling. @c _sub_sample==1 means no subsampling. */
size_t _sub_sample;
/** Holds the desired output sample rate, _sub_sample will be adjusted accordingly. */
double _oFs;
/** The current index of the ring buffer. */
size_t _ring_offset;
/** Holds the current number of samples averaged. */
size_t _sample_count;
/** Holds the current sum of the last @c _sample_count samples. */
CSScalar _last;
/** Buffer size of the source. */
size_t _sourceBs;
/** The filter kernel of order _order. */
Buffer<CSScalar> _kernel;
/** A ring buffer of past values. The band-pass filtering is performed on this buffer. */
Buffer<CSScalar> _ring;
/** The output buffer. */
Buffer<CScalar> _buffer;
};
/** 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. */
template <class Scalar>
class BaseBand: public Sink<Scalar>, public Source, public FreqShiftBase<Scalar>
{
public:
/** The complex input scalar. */
typedef typename FreqShiftBase<Scalar>::CScalar CScalar;
/** The real super scalar. */
typedef typename FreqShiftBase<Scalar>::SScalar SScalar;
/** The complex super scalar. */
typedef typename FreqShiftBase<Scalar>::CSScalar CSScalar;
public:
/** Constructs a new BaseBand instance.
* @param Fc Specifies the center frequency of the base band. The resulting
* signal will be shifted down by this frequency.
* @param width Specifies the with of the band pass filter.
* @param order Specifies the order of the FIR band pass filter.
* @param sub_sample Specifies the sub-sampling of the resulting singnal. */
BaseBand(double Fc, double width, size_t order, size_t sub_sample)
: Sink<Scalar>(), Source(), FreqShiftBase<Scalar>(Fc, 0),
_Ff(Fc), _width(width), _order(std::max(size_t(1), order)),
_sub_sample(sub_sample), _ring_offset(0), _sample_count(0),
_last(0), _kernel(_order)
{
// Allocate and reset ring buffer:
_ring = Buffer<SScalar>(_order);
for (size_t i=0; i<_order; i++) { _ring[i] = 0; }
}
/** Constructs a new BaseBand instance.
* @param Fc Specifies the frequency of the base band. The resulting
* signal will be shifted down by this frequency.
* @param Ff Specifies the center frequency of the band pass filter.
* @param width Specifies the with of the band pass filter.
* @param order Specifies the order of the FIR band pass filter.
* @param sub_sample Specifies the sub-sampling of the resulting singnal. */
BaseBand(double Fc, double Ff, double width, size_t order, size_t sub_sample)
: Sink<Scalar>(), Source(), FreqShiftBase<Scalar>(Fc, 0),
_Ff(Ff), _width(width), _order(std::max(size_t(1), order)),
_sub_sample(sub_sample), _ring_offset(0), _sample_count(0),
_last(0), _kernel(_order)
{
// Allocate and reset ring buffer:
_ring = Buffer<SScalar>(_order);
for (size_t i=0; i<_order; i++) { _ring[i] = 0; }
}
/** Destructor. */
virtual ~BaseBand() {
// Free buffers
_buffer.unref();
_kernel.unref();
_ring.unref();
}
/** Configures the base band node. Implements the @c Sink interface. */
virtual void config(const Config &src_cfg) {
// Requires type, sample rate & buffer size
if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.hasBufferSize()) { return; }
// Check buffer type
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure BaseBand: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Store sample rate
this->setSampleRate(src_cfg.sampleRate());
// Calc output buffer size
size_t buffer_size = src_cfg.bufferSize()/_sub_sample;
if (src_cfg.bufferSize()%_sub_sample) { buffer_size += 1; }
// Allocate output buffer
_buffer = Buffer<CScalar>(buffer_size);
_last = 0;
_sample_count = 0;
_ring_offset = 0;
LogMessage msg(LOG_DEBUG);
msg << "Configured BaseBand node:" << std::endl
<< " type " << Traits< std::complex<Scalar> >::scalarId << std::endl
<< " sample-rate " << FreqShiftBase<Scalar>::sampleRate() << "Hz" << std::endl
<< " center freq " << FreqShiftBase<Scalar>::frequencyShift() << "Hz" << std::endl
<< " width " << _width << "Hz" << std::endl
<< " kernel " << _kernel << std::endl
<< " in buffer size " << src_cfg.bufferSize() << std::endl
<< " sub-sample by " << _sub_sample << std::endl
<< " out buffer size " << buffer_size;
Logger::get().log(msg);
// Propergate config
this->setConfig(Config(Traits< std::complex<Scalar> >::scalarId,
FreqShiftBase<Scalar>::sampleRate()/_sub_sample, buffer_size, 1));
}
virtual void setSampleRate(double Fs) {
FreqShiftBase<Scalar>::setSampleRate(Fs);
_update_filter_kernel();
/// @bug Also signal config change of the sourcce by setConfig()!
}
/** Processes the input buffer. Implements the @c Sink interface. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite)
{
if (_buffer.isUnused()) {
_process(buffer, _buffer);
} else {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "BaseBand: Drop buffer: Output buffer still in use.";
Logger::get().log(msg);
#endif
}
}
protected:
/** Performs the actual procssing. First, the bandpass filter is applied, then the filtered signal
* is shifted and finally the signal gets averaged over @c _sub_sample samples, implementing the
* averaging sub-sampling. */
inline void _process(const Buffer<Scalar> &in, const Buffer<CScalar> &out) {
size_t i=0, j=0;
for (; i<in.size(); i++, _sample_count++) {
// Store sample in ring buffer
_ring[_ring_offset] = in[i];
_last += this->applyFrequencyShift(_filter_ring());
// _ring_offset modulo _order
_ring_offset++;
if (_order == _ring_offset) { _ring_offset = 0; }
// If _sample_count samples have been averaged:
if (_sub_sample == _sample_count) {
// Store average in output buffer
out[j] = _last/CSScalar(_sub_sample);;
// reset average, sample count and increment output buffer index j
_last = 0; _sample_count=0; j++;
}
}
this->send(out.head(j), true);
}
/** Applies the filter on the data stored in the ring buffer. */
inline CSScalar _filter_ring()
{
CSScalar res = 0;
size_t idx = _ring_offset+1;
if (_order == idx) { idx = 0; }
for (size_t i=0; i<_order; i++, idx++) {
if (_order == idx) { idx = 0; }
res += _kernel[i]*_ring[idx];
}
return (res>>Traits<Scalar>::shift);
}
/** Calculates or updates the filter kernel. */
void _update_filter_kernel() {
// First, create a filter kernel of a low-pass filter with
// _width/2 cut-off
Buffer< std::complex<double> > alpha(_order);
double w = (2*M_PI*_width)/(2*FreqShiftBase<Scalar>::sampleRate());
double M = double(_order)/2;
double norm = 0;
for (size_t i=0; i<_order; i++) {
if (_order == (2*i)) { alpha[i] = 1; }
else { alpha[i] = std::sin(w*(i-M))/(w*(i-M)); }
}
// Shift filter by _Fc, apply window function and calc norm of filter
std::complex<double> phi(0.0, (2*M_PI*_Ff)/FreqShiftBase<Scalar>::sampleRate()); phi = std::exp(phi);
for (size_t i=0; i<_order; i++) {
// Shift filter kernel
alpha[i] = alpha[i]*std::exp(std::complex<double>(0, (2*M_PI*_Ff*i)/FreqShiftBase<Scalar>::sampleRate()));
// apply window function
alpha[i] *= (0.42 - 0.5*cos((2*M_PI*(i+1))/(_order+2)) +
0.08*cos((4*M_PI*(i+1))/(_order+2)));
// Calc norm
norm += std::abs(alpha[i]);
}
// Normalize filter coeffs and store in _kernel:
for (size_t i=0; i<_order; i++) {
_kernel[i] = ((double(1<<Traits<Scalar>::shift) * alpha[i]) / norm);
}
// free alpha
alpha.unref();
}
protected:
/** The center frequency of the band pass filter. */
double _Ff;
/** The width of the band pass filter. */
double _width;
/** The order of the band pass filter. */
size_t _order;
/** The number of averages taken for subsampling. */
size_t _sub_sample;
/** The current index of the ring buffer. */
size_t _ring_offset;
/** The current number of averages. */
size_t _sample_count;
/** The current sum of the last @c _sample_count samples. */
CSScalar _last;
/** If true, @c Fc!=0. */
bool _shift_freq;
/** \f$\exp(i\phi)\f$ look-up table */
Buffer< CSScalar > _lut;
/** The LUT index increment per (1<<4) samples. */
size_t _lut_inc;
/** The current LUT index times (1<<4). */
size_t _lut_count;
/** The filter kernel of order _order. */
Buffer<CSScalar> _kernel;
/** A ring buffer of past values. */
Buffer<SScalar> _ring;
/** The output buffer. */
Buffer<CScalar> _buffer;
protected:
/** Size of the look-up table. */
static const size_t _lut_size=128;
};
}
#endif // __SDR_BASEBAND_HH__

@ -0,0 +1,105 @@
#include "buffer.hh"
#include <stdlib.h>
using namespace sdr;
/* ********************************************************************************************* *
* Implementation of RawBuffer
* ********************************************************************************************* */
RawBuffer::RawBuffer()
: _ptr(0), _storage_size(0), _b_offset(0), _b_length(0), _refcount(0), _owner(0)
{
// pass...
}
RawBuffer::RawBuffer(char *data, size_t offset, size_t len)
: _ptr(data), _storage_size(offset+len), _b_offset(offset), _b_length(len),
_refcount(0), _owner(0)
{
// pass...
}
RawBuffer::RawBuffer(size_t N, BufferOwner *owner)
: _ptr((char *)malloc(N)), _storage_size(N), _b_offset(0), _b_length(N),
_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; }
// Set refcount, done...
if (_refcount) { (*_refcount) = 1; }
}
RawBuffer::RawBuffer(const RawBuffer &other)
: _ptr(other._ptr), _storage_size(other._storage_size),
_b_offset(other._b_offset), _b_length(other._b_length),
_refcount(other._refcount), _owner(other._owner)
{
// pass...
}
RawBuffer::RawBuffer(const RawBuffer &other, size_t offset, size_t len)
: _ptr(other._ptr), _storage_size(other._storage_size),
_b_offset(other._b_offset+offset), _b_length(len),
_refcount(other._refcount), _owner(other._owner)
{
// pass...
}
RawBuffer::~RawBuffer()
{
// pass...
}
/** Increment reference counter. */
void RawBuffer::ref() const {
if (0 != _refcount) { (*_refcount)++; }
}
/** Dereferences the buffer. */
void RawBuffer::unref() {
// If empty -> skip...
if ((0 == _ptr) || (0 == _refcount)) { return; }
// Decrement refcount
(*_refcount) -= 1;
// 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); }
// If the buffer is unreachable -> free
if (0 == (*_refcount)) {
free(_ptr); free(_refcount);
// mark as empty
_ptr = 0; _refcount=0;
}
}
/* ********************************************************************************************* *
* Implementation of RawRingBuffer
* ********************************************************************************************* */
RawRingBuffer::RawRingBuffer()
: RawBuffer(), _take_idx(0), _b_stored(0)
{
// pass...
}
RawRingBuffer::RawRingBuffer(size_t size)
: RawBuffer(size), _take_idx(0), _b_stored(0)
{
// pass...
}
RawRingBuffer::RawRingBuffer(const RawRingBuffer &other)
: RawBuffer(other), _take_idx(other._take_idx), _b_stored(other._b_stored)
{
// pass...
}
RawRingBuffer::~RawRingBuffer() {
// pass...
}

@ -0,0 +1,545 @@
#ifndef __SDR_BUFFER_HH__
#define __SDR_BUFFER_HH__
#include <vector>
#include <map>
#include <list>
#include <ostream>
#include <complex>
#include <inttypes.h>
#include <cstdlib>
#include <cstring>
#include "config.hh"
#include "exception.hh"
namespace sdr {
// Forward declarations
class RawBuffer;
/** Abstract class (interface) of a buffer owner. If a buffer is owned, the owner gets notified
* once the buffer gets unused. */
class BufferOwner {
public:
/** Gets called once an owned buffer gets unused. */
virtual void bufferUnused(const RawBuffer &buffer) = 0;
};
/** Base class of all buffers, represents an untyped array of bytes. */
class RawBuffer
{
public:
/** Constructs an empty buffer. An empty buffer cannot be owned. */
RawBuffer();
/** Constructor from unowned data. */
RawBuffer(char *data, size_t offset, size_t len);
/** Constructs a buffer and allocates N bytes. */
RawBuffer(size_t N, BufferOwner *owner=0);
/** Copy constructor. */
RawBuffer(const RawBuffer &other);
/** Creates a new view on the buffer. */
RawBuffer(const RawBuffer &other, size_t offset, size_t len);
/** Destructor. */
virtual ~RawBuffer();
/** Assignment. */
inline const RawBuffer &operator= (const RawBuffer &other) {
// Now, reference new one
_ptr = other._ptr;
_storage_size = other._storage_size;
_b_offset = other._b_offset;
_b_length = other._b_length;
_refcount = other._refcount;
_owner = other._owner;
// done.
return *this;
}
/** Returns the pointer to the data (w/o view). */
inline char *ptr() const { return (char *)_ptr; }
/** Returns the pointer to the data of the buffer view. */
inline char *data() const { return ((char *)_ptr+_b_offset); }
/** Returns the offset of the data by the view. */
inline size_t bytesOffset() const { return _b_offset; }
/** Returns the size of the buffer by the view. */
inline size_t bytesLen() const { return _b_length; }
/** Returns the raw buffer size in bytes. */
inline size_t storageSize() const { return _storage_size; }
/** Returns true if the buffer is invalid/empty. */
inline bool isEmpty() const { return 0 == _ptr; }
/** Increment reference counter. */
void ref() const;
/** Dereferences the buffer. */
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
* owner holds the only reference to the buffer. */
inline bool isUnused() const {
if (0 == _refcount) { return true; }
return (1 == (*_refcount));
}
protected:
/** Holds the pointer to the data or 0, if buffer is empty. */
char *_ptr;
/** Holds the size of the buffer in bytes. */
size_t _storage_size;
/** Holds the offset of the buffer in bytes. */
size_t _b_offset;
/** Holds the length of the buffer (view) in bytes. */
size_t _b_length;
/** The reference counter. */
int *_refcount;
/** Holds a weak reference the buffer owner. */
BufferOwner *_owner;
};
/** A typed buffer. */
template <class T>
class Buffer: public RawBuffer
{
public:
/** Empty constructor. */
Buffer() : RawBuffer(), _size(0) {
// pass...
}
/** Constructor from raw data. */
Buffer(T *data, size_t size)
: RawBuffer((char *)data, 0, sizeof(T)*size), _size(size) {
// pass...
}
/** Creates a buffer with N samples. */
Buffer(size_t N, BufferOwner *owner=0)
: RawBuffer(N*sizeof(T), owner), _size(N) {
// pass...
}
/** Create a new reference to the buffer. */
Buffer(const Buffer<T> &other)
: RawBuffer(other), _size(other._size) {
// pass...
}
/** Destructor. */
virtual ~Buffer() {
_size = 0;
}
/** Explicit type cast. */
explicit Buffer(const RawBuffer &other)
: RawBuffer(other), _size(_b_length/sizeof(T))
{
// pass
}
public:
/** Assignment operator, turns this buffer into a reference to the @c other buffer. */
const Buffer<T> &operator= (const Buffer<T> other) {
RawBuffer::operator =(other);
_size = other._size;
return *this;
}
/** This is used to store buffers in sets. The comparison is performed on the pointer of the
* buffers and not on their content. */
inline bool operator<(const Buffer<T> &other) const {
// Comparison by pointer to data
return this->_ptr < other._ptr;
}
/** Returns the number of elements of type @c T in this buffer. */
inline size_t size() const { return _size; }
/** Element access. If compiled in debug mode, this function will perform a check on the
* index and throws a @c RuntimeError exception if the index is out of bounds. */
inline T &operator[] (int idx) const {
#ifdef SDR_DEBUG
if ((idx >= _size) || (idx < 0)) {
RuntimeError err;
err << "Index " << idx << " out of bounds [0," << _size << ")";
throw err;
}
#endif
return reinterpret_cast<T *>(_ptr+_b_offset)[idx];
}
/** Returns the \f$l^2\f$ norm of the buffer. */
inline double norm2() const {
double nrm2 = 0;
for (size_t i=0; i<size(); i++) {
nrm2 += std::real(std::conj((*this)[i])*(*this)[i]);
}
return std::sqrt(nrm2);
}
/** Returns the \f$l^1\f$ norm of the buffer. */
inline double norm() const {
double nrm = 0;
for (size_t i=0; i<size(); i++) {
nrm += std::abs((*this)[i]);
}
return nrm;
}
/** Returns the \f$l^p\f$ norm of the buffer. */
inline double norm(double p) const {
double nrm = 0;
for (size_t i=0; i<size(); i++) {
nrm += std::pow(std::abs((*this)[i]), p);
}
return std::pow(nrm, 1./p);
}
/** In-place, element wise product of the buffer with the scalar @c a. */
inline Buffer<T> &operator *=(const T &a) {
for (size_t i=0; i<size(); i++) {
(*this)[i] *= a;
}
return *this;
}
/** In-place, element wise division of the buffer with the scalar @c a. */
inline Buffer<T> &operator /=(const T &a) {
for (size_t i=0; i<size(); i++) {
(*this)[i] /= a;
}
return *this;
}
/** Explicit type cast. */
template <class oT>
Buffer<oT> as() const {
return Buffer<oT>((const RawBuffer &)(*this));
}
/** Returns a new view on this buffer. */
inline Buffer<T> sub(size_t offset, size_t len) const {
if ((offset+len) > _size) { return Buffer<T>(); }
return Buffer<T>(RawBuffer(*this, offset*sizeof(T), len*sizeof(T)));
}
/** Returns a new view on this buffer. */
inline Buffer<T> head(size_t n) const {
if (n > _size) { return Buffer<T>(); }
return this->sub(0, n);
}
/** Returns a new view on this buffer. */
inline Buffer<T> tail(size_t n) const {
if (n > _size) { return Buffer<T>(); }
return this->sub(_size-n, n);
}
protected:
/** Holds the number of elements of type T in the buffer. */
size_t _size;
};
/** Pretty printing of a buffer. */
template <class Scalar>
std::ostream &
operator<< (std::ostream &stream, const sdr::Buffer<Scalar> &buffer)
{
stream << "[";
if (10 < buffer.size()) {
stream << +buffer[0];
for (size_t i=1; i<5; i++) {
stream << ", " << +buffer[i];
}
stream << ", ..., " << +buffer[buffer.size()-6];
for (size_t i=1; i<5; i++) {
stream << ", " << +buffer[buffer.size()+i-6];
}
} else {
if (0 < buffer.size()) {
stream << +buffer[0];
for (size_t i=1; i<buffer.size(); i++) {
stream << ", " << +buffer[i];
}
}
}
stream << "]";
return stream;
}
/** A set of buffers, that tracks their usage. Frequently it is impossible to predict the time, a
* buffer will be in use. Instead of allocating a new buffer during runtime, one may allocate
* several buffer in advance. In this case, it is important to track which buffer is still in use
* efficiently. This class implements this functionality. A @c BufferSet pre-allocates several
* buffers. Once a buffer is requested from the set, it gets marked as "in-use". Once the buffer
* gets ununsed, it will be marked as "unused" and will be available again. */
template <class Scalar>
class BufferSet: public BufferOwner
{
public:
/** Preallocates N buffers of size @c size. */
BufferSet(size_t N, size_t size)
: _bufferSize(size)
{
_free_buffers.reserve(N);
for (size_t i=0; i<N; i++) {
Buffer<Scalar> buffer(size, this);
_buffers[buffer.ptr()] = buffer;
_free_buffers.push_back(buffer.ptr());
}
}
/** Destructor, unreferences all buffers. Buffers still in use, are freed once they are
* dereferenced. */
virtual ~BufferSet() {
typename std::map<void *, Buffer<Scalar> >::iterator item = _buffers.begin();
for(; item != _buffers.end(); item++) {
item->second.unref();
}
_buffers.clear();
_free_buffers.clear();
}
/** Returns true if there is a free buffer. */
inline bool hasBuffer() { return _free_buffers.size(); }
/** Obtains a free buffer. */
inline Buffer<Scalar> getBuffer() {
void *id = _free_buffers.back(); _free_buffers.pop_back();
return _buffers[id];
}
/** Callback gets called once the buffer gets unused. */
virtual void bufferUnused(const RawBuffer &buffer) {
// Add buffer to list of free buffers if they are still owned
if (0 != _buffers.count(buffer.ptr())) {
_free_buffers.push_back(buffer.ptr());
}
}
/** Resize the buffer set. */
void resize(size_t numBuffers) {
if (_buffers.size() == numBuffers) { return; }
if (_buffers.size() < numBuffers) {
// add some buffers
size_t N = (numBuffers - _buffers.size());
for (size_t i=0; i<N; i++) {
Buffer<Scalar> buffer(_bufferSize, this);
_buffers[buffer.ptr()] = buffer;
}
}
}
protected:
/** Size of each buffer. */
size_t _bufferSize;
/** Holds a reference to each buffer of the buffer set, referenced by the data pointer of the
* buffer. */
std::map<void *, Buffer<Scalar> > _buffers;
/** A vector of all unused buffers. */
std::vector<void *> _free_buffers;
};
/** A simple ring buffer. */
class RawRingBuffer: public RawBuffer
{
public:
/** Empty constructor. */
RawRingBuffer();
/** Constructs a raw ring buffer with size @c size. */
RawRingBuffer(size_t size);
/** Copy constructor. */
RawRingBuffer(const RawRingBuffer &other);
/** Destructor. */
virtual ~RawRingBuffer();
/** Assignment operator, turns this ring buffer into a reference to the other one. */
inline const RawRingBuffer &operator=(const RawRingBuffer &other) {
RawBuffer::operator =(other);
_take_idx = other._take_idx;
_b_stored = other._b_stored;
return *this;
}
/** Element access. */
char &operator[] (int idx) {
#ifdef SDR_DEBUG
if ((idx < 0) || (idx>=(int)_b_stored)) {
RuntimeError err;
err << "RawRingBuffer: Index " << idx << " out of bounds [0," << bytesLen() << ").";
throw err;
}
#endif
int i = _take_idx+idx;
if (i >= (int)_storage_size) { i -= _storage_size; }
return *(ptr()+i);
}
/** Returns the number of bytes available for reading. */
inline size_t bytesLen() const { return _b_stored; }
/** Returns the number of free bytes in the ring buffer. */
inline size_t bytesFree() const {
return _storage_size-_b_stored;
}
/** Puts the given data into the ring-buffer. The size of @c src must be smaller or equal to
* the number of free bytes. Returns @c true if the data was written successfully to the buffer. */
inline bool put(const RawBuffer &src) {
/// @todo Allow overwrite
// Data does not fit into ring buffer -> stop
if (src.bytesLen() > bytesFree()) { return false; }
// Obtain put_ptr
size_t put_idx = _take_idx+_b_stored;
if (put_idx > _storage_size) { put_idx -= _storage_size; }
// store data
if (_storage_size >= (put_idx+src.bytesLen())) {
// If the data can be copied directly
memcpy(_ptr+put_idx, src.data(), src.bytesLen());
_b_stored += src.bytesLen();
return true;
}
// If wrapped around -> Store first half
size_t num_a = _storage_size-put_idx;
memcpy(_ptr+put_idx, src.data(), num_a);
// store second half
memcpy(_ptr, src.data()+num_a, src.bytesLen()-num_a);
_b_stored += src.bytesLen();
return true;
}
/** Take @c N bytes from the ring buffer and store it into the given buffer @c dest.
* Returns @c true if the data was taken successfully from the ring buffer. */
inline bool take(const RawBuffer &dest, size_t N) {
// If there is not enough space in dest to store N bytes
if (N > dest.bytesLen()) { return false; }
// If there is not enough data available
if (N > bytesLen()) { return false; }
// If data can be taken at once
if (_storage_size > (_take_idx+N)) {
memcpy(dest.data(), _ptr+_take_idx, N);
_take_idx += N; _b_stored -= N;
return true;
}
// Copy in two steps
size_t num_a = _storage_size-_take_idx;
memcpy(dest.data(), _ptr+_take_idx, num_a);
memcpy(dest.data()+num_a, _ptr, N-num_a);
_take_idx = N-num_a; _b_stored -= N;
return true;
}
/** Drops at most @c N bytes from the buffer. */
inline void drop(size_t N) {
N = std::min(N, bytesLen());
if (_storage_size>(_take_idx+N)) { _take_idx+=N; _b_stored -= N; }
else { _take_idx = N-(_storage_size-_take_idx); _b_stored -= N; }
}
/** Clear the ring-buffer. */
inline void clear() { _take_idx = _b_stored = 0; }
/** Resizes the ring buffer. Also clears it. */
inline void resize(size_t N) {
if (_storage_size == N) { return; }
_take_idx=_b_stored=0;
RawBuffer::operator =(RawBuffer(N));
}
protected:
/** The current read pointer. */
size_t _take_idx;
/** Offset of the write pointer relative to the @c _take_idx ptr, such that the number of
* stored bytes is @c _b_stored and the write index is (_take_idx+_b_stored) % _storage_size. */
size_t _b_stored;
};
/** A simple typed ring-buffer. */
template <class Scalar>
class RingBuffer: public RawRingBuffer
{
public:
/** Empty constructor. */
RingBuffer() : RawRingBuffer(), _size(0), _stored(0) { }
/** Constructs a ring buffer of size @c N. */
RingBuffer(size_t N) : RawRingBuffer(N*sizeof(Scalar)), _size(N), _stored(0) { }
/** Copy constructor, creates a reference to the @c other ring buffer. */
RingBuffer(const RingBuffer<Scalar> &other)
: RawRingBuffer(other), _size(other._size), _stored(other._stored) { }
/** Destructor. */
virtual ~RingBuffer() { }
/** Assigment operator, turns this buffer into a reference to the @c other ring buffer. */
const RingBuffer<Scalar> &operator= (const RingBuffer<Scalar> &other) {
RawRingBuffer::operator =(other);
_size = other._size; _stored = other._stored;
return *this;
}
/** Element access. */
Scalar &operator[] (int idx) {
return reinterpret_cast<Scalar &>(RawRingBuffer::operator [](idx*sizeof(Scalar)));
}
/** Returns the number of stored elements. */
inline size_t stored() const { return _stored; }
/** Returns the number of free elements. */
inline size_t free() const { return _size-_stored; }
/** Returns the size of the ring buffer. */
inline size_t size() const { return _size; }
/** Puts the given elements into the ring buffer. If the elements do not fit into the
* buffer, @c false is returned. */
inline bool put(const Buffer<Scalar> &data) {
if (RawRingBuffer::put(data)) {
_stored += data.size(); return true;
}
return false;
}
/** Takes @c N elements from the buffer and stores them into @c dest. Returns @c true
* on success. */
inline bool take(const Buffer<Scalar> &dest, size_t N) {
if (RawRingBuffer::take(dest, N*sizeof(Scalar))) {
_stored -= N; return true;
}
return false;
}
/** Drops @c N elements from the ring buffer. */
inline void drop(size_t N) {
RawRingBuffer::drop(N*sizeof(Scalar)); _stored = _b_stored/sizeof(Scalar);
}
/** Resizes the ring buffer to @c N elements. */
inline void resize(size_t N) {
RawRingBuffer::resize(N*sizeof(Scalar));
}
protected:
/** The size of the ring buffer. */
size_t _size;
/** The number of stored elements. */
size_t _stored;
};
}
#endif // __SDR_BUFFER_HH__

@ -0,0 +1,2 @@
#include "buffernode.hh"

@ -0,0 +1,105 @@
#ifndef __SDR_BUFFERNODE_HH__
#define __SDR_BUFFERNODE_HH__
#include "node.hh"
#include "config.hh"
#include "logger.hh"
#include <iostream>
#include <cstring>
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. */
template <class Scalar>
class BufferNode : public Sink<Scalar>, public Source
{
public:
/** Constructs a new buffer node.
* @param bufferSize Specifies the desired size of the output buffers. */
BufferNode(size_t bufferSize)
: _bufferSize(bufferSize), _bufferSet(0, _bufferSize), _temp(bufferSize), _samplesLeft(0)
{
// pass...
}
/** Configures the buffer node. */
virtual void config(const Config &src_cfg)
{
// Check if source config is complete
if (Config::Type_UNDEFINED == src_cfg.type()) { return; }
if (0 == src_cfg.bufferSize()) { return; }
if (0 == src_cfg.numBuffers()) { return; }
// Check source type
if (src_cfg.type() != Config::typeId<Scalar>()) {
ConfigError err;
err << "Can not configure BufferNode sink. Source type is " << src_cfg.type()
<< " expected " << Config::typeId<Scalar>() << std::endl;
throw err;
}
// Estimate number of buffers needed:
size_t totSize = src_cfg.bufferSize()*src_cfg.numBuffers();
size_t numBuffers = std::max(size_t(2), totSize/_bufferSize);
_bufferSet.resize(numBuffers);
LogMessage msg(LOG_DEBUG);
msg << "Configure BufferNode: " << std::endl
<< " type: " << src_cfg.type() << std::endl
<< " sample-rate: " << src_cfg.sampleRate() << std::endl
<< " buffer-size: " << src_cfg.bufferSize()
<< " -> " << _bufferSize << std::endl
<< " # buffers: " << src_cfg.numBuffers();
// Propergate source config
this->setConfig(Config(src_cfg.type(), src_cfg.sampleRate(), _bufferSize, numBuffers));
}
/** Process the incomming data. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite)
{
// If the current buffer buffer does not contain enough smaples to fill an output buffer:
if ((_samplesLeft+buffer.size()) < _bufferSize) {
memcpy(_temp.data()+_samplesLeft*sizeof(Scalar), buffer.data(), sizeof(Scalar)*buffer.size());
_samplesLeft += buffer.size();
return;
}
// There are enough samples collected to fill an ouput buffer,
// fill first out buffer and send it
Buffer<Scalar> out = _bufferSet.getBuffer();
memcpy(out.data(), _temp.data(), sizeof(Scalar)*_samplesLeft);
memcpy(out.data()+_samplesLeft*sizeof(Scalar), buffer.data(), sizeof(Scalar)*(_bufferSize-_samplesLeft));
size_t in_offset = (_bufferSize-_samplesLeft);
// Determine the number of samples left
_samplesLeft = buffer.size()+_samplesLeft-_bufferSize;
this->send(out);
// Process remaining data
while (_samplesLeft >= _bufferSize) {
Buffer<Scalar> out = _bufferSet.getBuffer();
memcpy(out.data(), buffer.data()+in_offset*sizeof(Scalar), _bufferSize*sizeof(Scalar));
in_offset += _bufferSize;
_samplesLeft -= _bufferSize;
this->send(out);
}
// Store data left over into temp
memcpy(_temp.data(), buffer.data(), _samplesLeft*sizeof(Scalar));
}
protected:
/** The desired buffer size. */
size_t _bufferSize;
/** A set of output buffers. */
BufferSet<Scalar> _bufferSet;
/** An intermediate buffer to hold left-over samples from the previous buffers. */
Buffer<Scalar> _temp;
/** Number of samples left. */
size_t _samplesLeft;
};
}
#endif // __SDR_BUFFERNODE_HH__

@ -0,0 +1,2 @@
#include "combine.hh"

@ -0,0 +1,225 @@
#ifndef __SDR_COMBINE_HH__
#define __SDR_COMBINE_HH__
#include "config.hh"
#include "traits.hh"
#include "node.hh"
#include "buffer.hh"
#include <limits>
namespace sdr {
template <class Scalar> class Combine;
/** A single sink of a Combine node. */
template <class Scalar>
class CombineSink: public Sink<Scalar>
{
public:
/** Constructor. */
CombineSink(Combine<Scalar> *combine, size_t index, RingBuffer<Scalar> &buffer)
: Sink<Scalar>(), _index(index), _parent(combine), _buffer(buffer)
{
// Pass...
}
/** Destructor. */
virtual ~CombineSink() {
// pass...
}
/** Configures the sink. */
virtual void config(const Config &src_cfg) {
// Requires sample rate and type
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Check type
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure CombinSink: Invalid source type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Notify parent
_parent->notifyConfig(_index, src_cfg);
}
/** Handles the given buffer. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
// Copy data into ring buffer and notify parent
_buffer.put(buffer);
_parent->notifyData(_index);
}
protected:
/** The index of the sink within the combine node. */
size_t _index;
/** A reference to the combine node. */
Combine<Scalar> *_parent;
/** The input ring-buffer. */
RingBuffer<Scalar> &_buffer;
};
/** A combine node. This node allows to combine several streams into one. */
template <class Scalar>
class Combine
{
public:
/** Constructor, @n N specifies the number of sinks. */
Combine(size_t N) {
_buffers.reserve(N); _sinks.reserve(N);
for (size_t i=0; i<N; i++) {
_buffers.push_back(RingBuffer<Scalar>());
_sinks.push_back(new CombineSink<Scalar>(this, i, _buffers.back()));
}
}
/** Destructor. */
virtual ~Combine() {
// Unref all buffers and free sinks
for (size_t i=0; i<_sinks.size(); i++) {
_buffers[i].unref(); delete _sinks[i];
}
_buffers.clear(); _sinks.clear();
}
/** Needs to be overridden. */
virtual void config(const Config &cfg) = 0;
/** Needs to be overridden. */
virtual void process(std::vector< RingBuffer<Scalar> > &buffers, size_t N) = 0;
protected:
/** Unifies the configuration of all sinks. */
void notifyConfig(size_t idx, const Config &cfg)
{
// Requires type, sampleRate and buffer size
if (!cfg.hasType() || !cfg.hasSampleRate() || !cfg.hasBufferSize()) { return; }
// check or unify type
if (!_config.hasType()) { _config.setType(cfg.type()); }
else if (_config.type() != cfg.type()) {
ConfigError err;
err << "Can not configure Combine node: Invalid type of sink #" << idx
<< " " << cfg.type() << ", expected " << _config.type();
throw err;
}
// Check sample rate
if (!_config.hasSampleRate()) { _config.setSampleRate(cfg.sampleRate()); }
else if (_config.sampleRate() != cfg.sampleRate()) {
ConfigError err;
err << "Can ont configure Combine node: Invalid sample rate of sink #" << idx
<< " " << cfg.sampleRate() << ", expected " << _config.sampleRate();
throw err;
}
// Determine max buffer size
if (!_config.hasBufferSize()) { _config.setBufferSize(cfg.bufferSize()); }
else {
// Take maximum:
_config.setBufferSize(std::max(_config.bufferSize(), cfg.bufferSize()));
}
// Reallocate buffers
for (size_t i=0; i<_sinks.size(); i++) {
_buffers[i] = RingBuffer<Scalar>(_config.bufferSize());
}
// Propergate config
this->config(_config);
}
/** Determines the minimum amount of data that is available on all ring buffers. */
void notifyData(size_t idx) {
// Determine minimum size of available data
size_t N = std::numeric_limits<size_t>::max();
for (size_t i=0; i<_sinks.size(); i++) {
N = std::min(N, _buffers[i].stored());
}
if (N > 0) { this->process(_buffers, N); }
}
protected:
/** The ring buffers of all combine sinks. */
std::vector< RingBuffer<Scalar> > _buffers;
/** The combine sinks. */
std::vector< CombineSink<Scalar> *> _sinks;
/** The output configuration. */
Config _config;
friend class CombineSink<Scalar>;
};
/** Interleaves several input streams. */
template <class Scalar>
class Interleave : public Combine<Scalar>, public Source
{
public:
/** Constructor. */
Interleave(size_t N)
: Combine<Scalar>(N), Source(), _N(N), _buffer()
{
// pass...
}
/** Retunrs the i-th sink. */
Sink<Scalar> *sink(size_t i) {
if (i >= _N) {
RuntimeError err;
err << "Interleave: Sink index " << i << " out of range [0," << _N << ")";
throw err;
}
return Combine<Scalar>::_sinks[i];
}
/** Configures the interleave node. */
virtual void config(const Config &cfg) {
//Requres type & buffer size
if (!cfg.hasType() || !cfg.hasBufferSize()) { return; }
// Check type
if (Config::typeId<Scalar>() != cfg.type()) {
ConfigError err;
err << "Can not configure Interleave node: Invalid source type " << cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Allocate buffer:
_buffer = Buffer<Scalar>(_N*cfg.bufferSize());
// Propergate config
this->setConfig(Config(Config::typeId<Scalar>(), cfg.sampleRate(), _buffer.size(), 1));
}
/** Processes the data from all sinks. */
virtual void process(std::vector<RingBuffer<Scalar> > &buffers, size_t N) {
if (0 == N) { return; }
if (! _buffer.isUnused()) {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "Interleave: Output buffer in use: Drop " << _N << "x" << N
<< " input values";
Logger::get().log(msg);
#endif
for (size_t i=0; i<buffers.size(); i++) { buffers[i].drop(N); }
return;
}
size_t num = std::min(_buffer.size()/_N,N);
// Interleave data
size_t idx = 0;
for (size_t i=0; i<num; i++) {
for (size_t j=0; j<_N; j++, idx++) {
_buffer[idx] = buffers[j][i];
}
}
// Drop num elements from all ring buffers
for (size_t i=0; i<_N; i++) {
buffers[i].drop(num);
}
// Send buffer
this->send(_buffer.head(num*_N));
}
protected:
/** The number of sinks. */
size_t _N;
/** The putput buffer. */
Buffer<Scalar> _buffer;
};
}
#endif // __SDR_COMBINE_HH__

@ -0,0 +1,5 @@
#cmakedefine SDR_DEBUG 1
#cmakedefine SDR_WITH_FFTW 1
#cmakedefine SDR_WITH_PORTAUDIO 1
#cmakedefine SDR_WITH_RTLSDR 1

@ -0,0 +1,2 @@
#include "demod.hh"

@ -0,0 +1,402 @@
#ifndef __SDR_DEMOD_HH__
#define __SDR_DEMOD_HH__
#include "node.hh"
#include "traits.hh"
#include "config.hh"
#include "combine.hh"
#include "logger.hh"
namespace sdr {
/** Amplitude modulation (AM) demodulator from an I/Q signal. */
template <class Scalar>
class AMDemod
: public Sink< std::complex<Scalar> >, public Source
{
public:
/** Constructor. */
AMDemod() : Sink< std::complex<Scalar> >(), Source()
{
// pass...
}
/** Destructor. */
virtual ~AMDemod() {
// pass...
}
/** Configures the AM demod. */
virtual void config(const Config &src_cfg) {
// Requires type & buffer size
if (!src_cfg.hasType() || !src_cfg.hasBufferSize()) { return; }
// Check if buffer type matches template
if (Config::typeId< std::complex<Scalar> >() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AMDemod: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId< std::complex<Scalar> >();
throw err;
}
// Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Configure AMDemod:" << std::endl
<< " input type: " << Traits< std::complex<Scalar> >::scalarId << std::endl
<< " output type: " << Traits<Scalar>::scalarId << std::endl
<< " sample rate: " << src_cfg.sampleRate() << std::endl
<< " buffer size: " << src_cfg.bufferSize();
Logger::get().log(msg);
// Propergate config
this->setConfig(Config(Config::typeId<Scalar>(), src_cfg.sampleRate(),
src_cfg.bufferSize(), src_cfg.numBuffers()));
}
/** 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); }
else { out_buffer = _buffer; }
// Perform demodulation
for (size_t i=0; i<buffer.size(); i++) {
out_buffer[i] = std::sqrt(buffer[i].real()*buffer[i].real() +
buffer[i].imag()*buffer[i].imag());
}
// If the source allowed to overwrite the buffer, this source will allow it too.
// If this source used the internal buffer (_buffer), it allows to overwrite it anyway.
this->send(out_buffer.head(buffer.size()), true);
}
protected:
/** The output buffer. */
Buffer<Scalar> _buffer;
};
/** SSB upper side band (USB) demodulator from an I/Q signal. */
template <class Scalar>
class USBDemod
: public Sink< std::complex<Scalar> >, public Source
{
public:
/** The complex input scalar. */
typedef std::complex<Scalar> CScalar;
/** The real compute scalar. */
typedef typename Traits<Scalar>::SScalar SScalar;
public:
/** Constructor. */
USBDemod() : Sink<CScalar>(), Source()
{
// pass...
}
/** Destructor. */
virtual ~USBDemod() {
// pass...
}
/** Configures the USB demodulator. */
virtual void config(const Config &src_cfg) {
// Requires type & buffer size
if (!src_cfg.hasType() || !src_cfg.hasBufferSize()) { return; }
// Check if buffer type matches template
if (Config::typeId<CScalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure USBDemod: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<CScalar>();
throw err;
}
// Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Configure USBDemod:" << std::endl
<< " input type: " << Traits< std::complex<Scalar> >::scalarId << std::endl
<< " output type: " << Traits<Scalar>::scalarId << std::endl
<< " sample rate: " << src_cfg.sampleRate() << std::endl
<< " buffer size: " << src_cfg.bufferSize();
Logger::get().log(msg);
// Propergate config
this->setConfig(Config(Config::typeId<Scalar>(), src_cfg.sampleRate(),
src_cfg.bufferSize(), 1));
}
/** Performs the demodulation. */
virtual void process(const Buffer<CScalar> &buffer, bool allow_overwrite) {
if (allow_overwrite) {
// Process in-place
_process(buffer, Buffer<Scalar>(buffer));
} else if (_buffer.isUnused()) {
// 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
}
}
protected:
/** The actual demodulation. */
void _process(const Buffer< std::complex<Scalar> > &in, const Buffer< Scalar> &out) {
for (size_t i=0; i<in.size(); i++) {
out[i] = (SScalar(std::real(in[i])) + SScalar(std::imag(in[i])))/2;
}
this->send(out.head(in.size()));
}
protected:
/** The output buffer. */
Buffer<Scalar> _buffer;
};
/** Demodulates FM from an I/Q signal. */
template <class iScalar, class oScalar=iScalar>
class FMDemod: public Sink< std::complex<iScalar> >, public Source
{
public:
/** The super scalar. */
typedef typename Traits<iScalar>::SScalar SScalar;
public:
/** Constructor. */
FMDemod():
Sink< std::complex<iScalar> >(), Source(), _shift(0), _can_overwrite(false)
{
_shift = 8*(sizeof(oScalar)-sizeof(iScalar));
}
/** Destructor. */
virtual ~FMDemod() {
// pass...
}
/** Configures the FM demodulator. */
virtual void config(const Config &src_cfg) {
// Requires type & buffer size
if (!src_cfg.hasType() || !src_cfg.hasBufferSize()) { return; }
// Check if buffer type matches template
if (Config::typeId< std::complex<iScalar> >() != src_cfg.type()) {
ConfigError err;
err << "Can not configure USBDemod: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId< std::complex<iScalar> >();
throw err;
}
// Unreference buffer if non-empty
if (! _buffer.isEmpty()) { _buffer.unref(); }
// Allocate buffer
_buffer = Buffer<oScalar>(src_cfg.bufferSize());
// reset last value
_last_value = 0;
// Check if FM demod can be performed in-place
_can_overwrite = (sizeof(std::complex<iScalar>) >= sizeof(oScalar));
LogMessage msg(LOG_DEBUG);
msg << "Configured FMDemod node:" << std::endl
<< " sample-rate " << src_cfg.sampleRate() << std::endl
<< " in-type / out-type " << src_cfg.type()
<< " / " << Config::typeId<oScalar>() << std::endl
<< " in-place " << (_can_overwrite ? "true" : "false") << std::endl
<< " output scale: 2^" << _shift;
Logger::get().log(msg);
// Propergate config
this->setConfig(Config(Config::typeId<oScalar>(), src_cfg.sampleRate(),
src_cfg.bufferSize(), 1));
}
/** Performs the FM demodulation. */
virtual void process(const Buffer<std::complex<iScalar> > &buffer, bool allow_overwrite)
{
if (0 == buffer.size()) { return; }
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
}
}
protected:
/** A fast approximative implementation of the std::atan2() on integers. */
inline SScalar _fast_atan2(SScalar a, SScalar b) {
const SScalar pi4 = (1<<(Traits<iScalar>::shift-2));
const SScalar pi34 = (3<<(Traits<iScalar>::shift-2));
SScalar aabs, angle;
if ((0 == a) && (0 == b)) { return 0; }
aabs = (a >= 0) ? a : -a;
if (b >= 0) { angle = pi4 - pi4*(b-aabs) / (b+aabs); }
else { angle = pi34 - pi4*(b+aabs) / (aabs-b); }
angle >>= 2;
return (a >= 0) ? angle : -angle;
}
/** 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())
+ SScalar(in[0].imag())*SScalar(last_value.imag());
SScalar b = SScalar(in[0].imag())*SScalar(last_value.real())
- SScalar(in[0].real())*SScalar(last_value.imag());
// update last value
last_value = in[0];
// calc output (prob. overwriting the last value)
out[0] = (_fast_atan2(a, b)<<_shift);
// Calc remaining values
for (size_t i=1; i<in.size(); i++) {
a = SScalar(in[i].real())*last_value.real()
+ SScalar(in[i].imag())*last_value.imag();
b = SScalar(in[i].imag())*last_value.real()
- SScalar(in[i].real())*last_value.imag();
last_value = in[i];
out[i] = (_fast_atan2(a, b)<<_shift);
}
// Store last value
_last_value = last_value;
// propergate result
this->send(out.head(in.size()));
}
protected:
/** Output rescaling. */
int _shift;
/** The last input value. */
std::complex<iScalar> _last_value;
/** If true, in-place demodulation is poissible. */
bool _can_overwrite;
/** The output buffer, unused if demodulation is performed in-place. */
Buffer<oScalar> _buffer;
};
/** Binary phase shift demodulation with carrier from an I/Q signal. */
template <class Scalar>
class BPSKDemod : public Combine< std::complex<Scalar> >, public Source
{
public:
/** Complex input scalar type. */
typedef std::complex<Scalar> CScalar;
/** Real super scalar. */
typedef typename Traits<Scalar>::SScalar SScalar;
/** Complex super scalar. */
typedef std::complex<SScalar> CSScalar;
public:
/** Constructor. */
BPSKDemod()
: Combine<CScalar>(2), Source(), _buffer(0), _last(0)
{
// pass...
}
/** Destructor. */
virtual ~BPSKDemod() {
_buffer.unref();
}
/** The signal input sink. */
inline Sink<CScalar> *signal() { return Combine<CScalar>::_sinks[0]; }
/** The reference/carrier input sink. */
inline Sink<CScalar> *reference() { return Combine<CScalar>::_sinks[1]; }
/** Configures the BPSK demodulator. */
virtual void config(const Config &cfg) {
// Requires type and buffer size
if(!cfg.hasType() || !cfg.hasBufferSize()) { return; }
// Check type
if (Config::typeId<CScalar>() != cfg.type()) {
ConfigError err;
err << "Can not configure BPSKDemod node: Invalid type " << cfg.type()
<< ", expected " << Config::typeId<CScalar>();
throw err;
}
// Allocate buffer
_buffer = Buffer<uint8_t>(cfg.bufferSize());
// Propergate config
this->setConfig(Config(Config::Type_u8, cfg.sampleRate(), _buffer.size(), 1));
}
/** Performs the BPSK demodulation. */
virtual void process(std::vector< RingBuffer< std::complex<Scalar> > > &buffers, size_t N)
{
// If there is nothing -> done
if (0 == N) { return; }
// If buffer is still in use, drop input
if (!_buffer.isUnused()) {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "BPSKDemod: Output buffer still in use. Drop input.";
Logger::get().log(msg);
#endif
for (size_t i=0; i<Combine<CScalar>::_sinks.size(); i++) {
Combine<CScalar>::_buffers[i].drop(N);
}
return;
}
// Process
N = std::min(N, _buffer.size());
// Compute first value:
CSScalar tmp = (CSScalar(buffers[0][0])*CSScalar(std::conj(buffers[1][0])));
SScalar a = _last.real()*tmp.real() + _last.imag()*tmp.imag();
SScalar b = _last.real()*tmp.imag() - _last.imag()*tmp.real();
_buffer[0] = (std::abs(std::atan2(a,b))>M_PI/2); _last = tmp;
for (size_t i=0; i<N; i++) {
tmp = (CSScalar(buffers[0][i])*CSScalar(std::conj(buffers[1][i])));
a = _last.real()*tmp.real() + _last.imag()*tmp.imag();
b = _last.real()*tmp.imag() - _last.imag()*tmp.real();
_buffer[i] = (std::abs(std::atan2(a,b))>M_PI/2); _last = tmp;
}
// Consume elements
for (size_t i=0; i<Combine<CScalar>::_sinks.size(); i++) {
Combine<CScalar>::_buffers[i].drop(N);
}
// Send buffer
this->send(_buffer.head(N));
}
protected:
/** Output buffer (bits). */
Buffer<uint8_t> _buffer;
/** The last input value (signal). */
CSScalar _last;
};
}
#endif // __SDR_DEMOD_HH__

@ -0,0 +1,49 @@
#ifndef __SDR_EXCEPTION_HH__
#define __SDR_EXCEPTION_HH__
#include <exception>
#include <sstream>
namespace sdr {
/** Base class of all SDR exceptions. */
class SDRError : public std::exception, public std::stringstream {
public:
/** Constructor. */
SDRError(): std::exception(), std::stringstream() { }
/** Copy constructor. */
SDRError(const SDRError &other)
: std::exception(), std::stringstream() { this->str(other.str()); }
/** Destructor. */
virtual ~SDRError() throw() { }
/** Implements the @c std::exception interface. */
virtual const char *what() const throw() { return this->str().c_str(); }
};
/** The configuration error class. */
class ConfigError : public SDRError {
public:
/** Constructor. */
ConfigError(): SDRError() {}
/** Copy constructor. */
ConfigError(const ConfigError &other): SDRError(other) {}
/** Destructor. */
virtual ~ConfigError() throw() { }
};
/** The runtime error class. */
class RuntimeError: public SDRError {
public:
/** Constructor. */
RuntimeError(): SDRError() {}
/** Copy constructor. */
RuntimeError(const RuntimeError &other): SDRError(other) {}
/** Destructor. */
virtual ~RuntimeError() throw() { }
};
}
#endif // __SDR_EXCEPTION_HH__

@ -0,0 +1,2 @@
#include "fftplan.hh"

@ -0,0 +1,46 @@
#ifndef __SDR_FFTPLAN_HH__
#define __SDR_FFTPLAN_HH__
#include "buffer.hh"
#include "node.hh"
#include "config.hh"
namespace sdr {
// Forward declaration of FFTPlan
template <class Scalar> class FFTPlan { };
/** FFT module class, provides static methods to perfrom a FFT directly. */
class FFT {
public:
/** Direction type. */
typedef enum {
FORWARD, BACKWARD
} Direction;
/** Performs a FFT transform. */
template <class Scalar>
static void exec(const Buffer< std::complex<Scalar> > &in,
const Buffer< std::complex<Scalar> > &out, FFT::Direction dir)
{
FFTPlan<Scalar> plan(in, out, dir); plan();
}
/** Performs an in-place FFT transform. */
template <class Scalar>
static void exec(const Buffer< std::complex<Scalar> > &inplace, FFT::Direction dir)
{
FFTPlan<Scalar> plan(inplace, dir); plan();
}
};
}
#ifdef SDR_WITH_FFTW
#include "fftplan_fftw3.hh"
#endif
#endif // __SDR_FFTPLAN_HH__

@ -0,0 +1,149 @@
#ifndef __SDR_FFTPLAN_FFTW3_HH__
#define __SDR_FFTPLAN_FFTW3_HH__
#include "fftplan.hh"
#include <fftw3.h>
namespace sdr {
/** Template specialization for a FFT transform on std::complex<double> values. */
template<>
class FFTPlan<double>
{
public:
/** Constructor. */
FFTPlan(const Buffer< std::complex<double> > &in, const Buffer< std::complex<double> > &out,
FFT::Direction dir)
: _in(in), _out(out)
{
if (in.size() != out.size()) {
ConfigError err;
err << "Can not construct FFT plan: input & output buffers are of different size!";
throw err;
}
if (in.isEmpty() || out.isEmpty()) {
ConfigError err;
err << "Can not construct FFT plan: input or output buffer is empty!";
throw err;
}
int fftw_dir = FFTW_FORWARD;
if (FFT::BACKWARD == dir) { fftw_dir = FFTW_BACKWARD; }
_plan = fftw_plan_dft_1d(
in.size(), (fftw_complex *)in.data(), (fftw_complex *)out.data(),
fftw_dir, FFTW_ESTIMATE);
}
/** Constructor. */
FFTPlan(const Buffer< std::complex<double> > &inplace, FFT::Direction dir)
: _in(inplace), _out(inplace)
{
if (inplace.isEmpty()) {
ConfigError err;
err << "Can not construct FFT plan: Buffer is empty!";
throw err;
}
int fftw_dir = FFTW_FORWARD;
if (FFT::BACKWARD == dir) { fftw_dir = FFTW_BACKWARD; }
_plan = fftw_plan_dft_1d(
inplace.size(), (fftw_complex *)inplace.data(), (fftw_complex *)inplace.data(),
fftw_dir, FFTW_ESTIMATE);
}
/** Destructor. */
virtual ~FFTPlan() {
fftw_destroy_plan(_plan);
}
/** Performs the transformation. */
void operator() () {
fftw_execute(_plan);
}
protected:
/** Input buffer. */
Buffer< std::complex<double> > _in;
/** Output buffer. */
Buffer< std::complex<double> > _out;
/** The FFT plan. */
fftw_plan _plan;
};
/** Template specialization for a FFT transform on std::complex<float> values. */
template<>
class FFTPlan<float>
{
public:
/** Constructor. */
FFTPlan(const Buffer< std::complex<float> > &in, const Buffer< std::complex<float> > &out,
FFT::Direction dir)
: _in(in), _out(out)
{
if (in.size() != out.size()) {
ConfigError err;
err << "Can not construct FFT plan: input & output buffers are of different size!";
throw err;
}
if (in.isEmpty() || out.isEmpty()) {
ConfigError err;
err << "Can not construct FFT plan: input or output buffer is empty!";
throw err;
}
int fftw_dir = FFTW_FORWARD;
if (FFT::BACKWARD == dir) { fftw_dir = FFTW_BACKWARD; }
_plan = fftwf_plan_dft_1d(
in.size(), (fftwf_complex *)in.data(), (fftwf_complex *)out.data(),
fftw_dir, FFTW_ESTIMATE);
}
/** Constructor. */
FFTPlan(const Buffer< std::complex<float> > &inplace, FFT::Direction dir)
: _in(inplace), _out(inplace)
{
if (inplace.isEmpty()) {
ConfigError err;
err << "Can not construct FFT plan: Buffer is empty!";
throw err;
}
int fftw_dir = FFTW_FORWARD;
if (FFT::BACKWARD == dir) { fftw_dir = FFTW_BACKWARD; }
_plan = fftwf_plan_dft_1d(
inplace.size(), (fftwf_complex *)inplace.data(), (fftwf_complex *)inplace.data(),
fftw_dir, FFTW_ESTIMATE);
}
/** Destructor. */
virtual ~FFTPlan() {
fftwf_destroy_plan(_plan);
}
/** Performs the FFT transform. */
void operator() () {
fftwf_execute(_plan);
}
protected:
/** Input buffer. */
Buffer< std::complex<float> > _in;
/** Output buffer. */
Buffer< std::complex<float> > _out;
/** The fft plan. */
fftwf_plan _plan;
};
}
#endif // __SDR_FFTPLAN_FFTW3_HH__

@ -0,0 +1,88 @@
#ifndef __SDR_FFTPLAN_NATIVE_HH__
#define __SDR_FFTPLAN_NATIVE_HH__
#include "traits.hh"
#include "fftplan.hh"
namespace sdr {
/** Trivial FFT implementation for buffer sizes of N=2**K. */
template <class Scalar>
class FFTPlan
{
public:
/** The super-scalar of the input type. */
typedef typename Traits<Scalar >::SScalar SScalar;
public:
/** Constructs a FFT plan for the input and output buffers. */
FFTPlan(const Buffer< std::complex<Scalar> > &in, const Buffer< std::complex<Scalar> > &out,
FFT::Direction dir)
: _N(in.size()), _in(in), _out(out)
{
// Check dimensions
if (_in.size() != _out.size()) {
ConfigError err;
err << "Can not construct FFT plan: input & output buffers are of different size!";
throw err;
}
if (_in.isEmpty() || _out.isEmpty()) {
ConfigError err;
err << "Can not construct FFT plan: input or output buffer is empty!";
throw err;
}
// Check if N is power of two
if (_N != std::pow(2.0, std::log2(_N))) {
err << "Can not construct FFT plan: input and output buffer length must be a power of 2!";
throw err;
}
// Assemble lookuptable
int dir_fac = (dir==FFT::FORWARD) ? 1 : -1;
for (size_t i=0; i<_N; i++) {
_lut[i] = (std::exp(std::complex<double>(0,(-2*M_PI*i*dir_fac)/N)) * (1<<Traits<Scalar>::shift));
}
}
/** Destructor. */
virtual ~FFTPlan() {
_lut.unref();
}
/** Performs the FFT. */
void operator() () {
_calc(reinterpret_cast< std::complex<Scalar> >(_in.data()),
reinterpret_cast< std::complex<Scalar> >(_out.data()), _N, 1);
}
protected:
/** Actual FFT implmenetation. */
void _calc(std::complex<Scalar> *a, std::complex<Scalar> *b, size_t N, size_t stride) {
// Recursion exit
if (1 == N) { b[0] = a[0]; return; }
// Calc FFT recursive
_calc(a, b, N/2, 2*stride);
_calc(a+stride, b+N/2, N/2, 2*stride);
// Radix-2...
for (size_t i=0; i<N/2; i++) {
std::complex<SScalar> tmp = b[i];
// I hope that the compiler will turn the (x/(1<<L)) into a (x >> L) if x is an integer...
b[i] = tmp + (_lut[i]*std::complex<SScalar>(b[i+N/2])) / (1<<Traits<Scalar>::shift);
b[i+N/2] = tmp - (_lut[i]*std::complex<SScalar>(b[i+N/2])) / (1<<Traits<Scalar>::shift);
}
}
protected:
/** FFT size, needs to be a power of 2. */
size_t _N;
/** The input buffer. */
Buffer< std::complex<Scalar> > _in;
/** The output buffer. */
Buffer< std::complex<Scalar> > _out;
/** The exp(-i 2 pi k / N) look-up table. */
Buffer< std::complex<SScalar> > _lut;
};
}
#endif // __SDR_FFTPLAN_NATIVE_HH__

@ -0,0 +1,283 @@
#ifndef __SDR_FILTERNODE_HH__
#define __SDR_FILTERNODE_HH__
#include <limits>
#include <list>
#include "config.hh"
#include "node.hh"
#include "buffer.hh"
#include "buffernode.hh"
#include "fftplan.hh"
namespace sdr {
/** Evaluates the shifted filter function. */
template <class Scalar>
inline std::complex<Scalar> sinc_flt_kernel(int i, int N, double Fc, double bw, double Fs) {
std::complex<Scalar> v;
// Eval sinc
if ( (N/2) == i) { v = M_PI*(bw/Fs); }
else { v = std::sin(M_PI*(bw/Fs)*(i-N/2))/(i-N/2); }
// shift frequency
v *= std::exp(std::complex<Scalar>(0.0, (2*M_PI*Fc*i)/Fs));
// Apply window
v *= (0.42 - 0.5*cos((2*M_PI*i)/N) + 0.08*cos((4*M_PI*i)/N));
return v;
}
/** Performs the FFT forward transform. */
template <class Scalar>
class FilterSink: public Sink< std::complex<Scalar> >, public Source
{
public:
/** Constructor, best performance with block-size being a power of 2. */
FilterSink(size_t block_size)
: Sink< std::complex<Scalar> >(), Source(), _block_size(block_size),
_in_buffer(2*block_size), _out_buffer(2*block_size),
_plan(_in_buffer, _out_buffer, FFT::FORWARD)
{
// Fill second half of input buffer with 0s
for(size_t i=0; i<2*_block_size; i++) {
_in_buffer[i] = 0;
}
}
/** Destructor. */
virtual ~FilterSink() { }
/** Configures the node. */
virtual void config(const Config &src_cfg) {
// Skip if config is incomplete
if ((Config::Type_UNDEFINED==src_cfg.type()) || (0 == src_cfg.sampleRate()) || (0 == src_cfg.bufferSize())) {
return;
}
// Now check type (must be real double)
if (Config::typeId< std::complex<Scalar> >() != src_cfg.type()) {
ConfigError err;
err << "Can not configure filter-sink: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId< std::complex<Scalar> >();
throw err;
}
// Check buffer size
if (_block_size != src_cfg.bufferSize()) {
ConfigError err;
err << "Can not configure filter-sink: Invalid buffer size " << src_cfg.bufferSize()
<< ", expected " << _block_size;
throw err;
}
// Propagate configuration
setConfig(Config(Config::typeId< std::complex<Scalar> >(), src_cfg.sampleRate(),
src_cfg.bufferSize(), src_cfg.numBuffers()));
}
/** Performs the FFT forward transform. */
virtual void process(const Buffer< std::complex<Scalar> > &buffer, bool allow_overwrite) {
// Copy buffer into 1st half of input buffer
for (size_t i=0; i<_block_size; i++) { _in_buffer[i] = buffer[i]; }
// perform fft
_plan();
// send fft result
this->send(_out_buffer);
}
protected:
/** The block size. */
size_t _block_size;
/** The input buffer. */
Buffer< std::complex<Scalar> > _in_buffer;
/** The output buffer (transformed). */
Buffer< std::complex<Scalar> > _out_buffer;
/** The plan for the FFT forward transform. */
FFTPlan<Scalar> _plan;
};
/** Performs the overlap-add FFT filtering and back-transform. */
template <class Scalar>
class FilterSource: public Sink< std::complex<Scalar> >, public Source
{
public:
/** Constructor. */
FilterSource(size_t block_size, double fmin, double fmax)
: Sink< std::complex<Scalar> >(), Source(),
_block_size(block_size), _sample_rate(0),
_in_buffer(2u*_block_size), _trafo_buffer(2u*_block_size), _last_trafo(_block_size),
_kern(2u*_block_size), _buffers(1, _block_size), _fmin(fmin), _fmax(fmax),
_plan(_in_buffer, _trafo_buffer, FFT::BACKWARD)
{
// Set input & output buffer to 0
for (size_t i=0; i<_block_size; i++) {
_last_trafo[i] = 0;
}
}
/** Destructor. */
virtual ~FilterSource() {
// pas...
}
/** Set the frequency range. */
void setFreq(double fmin, double fmax) {
_fmin = fmin; _fmax = fmax; _updateFilter();
}
/** Configures the filter node. */
virtual void config(const Config &src_cfg) {
// Check config
if ((0 == src_cfg.sampleRate()) || (0 == src_cfg.bufferSize())) { return; }
if (_block_size != src_cfg.bufferSize()) {
ConfigError err;
err << "Can not configure FilterSource, block-size (=" << _block_size
<< ") != buffer-size (=" << src_cfg.bufferSize() << ")!";
throw err;
}
// calc filter kernel
_sample_rate = src_cfg.sampleRate();
_updateFilter();
// Resize buffer-set
_buffers.resize(src_cfg.numBuffers());
LogMessage msg(LOG_DEBUG);
double fmin = std::max(_fmin, -src_cfg.sampleRate()/2);
double fmax = std::min(_fmax, src_cfg.sampleRate()/2);
msg << "Configured FFT Filter: " << std::endl
<< " range: [" << fmin << ", " << fmax << "]" << std::endl
<< " fft size: " << 2*_block_size << std::endl
<< " Fc/BW: " << fmin+(fmax-fmin)/2 << " / " << (fmax-fmin) << std::endl
<< " sample rate: " << src_cfg.sampleRate();
Logger::get().log(msg);
// Propergate config
Source::setConfig(Config(Config::typeId< std::complex<Scalar> >(), src_cfg.sampleRate(),
_block_size, src_cfg.numBuffers()));
}
/** Performs the FFT filtering and back-transform. */
virtual void process(const Buffer<std::complex<Scalar> > &buffer, bool allow_overwrite) {
// Multiply with F(_kern)
for (size_t i=0; i<(2*_block_size); i++) {
_in_buffer[i] = buffer[i]*_kern[i];
}
// perform FFT
_plan();
// Get a output buffer
Buffer< std::complex<Scalar> > out = _buffers.getBuffer();
// Add first half of trafo buffer to second half of last trafo
// and store second half of the current trafo
for (size_t i=0; i<_block_size; i++) {
out[i] = _last_trafo[i] + (_trafo_buffer[i]/((Scalar)(2*_block_size)));
_last_trafo[i] = (_trafo_buffer[i+_block_size]/((Scalar)(2*_block_size)));
}
// Send output
this->send(out);
}
protected:
/** Updates the sink-filter. */
void _updateFilter() {
// Calc filter kernel
double Fs = _sample_rate;
double fmin = std::max(_fmin, -Fs/2);
double fmax = std::min(_fmax, Fs/2);
double bw = fmax-fmin;
double Fc = fmin + bw/2;
for (size_t i=0; i<_block_size; i++) {
// Eval kernel
_kern[i] = sinc_flt_kernel<Scalar>(i, _block_size, Fc, bw, Fs);
// set second half to 0
_kern[i+_block_size] = 0;
}
// Calculate FFT in-place
FFT::exec(_kern, FFT::FORWARD);
// Normalize filter kernel
_kern /= _kern.norm2();
}
protected:
/** Holds the block size of the filter. */
size_t _block_size;
/** The current sample-rate. */
double _sample_rate;
/** An input buffer. */
Buffer< std::complex<Scalar> > _in_buffer;
/** A compute buffer. */
Buffer< std::complex<Scalar> > _trafo_buffer;
/** Holds a copy of the second-half of the last output signal. */
Buffer< std::complex<Scalar> > _last_trafo;
/** Holds the current filter kernel. */
Buffer< std::complex<Scalar> > _kern;
/** The output buffers. */
BufferSet< std::complex<Scalar> >_buffers;
/** The lower frequency range. */
double _fmin;
/** The upper frequency range. */
double _fmax;
/** The FFT plan for the FFT back-transform. */
FFTPlan<Scalar> _plan;
};
/** A FFT filter bank node wich consists of several filters. */
template <class Scalar>
class FilterNode
{
public:
/** Constructor. */
FilterNode(size_t block_size=1024)
: _block_size(block_size), _buffer(0), _fft_fwd(0), _filters()
{
// Create input buffer node
_buffer = new BufferNode< std::complex<Scalar> >(block_size);
// Create FFT fwd transform
_fft_fwd = new FilterSink<Scalar>(block_size);
// Connect buffer source to FFT sink directly
_buffer->connect(_fft_fwd, true);
}
/** Destructor. */
virtual ~FilterNode() {
delete _buffer; delete _fft_fwd;
typename std::list< FilterSource<Scalar> *>::iterator item=_filters.begin();
for (; item!=_filters.end(); item++) {
delete *item;
}
}
/** The filter sink. */
Sink< std::complex<Scalar> > *sink() const {
return _buffer;
}
/** Adds a filter to the filter bank. */
FilterSource<Scalar> *addFilter(double fmin, double fmax) {
// Check if fmin < fmax
if (fmax < fmin) { std::swap(fmin, fmax); }
// Create and store filter instance
_filters.push_back(new FilterSource<Scalar>(_block_size, fmin, fmax));
// Connect fft_fwd trafo to filter directly
_fft_fwd->connect(_filters.back(), true);
return _filters.back();
}
protected:
/** The block size of the filters. */
size_t _block_size;
/** The current sample rate. */
double _sample_rate;
/** The input buffer (to ensure buffers of _block_size size. */
BufferNode< std::complex<Scalar> > *_buffer;
/** The filter sink (forward FFT). */
FilterSink<Scalar> *_fft_fwd;
/** The filter bank. */
std::list<FilterSource<Scalar> *> _filters;
};
}
#endif // __SDR_FILTERNODE_HH__

@ -0,0 +1 @@
#include "firfilter.hh"

@ -0,0 +1,331 @@
#ifndef __SDR_FIRFILTER_HH__
#define __SDR_FIRFILTER_HH__
#include "config.hh"
#include "node.hh"
#include "logger.hh"
namespace sdr {
/** Implements the calculation of the filter coefficients for the use in the @c FIRFilter
* template class. */
class FIRLowPassCoeffs
{
public:
/** Calculates the filter coefficients. */
static inline void coeffs(std::vector<double> &alpha, double Fl, double Fu, double Fs)
{
size_t N = alpha.size();
double w = 2*M_PI*Fu/Fs;
double M = double(N)/2;
double norm = 0;
for (size_t i=0; i<N; i++) {
if (N == 2*i) { alpha[i] = 4*w/M_PI; }
else { alpha[i] = std::sin(w*(i-M))/(w*(i-M)); }
// apply window function
alpha[i] *= (0.42 - 0.5*cos((2*M_PI*i)/N) + 0.08*cos((4*M_PI*i)/N));
// Calc norm
norm += std::abs(alpha[i]);
}
// Normalize filter coeffs:
for (size_t i=0; i<N; i++) { alpha[i] /= norm; }
}
};
/** Implements the calculation of the filter coefficients for the use in the @c FIRFilter
* template class. */
class FIRHighPassCoeffs
{
public:
/** Calculates the filter coefficients. */
static inline void coeffs(std::vector<double> &alpha, double Fl, double Fu, double Fs)
{
size_t N = alpha.size();
double w = 2*M_PI*Fl/Fs;
double M = double(N)/2;
double norm = 0;
for (size_t i=0; i<N; i++) {
if (N == 2*i) { alpha[i] = -std::sin(w*(i-M))/(w*(i-N)); }
else { alpha[i] = 1-4*w/M_PI; }
// apply window function
alpha[i] *= (0.42 - 0.5*cos((2*M_PI*i)/N) + 0.08*cos((4*M_PI*i)/N));
norm += std::abs(alpha[i]);
}
// Normalize filter coeffs:
for (size_t i=0; i<N; i++) { alpha[i] /= norm; }
}
};
/** Implements the calculation of the filter coefficients for the use in the @c FIRFilter
* template class. */
class FIRBandPassCoeffs
{
public:
/** Calculates the filter coefficients. */
static inline void coeffs(std::vector<double> &alpha, double Fl, double Fu, double Fs)
{
size_t N = alpha.size();
double wl = 2*M_PI*Fl/Fs;
double wu = 2*M_PI*Fu/Fs;
double M = double(N)/2;
double norm = 0;
for (size_t i=0; i<N; i++) {
if (N == 2*i) { alpha[i] = 4*(wl-wu)/M_PI; }
else { alpha[i] = std::sin(wl*(i-M)/(wl*(i-M))) - std::sin(wu*(i-M)/(wu*(i-M))); }
// apply window function
alpha[i] *= (0.42 - 0.5*cos((2*M_PI*i)/N) + 0.08*cos((4*M_PI*i)/N));
norm += std::abs(alpha[i]);
}
// Normalize filter coeffs:
for (size_t i=0; i<N; i++) { alpha[i] /= norm; }
}
};
/** Implements the calculation of the filter coefficients for the use in the @c FIRFilter
* template class. */
class FIRBandStopCoeffs
{
public:
/** Calculates the filter coefficients. */
static inline void coeffs(std::vector<double> &alpha, double Fl, double Fu, double Fs)
{
size_t N = alpha.size();
double wl = 2*M_PI*Fl/Fs;
double wu = 2*M_PI*Fl/Fs;
double M = double(N)/2;
double norm = 0;
for (size_t i=0; i<N; i++) {
if (N == 2*i) { alpha[i] = 1-4*(wl-wu)/M_PI; }
else { alpha[i] = std::sin(wu*(i-M)/(wu*(i-M))) - std::sin(wl*(i-M)/(wl*(i-M))); }
// apply window function
alpha[i] *= (0.42 - 0.5*cos((2*M_PI*i)/N) + 0.08*cos((4*M_PI*i)/N));
norm += std::abs(alpha[i]);
}
// Normalize filter coeffs:
for (size_t i=0; i<N; i++) { alpha[i] /= norm; }
}
};
/** Generic FIR filter class. Use one of the specializations below for a low-, high- or band-pass
* filter. */
template <class Scalar, class FilterCoeffs>
class FIRFilter: public Sink<Scalar>, public Source
{
public:
/** Constructor. */
FIRFilter(size_t order, double Fl, double Fu)
: Sink<Scalar>(), Source(), _enabled(true), _order(std::max(size_t(1), order)), _Fl(Fl), _Fu(Fu),
_Fs(0), _alpha(_order, 0), _ring(_order), _ring_offset(0)
{
// pass...
}
/** Destructor. */
virtual ~FIRFilter() {
_ring.unref();
_buffer.unref();
}
/** Returns true if the filter is enabled. */
inline bool enabled() const { return _enabled; }
/** Enable/Disable the filter. */
inline void enable(bool enable) { _enabled = enable; }
/** Returns the order of the filter. */
inline size_t order() const { return _order; }
/** Sets the order of the filter and updates the filter coefficients. */
virtual void setOrder(size_t order) {
// Ensure order is larger 0:
order = std::max(size_t(1), order);
if (order == _order) { return; }
_order = order;
// Resize past and coeffs
_alpha.resize(_order);
_ring = Buffer<Scalar>(_order);
_ring_offset = 0;
// Update coeffs:
FilterCoeffs::coeffs(_alpha, _Fl, _Fu, _Fs);
}
/** Returns the lower edge frequency. */
inline double lowerFreq() const { return _Fl; }
/** Sets the lower edge frequency. */
inline void setLowerFreq(double Fl) {
_Fl = Fl;
FilterCoeffs::coeffs(_alpha, _Fl, _Fu, _Fs);
}
/** Returns the upper edge frequency. */
inline double uppertFreq() const { return _Fl; }
/** Sets the upper edge frequency. */
inline void setUpperFreq(double Fu) {
_Fu = Fu;
FilterCoeffs::coeffs(_alpha, _Fl, _Fu, _Fs);
}
/** Configures the filter. */
virtual void config(const Config &src_cfg) {
// Requires type, sample-rate and buffer-size
if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.hasBufferSize()) { return; }
// check type
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure FIRLowPass: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Calc coeff
_Fs = src_cfg.sampleRate();
FilterCoeffs::coeffs(_alpha, _Fl, _Fu, _Fs);
// unref buffer if non-empty
if (!_buffer.isEmpty()) { _buffer.unref(); }
// allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
// Clear ring buffer
for (size_t i=0; i<_order; i++) { _ring[i] = 0; }
_ring_offset = 0;
LogMessage msg(LOG_DEBUG);
msg << "Configured FIRFilter:" << std::endl
<< " type " << src_cfg.type() << std::endl
<< " sample rate " << _Fs << std::endl
<< " buffer size " << src_cfg.bufferSize() << std::endl
<< " order " << _order;
Logger::get().log(msg);
// Propergate config
this->setConfig(Config(src_cfg.type(), src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
}
/** Performs the filtering. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
// If emtpy
if (0 == buffer.size()) { /* drop buffer */ }
// If disabled -> pass
if (!_enabled) { this->send(buffer, allow_overwrite); return; }
// Perform filtering in-place or out-of-place filtering
if (allow_overwrite) { _process(buffer, buffer); }
else if (_buffer.isUnused()) { _process(buffer, _buffer); }
else {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "FIR Filter: Drop buffer, output buffer still in use.";
Logger::get().log(msg);
#endif
}
}
protected:
/** performs the actual computation */
inline void _process(const Buffer<Scalar> &in, const Buffer<Scalar> &out)
{
for (size_t i=0; i<in.size(); i++) {
// Store input value into ring buffer
_ring[_ring_offset] = in[i]; _ring_offset++;
if (_ring_offset == _order) { _ring_offset=0; }
out[i] = 0;
// Apply filter on ring buffer
size_t idx = _ring_offset;
for (size_t j=0; j<_order; j++, idx++) {
if (idx == _order) { idx = 0; }
out[i] += _alpha[j]*_ring[idx];
}
}
// Done.
this->send(out.head(in.size()), true);
}
protected:
/** If true, the filtering is enabled. */
bool _enabled;
/** The order of the filter. */
size_t _order;
/** The lower edge frequency. */
double _Fl;
/** The upper edge frequency. */
double _Fu;
/** Current sample rate. */
double _Fs;
/** The current filter coefficients. */
std::vector<double> _alpha;
/** A "ring-buffer" used to perform the filtering. */
Buffer<Scalar> _ring;
/** Offset of the "ring-buffer". */
size_t _ring_offset;
/** The output buffer, unused if filtering is performed in-place. */
Buffer<Scalar> _buffer;
};
/** Low-pass FIR filter specialization. */
template <class Scalar>
class FIRLowPass: public FIRFilter<Scalar, FIRLowPassCoeffs>
{
public:
/** Constructor. */
FIRLowPass(size_t order, double Fc)
: FIRFilter<Scalar, FIRLowPassCoeffs>(order, 0, Fc) { }
/** Destructor. */
virtual ~FIRLowPass() { }
/** Returns the filter frequency. */
inline double freq() const { return FIRFilter<Scalar, FIRLowPassCoeffs>::_Fu; }
/** Sets the filter frequency. */
inline void setFreq(double freq) { FIRFilter<Scalar, FIRLowPassCoeffs>::setUpperFreq(freq); }
};
/** High-pass FIR filter specialization. */
template <class Scalar>
class FIRHighPass: public FIRFilter<Scalar, FIRHighPassCoeffs>
{
public:
/** Constructor. */
FIRHighPass(size_t order, double Fc)
: FIRFilter<Scalar, FIRHighPassCoeffs>(order, Fc, 0) { }
/** Destructor. */
virtual ~FIRHighPass() { }
/** Returns the filter frequency. */
inline double freq() const { return FIRFilter<Scalar, FIRLowPassCoeffs>::_Fl; }
/** Sets the filter frequency. */
inline void setFreq(double freq) { FIRFilter<Scalar, FIRLowPassCoeffs>::setLowerFreq(freq); }
};
/** Band-pass FIR filter specialization. */
template <class Scalar>
class FIRBandPass: public FIRFilter<Scalar, FIRBandPassCoeffs>
{
public:
/** Constructor. */
FIRBandPass(size_t order, double Fl, double Fu)
: FIRFilter<Scalar, FIRBandPassCoeffs>(order, Fl, Fu) { }
/** Destructor. */
virtual ~FIRBandPass() { }
};
/** Band-stop FIR filter specialization. */
template <class Scalar>
class FIRBandStop: public FIRFilter<Scalar, FIRBandStopCoeffs>
{
public:
/** Constructor. */
FIRBandStop(size_t order, double Fl, double Fu)
: FIRFilter<Scalar, FIRBandStopCoeffs>(order, Fl, Fu) { }
/** Destructor. */
virtual ~FIRBandStop() { }
};
}
#endif // FIRFILTER_HH

@ -0,0 +1,2 @@
#include "freqshift.hh"

@ -0,0 +1,104 @@
#ifndef __SDR_FREQSHIFT_HH__
#define __SDR_FREQSHIFT_HH__
#include "config.hh"
#include "traits.hh"
#include "node.hh"
namespace sdr {
/** A performant implementation of a frequency shift operation on integer signals. */
template <class Scalar>
class FreqShiftBase
{
public:
/** The complex input signal. */
typedef std::complex<Scalar> CScalar;
/** The compute (super) scalar of the input type. */
typedef typename Traits<Scalar>::SScalar SScalar;
/** The complex compute (super) scalar of the input type. */
typedef std::complex<SScalar> CSScalar;
public:
/** Constructor. */
FreqShiftBase(double F, double Fs)
: _freq_shift(F), _Fs(Fs), _lut_inc(0), _lut_count(0), _lut(_lut_size)
{
// Allocate and assemble LUT
// Allocate LUT for (neg) frequency shift
_lut = Buffer<CSScalar>(_lut_size);
for (size_t i=0; i<_lut_size; i++) {
_lut[i] = double(1 << Traits<Scalar>::shift) *
std::exp(std::complex<double>(0,-(2*M_PI*i)/_lut_size));
}
}
/** Destructor. */
virtual ~FreqShiftBase() {
_lut.unref();
}
/** Returns the sample rate. */
inline double sampleRate() const { return _Fs; }
/** Sets the sample rate and updates the LUT. */
virtual void setSampleRate(double Fs) {
_Fs = Fs; _update_lut_incr();
}
/** Returns the frequency shift. */
inline double frequencyShift() const { return _freq_shift; }
/** Sets the frequency shift and updates the LUT. */
virtual void setFrequencyShift(double F) {
_freq_shift = F; _update_lut_incr();
}
/** Performs the frequency shift on a single sample. */
inline CSScalar applyFrequencyShift(CSScalar value)
{
// If frequency shift is actually 0 -> return
if (0 == _lut_inc) { return value; }
// Get index, idx = (_lut_count/256)
size_t idx = (_lut_count>>8);
if (0 > _freq_shift) { idx = _lut_size - idx - 1; }
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); }
return value;
}
protected:
/** Updates the multiplier LUT. */
void _update_lut_incr() {
// 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
// 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;
_lut_count = 0;
}
protected:
/** The current frequency shift. */
double _freq_shift;
/** The current sample rate. */
double _Fs;
/** The LUT increment. */
size_t _lut_inc;
/** The LUT index counter. */
size_t _lut_count;
/** The LUT. */
Buffer<CSScalar> _lut;
protected:
/** The size of the LUT. */
static const size_t _lut_size = 127;
};
}
#endif // FREQSHIFT_HH

@ -0,0 +1,6 @@
#ifndef __SDR_GUI_GUI_HH__
#define __SDR_GUI_GUI_HH__
#include "spectrumview.hh"
#endif

@ -0,0 +1,237 @@
#include "spectrum.hh"
#include "config.hh"
using namespace sdr;
using namespace sdr::gui;
Spectrum::Spectrum(double rrate, size_t fftsize, size_t max_avg, QObject *parent) :
QObject(parent), _rrate(rrate), _fft_size(fftsize), _fft_buffer(fftsize),
_compute(fftsize), _spectrum(fftsize), _sample_count(0), _N_samples(0),
_trafo_count(0), _Ntrafo(max_avg),
_samples_left(0), _input_type(Config::Type_UNDEFINED), _sample_rate(0)
{
// Construct FFT plan
_plan = fftw_plan_dft_1d(
fftsize, (fftw_complex *)_fft_buffer.ptr(), (fftw_complex *)_fft_buffer.ptr(),
FFTW_FORWARD, FFTW_ESTIMATE);
// Set spectrum to 0:
for (size_t i=0; i<_fft_size; i++) { _spectrum[i] = 0; _compute[i] = 0; }
}
Spectrum::~Spectrum() {
fftw_destroy_plan(_plan);
}
void
Spectrum::config(const Config &src_cfg) {
// Requires at least sample rate and type
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Store sample rate
_sample_rate = src_cfg.sampleRate();
_samples_left = 0;
_trafo_count = 0; _sample_count=0;
_input_type = src_cfg.type();
// Compute number of averages to compute to meet rrate approximately
_N_samples = _sample_rate/(_Ntrafo*_rrate);
LogMessage msg(LOG_DEBUG);
msg << "Configured SpectrumView: " << std::endl
<< " Data type: " << _input_type << std::endl
<< " sample-rate: " << _sample_rate << std::endl
<< " FFT size: " << _fft_size << std::endl
<< " # sample drops: " << _N_samples << std::endl
<< " # averages: " << _Ntrafo << std::endl
<< " refresh rate: " << _sample_rate/(_N_samples*_Ntrafo) << "Hz";
Logger::get().log(msg);
}
bool
Spectrum::isInputReal() const {
switch (_input_type) {
case Config::Type_u8:
case Config::Type_s8:
case Config::Type_u16:
case Config::Type_s16:
case Config::Type_f32:
case Config::Type_f64:
return true;
default:
break;
}
return false;
}
void
Spectrum::handleBuffer(const RawBuffer &buffer, bool allow_overwrite)
{
double scale=1, offset=0;
switch (_input_type) {
case Config::Type_u8: scale = 1<<8; offset=-128; break;
case Config::Type_cu8: scale = 1<<8; offset=-128; break;
case Config::Type_s8: scale = 1<<8; offset=0; break;
case Config::Type_cs8: scale = 1<<8; offset=0; break;
case Config::Type_u16: scale = 1<<16; offset=-32768; break;
case Config::Type_cu16: scale = 1<<16; offset=-32768; break;
case Config::Type_s16: scale = 1<<16; offset=0; break;
case Config::Type_cs16: scale = 1<<16; offset=0; break;
default: break;
}
// Dispatch by input type:
if (Config::Type_u8 == _input_type) {
Buffer<uint8_t> input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = (double(input[i])+offset)/scale; _samples_left++;
// Once a FFT buffer is completed -> transform
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_s8 == _input_type) {
Buffer<int8_t> input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = (double(input[i])+offset)/scale; _samples_left++;
// Once a FFT buffer is completed -> transform
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_u16 == _input_type) {
Buffer<uint16_t> input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = (double(input[i])+offset)/scale; _samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_s16 == _input_type) {
Buffer<int16_t> input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = (double(input[i])+offset)/scale; _samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_f32 == _input_type) {
Buffer<float> input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = input[i]; _samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_f64 == _input_type) {
Buffer<double> input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = input[i]; _samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0;}
}
} else if (Config::Type_cu8 == _input_type) {
Buffer< std::complex<uint8_t> > input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = std::complex<double>(
double(input[i].real())+offset,
double(input[i].imag())+offset)/scale;
_samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_cs8 == _input_type) {
Buffer< std::complex<int8_t> > input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = std::complex<double>(
double(input[i].real())+offset,
double(input[i].imag())+offset)/scale;
_samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_cu16 == _input_type) {
Buffer< std::complex<uint16_t> > input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = std::complex<double>(
double(input[i].real())+offset,double(input[i].imag())+offset)/scale;
_samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_cs16 == _input_type) {
Buffer< std::complex<int16_t> > input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = std::complex<double>(
double(input[i].real())+offset,double(input[i].imag())+offset)/scale;
_samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_cf32 == _input_type) {
Buffer< std::complex<float> > input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = input[i]; _samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else if (Config::Type_cf64 == _input_type) {
Buffer< std::complex<double> > input(buffer);
for (size_t i=0; i<input.size(); i++, _sample_count++) {
// Skip until _N_samples is reached
if (_sample_count < _N_samples) { continue; }
// Copy value into buffer
_fft_buffer[_samples_left] = input[i]; _samples_left++;
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
}
} else {
RuntimeError err;
err << "SpectrumView: Can not process buffer of type " << _input_type
<< ", unsupported type.";
throw err;
}
}
void
Spectrum::_updateFFT() {
// calc fft
fftw_execute(_plan);
// Update _compute with PSD
for(size_t i=0; i<_fft_size; i++) {
_compute[i] += std::real(std::conj(_fft_buffer[i])*_fft_buffer[i])/_Ntrafo;
}
_trafo_count++;
// If number of averages reached:
if (_trafo_count == _Ntrafo) {
// copy _compute to _spectrum and reset _compute to 0
for (size_t i=0; i<_fft_size; i++) {
_spectrum[i] = _compute[i];
_compute[i] = 0;
}
// done...
_trafo_count=0;
// signal spectrum update
emit spectrumUpdated();
}
}

@ -0,0 +1,89 @@
#ifndef __SDR_GUI_SPECTRUM_HH__
#define __SDR_GUI_SPECTRUM_HH__
#include <QObject>
#include <fftw3.h>
#include "sdr.hh"
namespace sdr {
namespace gui {
/** Calculates the power spectrum of a singnal. The spectrum gets updated with a specified
* rate (if possible). */
class Spectrum : public QObject, public SinkBase
{
Q_OBJECT
public:
/** Constructs a new spectrum recorder (sink).
* @param rrate Specifies the desired refresh-rate of the spectrum.
* @param fftsize Specifies the size (bins) of the spectrum.
* @param max_avg Specifies the max. number of averages to take.
* @param parent Specifies the parent of the @c Spectrum instance. */
explicit Spectrum(double rrate=2, size_t fftsize=1024, size_t max_avg=1, QObject *parent = 0);
/** Destructor. */
virtual ~Spectrum();
/** Configures the Sink. */
virtual void config(const Config &src_cfg);
/** Receives the data stream and updates the _fft_buffer. */
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite);
/** Returns true if the input is real. */
bool isInputReal() const;
/** Retunrs the sample rate. */
inline double sampleRate() const { return _sample_rate; }
/** Returns the FFT size. */
inline size_t fftSize() const { return _fft_size; }
/** Returns the current spectrum. */
inline const Buffer<double> &spectrum() const { return _spectrum; }
signals:
/** Gets emitted once the spectrum was updated. */
void spectrumUpdated();
protected:
/** Updates the FFT in the _compute buffer. */
void _updateFFT();
protected:
/** The desired refresh rate for the spectrum plot. */
double _rrate;
/** Size of the FFT (resolution). */
size_t _fft_size;
/** Input and compute buffer for the FFT. */
Buffer< std::complex<double> > _fft_buffer;
/** Summation buffer of the spectrum. */
Buffer< double > _compute;
/** The current spectrum. */
Buffer< double > _spectrum;
/** Specifies the number of received samples since last FFT. */
size_t _sample_count;
/** Specifies the number of samples to drop before performing a FFT. */
size_t _N_samples;
/** Number of transforms in the _compute buffer. */
size_t _trafo_count;
/** Max. number of transforms. */
size_t _Ntrafo;
/** Number of samples, left in the buffer by the previous input. */
size_t _samples_left;
/** The input type. */
Config::Type _input_type;
/** The sample-rate of the input. */
double _sample_rate;
/** The FFTW plan. */
fftw_plan _plan;
};
}
}
#endif // SPECTRUM_HH

@ -0,0 +1,191 @@
#include "spectrumview.hh"
#include <QPaintEvent>
#include <QPainter>
#include <QPaintEngine>
#include <QResizeEvent>
#include <QFontMetrics>
using namespace sdr;
using namespace sdr::gui;
SpectrumView::SpectrumView(Spectrum *spectrum, QWidget *parent)
: QWidget(parent), _spectrum(spectrum), _numXTicks(11), _numYTicks(6),
_maxF(std::numeric_limits<double>::infinity()), _mindB(-60)
{
// Assemble pens
_axisPen = QPen(Qt::black);
_axisPen.setWidth(3);
_axisPen.setStyle(Qt::SolidLine);
_graphPen = QPen(Qt::blue);
_axisPen.setWidth(2);
_axisPen.setStyle(Qt::SolidLine);
// Connect to update signal
QObject::connect(_spectrum, SIGNAL(spectrumUpdated()), this, SLOT(update()));
}
SpectrumView::~SpectrumView() {
// pass...
}
void
SpectrumView::mouseReleaseEvent(QMouseEvent *evt) {
// If event is accepted -> determine frequency
if ((evt->pos().x() < _plotArea.left()) || (evt->pos().x() > _plotArea.right())) { return; }
double f=0;
if (_spectrum->isInputReal()) {
double dfdx = _spectrum->sampleRate()/(2*_plotArea.width());
f = dfdx * (evt->pos().x()-_plotArea.left());
} else {
double dfdx = _spectrum->sampleRate()/_plotArea.width();
f = -_spectrum->sampleRate()/2 + dfdx * (evt->pos().x()-_plotArea.left());
}
emit click(f);
// Forward to default impl:
QWidget::mouseReleaseEvent(evt);
// redraw
update();
}
void
SpectrumView::resizeEvent(QResizeEvent *evt) {
// First, forward to default impl.
QWidget::resizeEvent(evt);
// If resize event was accepted, recalc plot area
if (evt->isAccepted()) {
QSize ws = evt->size();
QFontMetrics fm(_axisFont);
int leftMargin = 15 + 6*fm.width("x");
int topMargin = 10;
int rightMargin = 3*fm.width("x");
int bottomMargin = 15 + 2*fm.xHeight();
_plotArea = QRect(
leftMargin, topMargin,
ws.width()-leftMargin-rightMargin,
ws.height()-bottomMargin-topMargin);
}
}
void
SpectrumView::paintEvent(QPaintEvent *evt) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.save();
// Clip region to update
painter.setClipRect(evt->rect());
// Draw background
painter.fillRect(QRect(0,0, this->width(), this->height()), QColor(Qt::white));
_drawAxis(painter);
_drawGraph(painter);
painter.restore();
}
void
SpectrumView::_drawGraph(QPainter &painter) {
// Draw spectrum
painter.save();
painter.setClipRect(_plotArea);
painter.setPen(_graphPen);
double height = _plotArea.height();
double width = _plotArea.width();
if (_spectrum->isInputReal()) {
double df = double(2*width)/_spectrum->fftSize();
double dh = double(height)/_mindB;
int xoff = _plotArea.x();
int yoff = _plotArea.y();
for (size_t i=1; i<_spectrum->fftSize()/2; i++) {
int x1 = xoff+df*(i-1), x2 = xoff+df*(i);
int y1 = yoff+dh*(10*log10(_spectrum->spectrum()[i-1])-10*log10(_spectrum->fftSize()));
int y2 = yoff+dh*(10*log10(_spectrum->spectrum()[i])-10*log10(_spectrum->fftSize()));
painter.drawLine(x1, y1, x2, y2);
}
} else {
double df = double(width)/_spectrum->fftSize();
double dh = double(height)/_mindB;
int xoff = _plotArea.x();
int yoff = _plotArea.y();
for (size_t i=1; i<_spectrum->fftSize(); i++) {
int idx1 = (_spectrum->fftSize()/2+i-1) % _spectrum->fftSize();
int idx2 = (_spectrum->fftSize()/2+i) % _spectrum->fftSize();
int x1 = xoff+df*(i-1), x2 = xoff+df*(i);
int y1 = yoff+dh*(10*log10(_spectrum->spectrum()[idx1])-10*log10(_spectrum->fftSize()));
int y2 = yoff+dh*(10*log10(_spectrum->spectrum()[idx2])-10*log10(_spectrum->fftSize()));
painter.drawLine(x1,y1, x2,y2);
}
}
painter.restore();
}
void
SpectrumView::_drawAxis(QPainter &painter) {
painter.save();
// Get some sizes
double height = _plotArea.height();
double width = _plotArea.width();
painter.setPen(_axisPen);
// Axes
painter.drawLine(_plotArea.topLeft(), _plotArea.bottomLeft());
painter.drawLine(_plotArea.bottomLeft(), _plotArea.bottomRight());
// draw y-ticks & labels
double dh = double(height)/_mindB;
double x = _plotArea.topLeft().x();
double y = _plotArea.topLeft().y();
double ddB = _mindB/(_numYTicks-1);
QFontMetrics fm(_axisFont); QRect bb; double db=0;
for (size_t i=0; i<_numYTicks; i++, db+=ddB) {
QString label = QString("%1").arg(db);
bb = fm.boundingRect(label);
y = _plotArea.topLeft().y() + db*dh;
bb.translate(x-8-bb.width(),y+fm.strikeOutPos());
painter.drawLine(x-5,y, x,y);
painter.drawText(bb, label);
}
// Draw x ticks & labels (real spectrum)
if (_spectrum->isInputReal()) {
double dx = double(width)/(_numXTicks-1);
double maxF = std::min(_maxF, _spectrum->sampleRate()/2);
double df = maxF/(_numXTicks-1);
double x = _plotArea.bottomLeft().x();
double y = _plotArea.bottomLeft().y();
double f = 0;
for (size_t i=0; i<11; i++, f +=df, x += dx) {
QString label = QString("%1").arg(f);
bb = fm.boundingRect(label);
bb.translate(x-bb.width()/2,y+8+fm.overlinePos());
painter.drawLine(x,y, x,y+5);
painter.drawText(bb, label);
}
} else {
double dx = double(width)/(_numXTicks-1);
double df = _spectrum->sampleRate()/(_numXTicks-1);
double x = _plotArea.bottomLeft().x();
double y = _plotArea.bottomLeft().y();
double f = -_spectrum->sampleRate()/2;
for (size_t i=0; i<_numXTicks; i++, f +=df, x += dx) {
QString label = QString("%1").arg(f);
bb = fm.boundingRect(label);
bb.translate(x-bb.width()/2,y+8+fm.overlinePos());
painter.drawLine(x,y, x,y+5);
painter.drawText(bb, label);
}
}
painter.restore();
}

@ -0,0 +1,74 @@
#ifndef __SDR_GUI_SPECTRUMVIEW_HH__
#define __SDR_GUI_SPECTRUMVIEW_HH__
#include <QWidget>
#include <QPen>
#include "sdr.hh"
#include "spectrum.hh"
namespace sdr {
namespace gui {
/** A simple widget to display a PSD with live update. */
class SpectrumView: public QWidget
{
Q_OBJECT
public:
/** @param rrate Specifies the (approx) refreshrate of the FFT plot. */
explicit SpectrumView(Spectrum *spectrum, QWidget *parent=0);
inline size_t numXTicks() const { return _numXTicks; }
inline void setNumXTicks(size_t N) { _numXTicks=N; update(); }
inline size_t numYTicks() const { return _numYTicks; }
inline void setNumYTicks(size_t N) { _numYTicks = N; }
inline double mindB() const { return _mindB; }
inline void setMindB(double mindB) { _mindB = mindB; }
/// Destructor.
virtual ~SpectrumView();
signals:
void click(double f);
protected:
/** Handles mouse clicks. */
virtual void mouseReleaseEvent(QMouseEvent *evt);
/** Updates _plotArea on resize events. */
virtual void resizeEvent(QResizeEvent *evt);
/** Draws the spectrum. */
virtual void paintEvent(QPaintEvent *evt);
/** Draws the spectrum graph */
void _drawGraph(QPainter &painter);
/** Draw the axes, ticks and labels. */
void _drawAxis(QPainter &painter);
protected:
/** Holds a weak reference to the spectrum recorder object. */
Spectrum *_spectrum;
/// The font being used for axis labels
QFont _axisFont;
/// The plot area
QRect _plotArea;
/// Axis pen.
QPen _axisPen;
/// The pen used to draw the graph.
QPen _graphPen;
size_t _numXTicks;
size_t _numYTicks;
double _maxF;
/** Lower bound of the PSD plot. */
double _mindB;
};
}
}
#endif // __SDR_GUI_SPECTRUMVIEW_HH__

@ -0,0 +1,11 @@
#include "waterfallview.hh"
using namespace sdr;
using namespace sdr::gui;
WaterFallView::WaterFallView(Spectrum *spectrum, QWidget *parent)
: QWidget(parent), _spectrum(spectrum)
{
}

@ -0,0 +1,27 @@
#ifndef __SDR_GUI_WATERFALLVIEW_HH__
#define __SDR_GUI_WATERFALLVIEW_HH__
#include <QWidget>
#include <QImage>
#include "spectrum.hh"
namespace sdr {
namespace gui {
class WaterFallView : public QWidget
{
Q_OBJECT
public:
explicit WaterFallView(Spectrum *spectrum, QWidget *parent = 0);
protected:
Spectrum *_spectrum;
QImage *_waterfall;
};
}
}
#endif // WATERFALLVIEW_HH

@ -0,0 +1,105 @@
#include "logger.hh"
using namespace sdr;
/* ********************************************************************************************* *
* LogMessage
* ********************************************************************************************* */
LogMessage::LogMessage(LogLevel level, const std::string &msg)
: std::stringstream(), _level(level)
{
(*this) << msg;
}
LogMessage::LogMessage(const LogMessage &other)
: std::stringstream(), _level(other._level)
{
(*this) << other.message();
}
LogMessage::~LogMessage() {
// pass...
}
LogLevel
LogMessage::level() const {
return _level;
}
/* ********************************************************************************************* *
* LogHandler
* ********************************************************************************************* */
LogHandler::LogHandler() {
// pass...
}
LogHandler::~LogHandler() {
// pass...
}
/* ********************************************************************************************* *
* StreamLogHandler
* ********************************************************************************************* */
StreamLogHandler::StreamLogHandler(std::ostream &stream, LogLevel level)
: LogHandler(), _stream(stream), _level(level)
{
// pass...
}
StreamLogHandler::~StreamLogHandler() {
// pass...
}
void
StreamLogHandler::handle(const LogMessage &msg) {
if (msg.level() < _level) { return; }
switch (msg.level()) {
case LOG_DEBUG: _stream << "DEBUG: "; break;
case LOG_INFO: _stream << "INFO: "; break;
case LOG_WARNING: _stream << "WARN: "; break;
case LOG_ERROR: _stream << "ERROR: "; break;
}
_stream << msg.message() << std::endl;
}
/* ********************************************************************************************* *
* Logger
* ********************************************************************************************* */
Logger *Logger::_instance = 0;
Logger::Logger()
: _handler()
{
// pass...
}
Logger::~Logger() {
std::list<LogHandler *>::iterator item = _handler.begin();
for (; item != _handler.end(); item++) {
delete (*item);
}
_handler.clear();
}
Logger &
Logger::get() {
if (0 == _instance) { _instance = new Logger(); }
return *_instance;
}
void
Logger::addHandler(LogHandler *handler) {
_handler.push_back(handler);
}
void
Logger::log(const LogMessage &message) {
std::list<LogHandler *>::iterator item = _handler.begin();
for (; item != _handler.end(); item++) {
(*item)->handle(message);
}
}

@ -0,0 +1,112 @@
#ifndef __SDR_LOGGER_HH__
#define __SDR_LOGGER_HH__
#include <string>
#include <sstream>
#include <list>
namespace sdr {
/** Specifies the possible log-level. */
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR
} LogLevel;
/** A log message. */
class LogMessage: public std::stringstream
{
public:
/** Constructor.
* @param level Specified the log-level of the message.
* @param msg An optional message. */
LogMessage(LogLevel level, const std::string &msg="");
/** Copy constructor. */
LogMessage(const LogMessage &other);
/** Destructor. */
virtual ~LogMessage();
/** Returns the level of the message. */
LogLevel level() const;
/** Returns the message. */
inline std::string message() const { return this->str(); }
protected:
/** The level of the message. */
LogLevel _level;
};
/** Base class of all log message handlers. */
class LogHandler
{
protected:
/** Hidden constructor. */
LogHandler();
public:
/** Destructor. */
virtual ~LogHandler();
/** Needs to be implemented by sub-classes to handle log messages. */
virtual void handle(const LogMessage &msg) = 0;
};
/** Serializes log message into the specified stream. */
class StreamLogHandler: public LogHandler
{
public:
/** Constructor.
* @param stream Specifies the stream, the messages are serialized into.
* @param level Specifies the minimum log level of the messages being serialized.
*/
StreamLogHandler(std::ostream &stream, LogLevel level);
/** Destructor. */
virtual ~StreamLogHandler();
/** Handles the message. */
virtual void handle(const LogMessage &msg);
protected:
/** The output stream. */
std::ostream &_stream;
/** The minimum log-level. */
LogLevel _level;
};
/** The logger class (singleton). */
class Logger
{
protected:
/** Hidden constructor. Use @c get to obtain an instance. */
Logger();
public:
/** Destructor. */
virtual ~Logger();
/** Returns the singleton instance of the logger. */
static Logger &get();
/** 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);
protected:
/** The singleton instance. */
static Logger *_instance;
/** All registered handlers. */
std::list<LogHandler *> _handler;
};
}
#endif // __SDR_LOGGER_HH__

@ -0,0 +1,215 @@
#include "node.hh"
using namespace sdr;
/* ********************************************************************************************* *
* Implementation of Config class
* ********************************************************************************************* */
Config::Config()
: _type(Type_UNDEFINED), _sampleRate(0), _bufferSize(0), _numBuffers(0)
{
// pass...
}
Config::Config(Type type, double sampleRate, size_t bufferSize, size_t numBuffers)
: _type(type), _sampleRate(sampleRate), _bufferSize(bufferSize), _numBuffers(numBuffers)
{
// pass...
}
Config::Config(const Config &other)
: _type(other._type), _sampleRate(other._sampleRate),
_bufferSize(other._bufferSize), _numBuffers(other._numBuffers)
{
// pass...
}
const Config &
Config::operator =(const Config &other) {
_type = other._type; _sampleRate = other._sampleRate;
_bufferSize = other._bufferSize; _numBuffers = other._numBuffers;
return *this;
}
bool
Config::operator ==(const Config &other) const {
return (other._type == _type) && (other._sampleRate == _sampleRate) &&
(other._bufferSize == _bufferSize) && (other._numBuffers == _numBuffers);
}
/* ********************************************************************************************* *
* Implementation of SinkBase
* ********************************************************************************************* */
SinkBase::SinkBase() {
// pass...
}
SinkBase::~SinkBase() {
// pass...
}
/* ********************************************************************************************* *
* Implementation of Source class
* ********************************************************************************************* */
Source::Source()
: _config(), _sinks()
{
// pass..
}
Source::~Source() {
// pass...
}
void
Source::send(const RawBuffer &buffer, bool allow_overwrite) {
std::map<SinkBase *, bool>::iterator item = _sinks.begin();
for (; item != _sinks.end(); item++) {
// If connected directly, call directly
if (item->second) {
// The source allows the sink to manipulate the buffer directly,
// iff, the sink is the only one receiving this buffer, the source allows it and the
// connection is direct.
allow_overwrite = allow_overwrite && (1 == _sinks.size());
item->first->handleBuffer(buffer, allow_overwrite);
}
// otherwise, queue buffer
else {
allow_overwrite = allow_overwrite && (1 == _sinks.size());
Queue::get().send(buffer, item->first, allow_overwrite);
}
}
}
void
Source::connect(SinkBase *sink, bool direct) {
_sinks[sink] = direct;
sink->config(_config);
}
void
Source::disconnect(SinkBase *sink) {
_sinks.erase(sink);
}
void
Source::setConfig(const Config &config) {
// If the config did not changed -> skip
if (config == _config) { return; }
// Store config
_config = config;
// Propergate config
propagateConfig(_config);
}
void
Source::propagateConfig(const Config &config) {
// propagate config to all connected sinks
std::map<SinkBase *, bool>::iterator item = _sinks.begin();
for (; item != _sinks.end(); item++) {
item->first->config(_config);
}
}
Config::Type
Source::type() const {
return _config.type();
}
double
Source::sampleRate() const {
return _config.sampleRate();
}
void
Source::signalEOS() {
std::list<DelegateInterface *>::iterator item = _eos.begin();
for (; item != _eos.end(); item++) { (**item)(); }
}
/* ********************************************************************************************* *
* Implementation of BlockingSource
* ********************************************************************************************* */
BlockingSource::BlockingSource(bool parallel, bool connect_idle, bool stop_queue_on_eos)
: Source(), _is_active(false), _is_parallel(parallel)
{
// If the source is not autonomous and shall be triggered by the idle event of the Queue
// -> connect to it.
if (!parallel && connect_idle) {
Queue::get().addIdle(this, &BlockingSource::_nonvirt_idle_cb);
}
// If the EOS signal of the source shall stop the queue
if (stop_queue_on_eos) { this->addEOS(&Queue::get(), &Queue::stop); }
}
BlockingSource::~BlockingSource() {
if (isActive()) { stop(); }
}
void
BlockingSource::start() {
if (_is_active) { return; }
if (_is_parallel) {
pthread_create(&_thread, 0, BlockingSource::_pthread_main_wrapper, this);
}
}
void
BlockingSource::stop() {
if (! _is_active) { return; }
_is_active = false;
if (_is_parallel) {
void *ret_ptr;
pthread_join(_thread, &ret_ptr);
}
}
void
BlockingSource::_parallel_main() {
while (_is_active && Queue::get().isRunning()) {
this->next();
}
}
void
BlockingSource::_nonvirt_idle_cb() {
if (_is_active && Queue::get().isRunning()) {
this->next();
}
}
void *
BlockingSource::_pthread_main_wrapper(void *ptr) {
BlockingSource *obj = reinterpret_cast<BlockingSource *>(ptr);
obj->_parallel_main();
return 0;
}
/* ********************************************************************************************* *
* Implementation of Proxy
* ********************************************************************************************* */
Proxy::Proxy()
: SinkBase(), Source()
{
// pass...
}
Proxy::~Proxy() {
// pass...
}
void
Proxy::config(const Config &src_cfg) {
this->setConfig(src_cfg);
}
void
Proxy::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) {
this->send(buffer);
}

@ -0,0 +1,315 @@
#ifndef __SDR_NODE_HH__
#define __SDR_NODE_HH__
#include <set>
#include <list>
#include <iostream>
#include <complex>
#include <stdint.h>
#include <pthread.h>
#include "buffer.hh"
#include "queue.hh"
#include "exception.hh"
namespace sdr {
/** A collection of configuration information that is send by a source to all connected sinks
* to propergate and check the configuration of the processing network. */
class Config
{
public:
/** The type IDs. */
typedef enum {
Type_UNDEFINED = 0,
Type_u8, ///< Real unsigned 8b ints.
Type_s8, ///< Real signed 8b ints.
Type_u16, ///< Real unsigned 16b ints.
Type_s16, ///< Real signed 16b ints.
Type_f32, ///< Real 32b floats aka. "float".
Type_f64, ///< Real 64b floats aka. "double".
Type_cu8, ///< Complex (aka I/Q) type of unsigned 8b ints.
Type_cs8, ///< Complex (aka I/Q) type of signed 8b ints.
Type_cu16, ///< Complex (aka I/Q) type of unsigned 16b ints.
Type_cs16, ///< Complex (aka I/Q) type of signed 16b ints.
Type_cf32, ///< Complex (aka I/Q) type of 32bit floats aka. std::complex<float>.
Type_cf64 ///< Complex (aka I/Q) type of 64bit floats aka. std::complex<double>.
} Type;
public:
/** Empty constructor, will result into an invalid configuration. */
Config();
/** Constructor. */
Config(Type type, double sampleRate, size_t bufferSize, size_t numBuffers);
/** Copy constructor. */
Config(const Config &other);
/** Assignment operator. */
const Config &operator= (const Config &other);
/** Coparison operator. */
bool operator== (const Config &other) const;
/** If true, the configuration has a type. */
inline bool hasType() const { return Type_UNDEFINED != _type; }
/** Returns the type. */
inline Type type() const { return _type; }
/** Sets the type. */
inline void setType(Type type) { _type = type; }
/** If true, the configuration has a sample rate. */
inline bool hasSampleRate() const { return 0 != _sampleRate; }
/** Returns the sample rate. */
inline double sampleRate() const { return _sampleRate; }
/** Sets the sample rate. */
inline void setSampleRate(double rate) { _sampleRate = rate; }
/** If true, the configuration has a buffer size. */
inline bool hasBufferSize() const { return 0 != _bufferSize; }
/** Returns the max. buffer size. */
inline size_t bufferSize() const { return _bufferSize; }
/** Sets the max. buffer size. */
inline void setBufferSize(size_t size) { _bufferSize = size; }
/** If true, the configuration has a number of buffers. */
inline bool hasNumBuffers() const { return 0 != _numBuffers; }
/** Returns the max. number of buffers. */
inline size_t numBuffers() const { return _numBuffers; }
/** Sets the max. number of buffers. */
inline void setNumBuffers(size_t N) { _numBuffers = N; }
/** Returns the type-id of the template type. */
template <typename T> static inline Type typeId();
protected:
/** Holds the type of the source. */
Type _type;
/** Holds the sample rate of the source. */
double _sampleRate;
/** Holds the max. buffer size of the source. */
size_t _bufferSize;
/** Holds the max. number of buffers of the source. */
size_t _numBuffers;
};
/** Type-id for @c uint8. */
template <>
inline Config::Type Config::typeId<uint8_t>() { return Type_u8; }
/** Type-id for @c int8. */
template <>
inline Config::Type Config::typeId<int8_t>() { return Type_s8; }
/** Type-id for @c uint16. */
template <>
inline Config::Type Config::typeId<uint16_t>() { return Type_u16; }
/** Type-id for @c int16. */
template <>
inline Config::Type Config::typeId<int16_t>() { return Type_s16; }
/** Type-id for @c float. */
template <>
inline Config::Type Config::typeId<float>() { return Type_f32; }
/** Type-id for @c double. */
template <>
inline Config::Type Config::typeId<double>() { return Type_f64; }
/** Type-id for @c std::complex<uint8>. */
template <>
inline Config::Type Config::typeId< std::complex<uint8_t> >() { return Type_cu8; }
/** Type-id for @c std::complex<int8>. */
template <>
inline Config::Type Config::typeId< std::complex<int8_t> >() { return Type_cs8; }
/** Type-id for @c std::complex<uint16>. */
template <>
inline Config::Type Config::typeId< std::complex<uint16_t> >() { return Type_cu16; }
/** Type-id for @c std::complex<int16>. */
template <>
inline Config::Type Config::typeId< std::complex<int16_t> >() { return Type_cs16; }
/** Type-id for @c std::complex<float>. */
template <>
inline Config::Type Config::typeId< std::complex<float> >() { return Type_cf32; }
/** Type-id for @c std::complex<double>. */
template <>
inline Config::Type Config::typeId< std::complex<double> >() { return Type_cf64; }
/** Convert type constant into type name. */
inline const char *typeName(Config::Type type) {
switch (type) {
case Config::Type_UNDEFINED: return "UNDEFINED";
case Config::Type_u8: return "uint8";
case Config::Type_s8: return "int8";
case Config::Type_u16: return "uint16";
case Config::Type_s16: return "int16";
case Config::Type_f32: return "float";
case Config::Type_f64: return "double";
case Config::Type_cu8: return "complex uint8";
case Config::Type_cs8: return "complex int8";
case Config::Type_cu16: return "complex uint16";
case Config::Type_cs16: return "complex int16";
case Config::Type_cf32: return "complex float";
case Config::Type_cf64: return "complex double";
}
return "unknown";
}
/** Printing type constants. */
inline std::ostream &operator<<(std::ostream &stream, Config::Type type) {
stream << typeName(type) << " (" << (int)type << ")";
return stream;
}
/** Basic interface of all Sinks. Usually, sinks are derived from
* the @c Sink template. */
class SinkBase
{
public:
/** Constructor. */
SinkBase();
/** Destructor. */
virtual ~SinkBase();
/** Needs to be implemented by any sub-type to process the received data. */
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite) = 0;
/** Needs to be implemented by any sub-type to check and perform the configuration of the node. */
virtual void config(const Config &src_cfg) = 0;
};
/** Typed sink. */
template <class Scalar>
class Sink: public SinkBase
{
public:
/** Constructor. */
Sink() : SinkBase() { }
/** Drestructor. */
virtual ~Sink() { }
/** Needs to be implemented by any sub-type to process the received data. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) = 0;
/** Re-implemented from @c SinkBase. Casts the buffer into the requested type
* and forwards the call to @c process. */
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite) {
this->process(Buffer<Scalar>(buffer), allow_overwrite);
}
};
/** Generic source class. */
class Source
{
public:
/** Constructor. */
Source();
/** Destructor. */
virtual ~Source();
/** Sends the given buffer to all connected sinks. */
virtual void send(const RawBuffer &buffer, bool allow_overwrite=false);
/** Connect this source to a sink. */
void connect(SinkBase *sink, bool direct=false);
/** Disconnect a sink again. */
void disconnect(SinkBase *sink);
/** Stores the configuration and propergates it if
* the configuration has been changed. */
virtual void setConfig(const Config &config);
/** Returns the configured sample rate or @c 0 otherwise. */
virtual double sampleRate() const;
/** Returns the configured source type or @c Config::Type_UNDEFINED otherwise. */
virtual Config::Type type() const;
/** Adds a callback to the end-of-stream signal of the source. */
template <class T>
void addEOS(T* instance, void (T::*function)()) {
_eos.push_back(new Delegate<T>(instance, function));
}
protected:
/** Signals the EOS. */
void signalEOS();
/** Propagates the given configuration to all connected sinks. */
void propagateConfig(const Config &config);
protected:
/** Holds the source configuration, this can be updated by calling @c setConfig. */
Config _config;
/** The connected sinks. */
std::map<SinkBase *, bool> _sinks;
/** The connected EOS singal handlers. */
std::list<DelegateInterface *> _eos;
};
/** Iterface of a blocking source. Blocking sources are usually input sources that wait for data
* from a device or file.
* Please note, a proper input source that reads data from a file should emmit the
* eos signal using the @c signalEOS method. This can be used to stop the @c Queue loop
* once the input can not provide data anymore. */
class BlockingSource: public Source {
public:
/** Constructor.
* @param parallel Specifies whether the source is waiting in a separate thread for new data.
* @param connect_idle Specifies wheter the input source @c next() method should be connected
* to the idle signal of the @c Queue.
* @param stop_queue_on_eos Signals the that the Queue should be stopped once the
* EOS is reached. Use this flag only if this source is the only source in the
* data stream. */
BlockingSource(bool parallel=false, bool connect_idle=true, bool stop_queue_on_eos=false);
/** Destructor. */
virtual ~BlockingSource();
/** This method gets called either by the @c Queue on idle events or by a thread to read more
* data from the input stream. The @c next function should be blocking in order to avoid busy
* waiting on incomming data. */
virtual void next() = 0;
/** Returns true if the source is active. */
inline bool isActive() const { return _is_active; }
/** This function starts the input stream. */
virtual void start();
/** This function stops the input stream. */
virtual void stop();
protected:
/** The parallel main loop. */
void _parallel_main();
/** The non-virtual idle callback. */
void _nonvirt_idle_cb();
protected:
/** If true, the source is active. */
bool _is_active;
/** If true, the surce is processed in parallel. */
bool _is_parallel;
/** The thread of the source. */
pthread_t _thread;
private:
/** Wrapper for the pthread library. */
static void *_pthread_main_wrapper(void *);
};
/** A NOP node. */
class Proxy: public SinkBase, public Source
{
public:
/** Constructor. */
Proxy();
/** Destructor. */
virtual ~Proxy();
/** Configures the node. */
virtual void config(const Config &src_cfg);
/** Forwards the buffer. */
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite);
};
}
#endif // __SDR_NODE_HH__

@ -0,0 +1,174 @@
#ifndef __SDR_OPERATORS_HH__
#define __SDR_OPERATORS_HH__
#include <stdint.h>
#include <complex>
// Tiny helper functions to handle std::real & std::conj on real integers
namespace std {
inline int16_t conj(int16_t value) { return value; }
inline int16_t real(int16_t value) { return value; }
inline int16_t imag(int16_t value) { return 0; }
}
inline std::complex<double> operator*(const double &a, const std::complex<float> &b) {
return std::complex<double>(a*b.real(), a*b.imag());
}
inline std::complex<double> operator*(const double &a, const std::complex<uint16_t> &b) {
return std::complex<double>(a*b.real(), a*b.imag());
}
inline std::complex<double> operator*(const double &a, const std::complex<int16_t> &b) {
return std::complex<double>(a*b.real(), a*b.imag());
}
inline std::complex<double> operator*(const double &a, const std::complex<uint8_t> &b) {
return std::complex<double>(a*b.real(), a*b.imag());
}
inline std::complex<double> operator*(const double &a, const std::complex<int8_t> &b) {
return std::complex<double>(a*b.real(), a*b.imag());
}
inline std::complex<float> operator* (const std::complex<float> &a, const std::complex<int16_t> &b) {
return a*std::complex<float>(std::real(b), std::imag(b));
}
inline std::complex<double> operator* (const std::complex<double> &a, const std::complex<int16_t> &b) {
return a*std::complex<double>(std::real(b), std::imag(b));
}
inline std::complex<int32_t> operator<< (const std::complex<int32_t> &a, int b) {
return std::complex<int32_t>(std::real(a)<<b, std::imag(a)<<b);
}
inline std::complex<int32_t> operator>> (const std::complex<int32_t> &a, int b) {
return std::complex<int32_t>(std::real(a)>>b, std::imag(a)>>b);
}
namespace sdr {
template <class iScalar, class oScalar>
inline oScalar cast(const iScalar &a) { return oScalar(a); }
template<>
inline std::complex<double> cast<int8_t, std::complex<double> >(const int8_t &a) {
return std::complex<double>(std::real(a), std::imag(a));
}
template<>
inline std::complex<double> cast<std::complex<int8_t>, std::complex<double> >(const std::complex<int8_t> &a) {
return std::complex<double>(std::real(a), std::imag(a));
}
template<>
inline std::complex<double> cast<int16_t, std::complex<double> >(const int16_t &a) {
return std::complex<double>(std::real(a), std::imag(a));
}
template<>
inline std::complex<double> cast<std::complex<int16_t>, std::complex<double> >(const std::complex<int16_t> &a) {
return std::complex<double>(std::real(a), std::imag(a));
}
/** Mulitplication by a power of two. */
inline uint8_t mul2(uint8_t a, int n) {
if (n < 0) { return a >> -n; }
else { return a << n; }
}
/** Division by a power of two. */
inline uint8_t div2(uint8_t a, int n) {
if (n < 0) { return a << -n; }
else { return a >> n; }
}
/** Mulitplication by a power of two. */
inline std::complex<uint8_t> mul2(const std::complex<uint8_t> &a, int n) {
if (n < 0) {
return std::complex<uint8_t>(std::real(a)>>(-n), std::imag(a)>>(-n));
} else {
return std::complex<uint8_t>(std::real(a)<<(n), std::imag(a)<<(n));
}
}
/** Division by a power of two. */
inline std::complex<uint8_t> div2(const std::complex<uint8_t> &a, int n) {
if (n < 0) {
return std::complex<uint8_t>(std::real(a)<<(-n), std::imag(a)<<(-n));
} else {
return std::complex<uint8_t>(std::real(a)>>(n), std::imag(a)>>(n));
}
}
/** Mulitplication by a power of two. */
inline int8_t mul2(int8_t a, int n) {
if (n < 0) { return a >> -n; }
else { return a << n; }
}
/** Mulitplication by a power of two. */
inline std::complex<int8_t> mul2(const std::complex<int8_t> &a, int n) {
if (n < 0) {
return std::complex<int8_t>(std::real(a)>>(-n), std::imag(a)>>(-n));
} else {
return std::complex<int8_t>(std::real(a)>>(-n), std::imag(a)>>(-n));
}
}
/** Mulitplication by a power of two. */
inline uint16_t mul2(uint16_t a, int n) {
if (n < 0) { return a >> -n; }
else { return a << n; }
}
/** Mulitplication by a power of two. */
inline std::complex<uint16_t> mul2(const std::complex<uint16_t> &a, int n) {
if (n < 0) {
return std::complex<uint16_t>(std::real(a)>>(-n), std::imag(a)>>(-n));
} else {
return std::complex<uint16_t>(std::real(a)>>(-n), std::imag(a)>>(-n));
}
}
/** Mulitplication by a power of two. */
inline int16_t mul2(int16_t a, int n) {
if (n < 0) { return a >> -n; }
else { return a << n; }
}
/** Mulitplication by a power of two. */
inline std::complex<int16_t> mul2(const std::complex<int16_t> &a, int n) {
if (n < 0) {
return std::complex<int16_t>(std::real(a)>>(-n), std::imag(a)>>(-n));
} else {
return std::complex<int16_t>(std::real(a)>>(-n), std::imag(a)>>(-n));
}
}
/** Mulitplication by a power of two. */
inline float mul2(float a, int n) {
if (n < 0) { return a/(1<<(-n)); }
else { return a*(1<<n); }
}
/** Mulitplication by a power of two. */
inline std::complex<float> mul2(const std::complex<float> &a, int n) {
if (n < 0) { return a/float(1<<(-n)); }
else { return a*float(1<<n); }
}
/** Mulitplication by a power of two. */
inline std::complex<double> mul2(const std::complex<double> &a, int n) {
if (n < 0) { return a/double(1<<(-n)); }
else { return a*double(1<<n); }
}
}
#endif // OPERATORS_HH

@ -0,0 +1,155 @@
#include "portaudio.hh"
using namespace sdr;
/* ******************************************************************************************* *
* PortAudio interface
* ******************************************************************************************* */
void
PortAudio::init() {
Pa_Initialize();
}
void
PortAudio::terminate() {
Pa_Terminate();
}
int
PortAudio::numDevices() {
return Pa_GetDeviceCount();
}
int
PortAudio::defaultInputDevice() {
return Pa_GetDefaultInputDevice();
}
int
PortAudio::defaultOutputDevice() {
return Pa_GetDefaultOutputDevice();
}
bool
PortAudio::hasInputStream(int idx) {
const PaDeviceInfo *info = Pa_GetDeviceInfo(idx);
return 0 != info->maxInputChannels;
}
bool
PortAudio::hasOutputStream(int idx) {
const PaDeviceInfo *info = Pa_GetDeviceInfo(idx);
return 0 != info->maxOutputChannels;
}
std::string
PortAudio::deviceName(int idx) {
const PaDeviceInfo *info = Pa_GetDeviceInfo(idx);
return info->name;
}
/* ******************************************************************************************* *
* PortSink implementation
* ******************************************************************************************* */
PortSink::PortSink()
: SinkBase(), _stream(0), _frame_size(0)
{
// pass...
}
PortSink::~PortSink() {
if (0 != _stream) {
Pa_CloseStream(_stream);
}
}
void
PortSink::config(const Config &src_cfg) {
// Skip if config is incomplete
if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.bufferSize()) {
return;
}
// Check type:
PaSampleFormat fmt;
size_t nChanels;
switch (src_cfg.type()) {
case Config::Type_u8:
case Config::Type_s8:
fmt = paInt8;
nChanels = 1;
_frame_size = nChanels*1;
break;
case Config::Type_cu8:
case Config::Type_cs8:
fmt = paInt8;
nChanels = 2;
_frame_size = nChanels*1;
break;
case Config::Type_u16:
case Config::Type_s16:
fmt = paInt16;
nChanels = 1;
_frame_size = nChanels*2;
break;
case Config::Type_cu16:
case Config::Type_cs16:
fmt = paInt16;
nChanels = 2;
_frame_size = nChanels*2;
break;
case Config::Type_f32:
fmt = paFloat32;
nChanels = 1;
_frame_size = nChanels*4;
break;
case Config::Type_cf32:
fmt = paFloat32;
nChanels = 2;
_frame_size = nChanels*4;
break;
default:
ConfigError err;
err << "Can not configure PortAudio sink: Unsupported format " << src_cfg.type()
<< " must be one of " << Config::Type_u8 << ", " << Config::Type_s8 << ", "
<< Config::Type_cu8 << ", " << Config::Type_cs8 << ", " << Config::Type_u16 << ", "
<< Config::Type_s16 << ", " << Config::Type_cu16 << ", " << Config::Type_cs16 << ", "
<< Config::Type_f32 << " or " << Config::Type_cf32;
throw err;
}
/* Configure PortAudio stream */
// Close stream if already open
if (0 != _stream) { Pa_StopStream(_stream); Pa_CloseStream(_stream); }
PaError err;
// One output chanel (mono), format == float, samplerate and buffer size as provided by src_cfg
if ((err = Pa_OpenDefaultStream(&_stream, 0, nChanels, fmt,
(unsigned int)src_cfg.sampleRate(), src_cfg.bufferSize(), 0, 0)))
{
ConfigError ex;
ex << "Can not configure PortAudio sink: "
<< ((const char *)Pa_GetErrorText(err));
throw ex;
}
LogMessage msg(LOG_DEBUG);
msg << "Configure PortAudio sink: " << std::endl
<< " sample rate " << (int)src_cfg.sampleRate() << std::endl
<< " buffer size " << src_cfg.bufferSize() << std::endl
<< " format " << src_cfg.type() << std::endl
<< " # chanels " << nChanels;
Logger::get().log(msg);
// start
Pa_StartStream(_stream);
}
void
PortSink::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) {
// Bug, check if data was send properly
Pa_WriteStream(_stream, buffer.data(), buffer.bytesLen()/_frame_size);
}

@ -0,0 +1,202 @@
#ifndef __SDR_PORTAUDIO_HH__
#define __SDR_PORTAUDIO_HH__
#include <portaudio.h>
#include "buffer.hh"
#include "node.hh"
#include "config.hh"
#include "logger.hh"
namespace sdr {
/** "Namespace" to collect all static, PortAudio related functions. */
class PortAudio {
public:
/** Initializes the PortAudio system, must be called first. */
static void init();
/** Shutdown. */
static void terminate();
/** Returns the number of devices available. */
static int numDevices();
/** Returns the index of the default input device. */
static int defaultInputDevice();
/** Returns the index of the default output device. */
static int defaultOutputDevice();
/** Returns @c true of the given device provides an input chanel. */
static bool hasInputStream(int idx);
/** Returns @c true of the given device provides an output chanel. */
static bool hasOutputStream(int idx);
/** Returns the device name. */
static std::string deviceName(int idx);
};
/** PortAudio playback node. */
class PortSink: public SinkBase
{
public:
/** Constructor. */
PortSink();
/** Destructor. */
virtual ~PortSink();
/** Configures the PortAudio output. */
virtual void config(const Config &src_cfg);
/** Playback. */
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite);
protected:
/** The PortAudio stream. */
PaStream *_stream;
/** The frame-size. */
size_t _frame_size;
};
/** PortAudio input stream as a @c Source. */
template <class Scalar>
class PortSource: public Source
{
public:
/** Constructor. */
PortSource(double sampleRate, size_t bufferSize, int dev=-1):
Source(), _streamIsOpen(false), _stream(0), _sampleRate(sampleRate), _is_real(true)
{
// Allocate buffer
_buffer = Buffer<Scalar>(bufferSize);
_initializeStream(dev);
}
/** Destructor. */
virtual ~PortSource() { if (0 != _stream) { Pa_CloseStream(_stream); } }
/** Reads (blocking) the next buffer from the PortAudio stream. This function can be
* connected to the idle event of the @c Queue. */
void next() {
/// @todo Signal loss of samples in debug mode.
/// @bug Drop data if output buffer is in use.
Pa_ReadStream(_stream, _buffer.ptr(), _buffer.size());
this->send(_buffer);
}
/** Returns the currently selected input device. */
int deviceIndex() const { return _deviceIndex; }
/** Selects the input device, throws a @c ConfigError exception if the device can not
* be setup as an input device. Do not call this function, while the @c Queue is running. */
void setDeviceIndex(int idx=-1) {
_initializeStream(idx);
}
/** Checks if the given sample rate is supported by the device. */
bool hasSampleRate(double sampleRate) {
PaStreamParameters params;
params.device = _deviceIndex;
params.channelCount = _is_real ? 1 : 2;
params.sampleFormat = _fmt;
params.hostApiSpecificStreamInfo = 0;
return paFormatIsSupported == Pa_IsFormatSupported(&params, 0, sampleRate);
}
/** Resets the sample rate of the input device. Do not call this function, while the
* @c Queue is running. */
void setSampleRate(double sampleRate) {
_sampleRate = sampleRate;
_initializeStream(_deviceIndex);
}
protected:
/** Device setup. */
void _initializeStream(int idx=-1) {
// Stop and close stream if running
if (_streamIsOpen) {
Pa_StopStream(_stream); Pa_CloseStream(_stream); _streamIsOpen = false;
}
// Determine input stream format by scalar type
switch (Config::typeId<Scalar>()) {
case Config::Type_f32:
_fmt = paFloat32;
_is_real = true;
break;
case Config::Type_u16:
case Config::Type_s16:
_fmt = paInt16;
_is_real = true;
break;
case Config::Type_cf32:
_fmt = paFloat32;
_is_real = false;
break;
case Config::Type_cu16:
case Config::Type_cs16:
_fmt = paInt16;
_is_real = false;
break;
default:
ConfigError err;
err << "Can not configure PortAudio sink: Unsupported format " << Config::typeId<Scalar>()
<< " must be one of " << Config::Type_u16 << ", " << Config::Type_s16
<< ", " << Config::Type_f32 << ", " << Config::Type_cu16 << ", " << Config::Type_cs16
<< " or " << Config::Type_cf32 << ".";
throw err;
}
// Assemble config:
int numCh = _is_real ? 1 : 2;
_deviceIndex = idx < 0 ? Pa_GetDefaultInputDevice() : idx;
PaStreamParameters params;
params.device = _deviceIndex;
params.channelCount = numCh;
params.sampleFormat = _fmt;
params.suggestedLatency = Pa_GetDeviceInfo(_deviceIndex)->defaultHighInputLatency;
params.hostApiSpecificStreamInfo = 0;
// open stream
PaError err = Pa_OpenStream(&_stream, &params, 0, _sampleRate, _buffer.size(), 0, 0, 0);
// Check for errors
if (0 > err) {
ConfigError err;
err << "Can not open PortAudio input stream!"; throw err;
}
// Mark stream as open
_streamIsOpen = true;
// Start stream
Pa_StartStream(_stream);
LogMessage msg(LOG_DEBUG);
msg << "Configure PortAudio source: " << std::endl
<< " sample rate " << _sampleRate << std::endl
<< " buffer size " << _buffer.size() << std::endl
<< " format " << Config::typeId<Scalar>() << std::endl
<< " num chanels " << numCh;
Logger::get().log(msg);
// Set config
this->setConfig(Config(Config::typeId<Scalar>(), _sampleRate, _buffer.size(), 1));
}
protected:
/** If true, the PortAudio stream, @c _stream is open. */
bool _streamIsOpen;
/** The PortAudio input stream. */
PaStream *_stream;
/** The current format of the PortAudio stream. */
PaSampleFormat _fmt;
/** The current sample rate. */
double _sampleRate;
/** The current input device index. */
int _deviceIndex;
/** If @c true, the input stream is real (1 chanel), otherwise complex (2 chanels). */
bool _is_real;
/** The output buffer. */
Buffer<Scalar> _buffer;
};
}
#endif // __SDR_PORTAUDIO_HH__

@ -0,0 +1,158 @@
#include "queue.hh"
#include "node.hh"
#include "config.hh"
#include "logger.hh"
using namespace sdr;
/* ********************************************************************************************* *
* Implementation of Queue class
* ********************************************************************************************* */
Queue *Queue::_instance = 0;
Queue &
Queue::get() {
if (0 == Queue::_instance) {
Queue::_instance = new Queue();
}
return *Queue::_instance;
}
Queue::Queue()
: _running(false)
{
// allocate queue lock and condition
pthread_mutex_init(&_queue_lock, NULL);
pthread_cond_init(&_queue_cond, NULL);
}
Queue::~Queue() {
pthread_mutex_destroy(&_queue_lock);
pthread_cond_destroy(&_queue_cond);
}
void
Queue::send(const RawBuffer &buffer, SinkBase *sink, bool allow_overwrite) {
// Refrerence buffer
pthread_mutex_lock(&_queue_lock);
buffer.ref();
_queue.push_back(Message(buffer, sink, allow_overwrite));
pthread_cond_signal(&_queue_cond);
pthread_mutex_unlock(&_queue_lock);
}
bool
Queue::isStopped() const {
return !_running;
}
bool
Queue::isRunning() const {
return _running;
}
void
Queue::start() {
if (_running) { return; }
pthread_create(&_thread, 0, Queue::__thread_start, this);
}
void
Queue::stop() {
_running = false;
pthread_cond_signal(&_queue_cond);
}
void
Queue::wait() {
void *p;
pthread_join(_thread, &p);
}
void
Queue::_main()
{
// set state
_running = true;
Logger::get().log(LogMessage(LOG_DEBUG, "Queue started."));
// Call all start signal handlers...
_signalStart();
// As long as the queue runs or there are any buffers left to be processed
while (_running || (_queue.size() > 0)) {
// Process all messages in queue
while (_queue.size() > 0) {
// Get a Message from the queue
pthread_mutex_lock(&_queue_lock);
Message msg(_queue.front()); _queue.pop_front();
pthread_mutex_unlock(&_queue_lock);
// Process message
msg.sink()->handleBuffer(msg.buffer(), msg.allowOverwrite());
// Mark buffer unused
msg.buffer().unref();
}
// If there are no buffer in the queue and the queue is still running:
if ((0 == _queue.size()) && _running) {
// Signal idle handlers
_signalIdle();
// -> wait until a buffer gets available
pthread_mutex_lock(&_queue_lock);
while( (0 == _queue.size()) && _running ) { pthread_cond_wait(&_queue_cond, &_queue_lock); }
pthread_mutex_unlock(&_queue_lock);
}
}
// Call all stop-signal handlers
_signalStop();
Logger::get().log(LogMessage(LOG_DEBUG, "Queue stopped."));
}
void
Queue::_signalIdle() {
std::list<DelegateInterface *>::iterator item = _idle.begin();
for (; item != _idle.end(); item++) {
(**item)();
}
}
void
Queue::_signalStart() {
std::list<DelegateInterface *>::iterator item = _onStart.begin();
for (; item != _onStart.end(); item++) {
(**item)();
}
}
void
Queue::_signalStop() {
std::list<DelegateInterface *>::iterator item = _onStop.begin();
for (; item != _onStop.end(); item++) {
(**item)();
}
}
void *
Queue::__thread_start(void *ptr) {
Queue *queue = reinterpret_cast<Queue *>(ptr);
try {
queue->_main();
} catch (std::exception &err) {
LogMessage msg(LOG_ERROR);
msg << "Caught exception in thread: " << err.what()
<< " -> Stop thread.";
Logger::get().log(msg);
} catch (...) {
LogMessage msg(LOG_ERROR);
msg << "Caught (known) exception in thread -> Stop thread.";
Logger::get().log(msg);
}
queue->_running = false;
pthread_exit(0);
return 0;
}

@ -0,0 +1,176 @@
#ifndef __SDR_QUEUE_HH__
#define __SDR_QUEUE_HH__
#include <list>
#include <map>
#include "buffer.hh"
#include <pthread.h>
namespace sdr {
// Forward decl.
class SinkBase;
/** Interface of a delegate. */
class DelegateInterface {
public:
/** Call back interface. */
virtual void operator() () = 0;
};
/** Specific delegate to a method of an object . */
template <class T>
class Delegate: public DelegateInterface
{
public:
/** Constructs a delegate to the method @c func of the instance @c instance.*/
Delegate(T *instance, void (T::*func)(void)) : _instance(instance), _function(func) { }
/** Destructor. */
virtual ~Delegate() {}
/** Callback, simply calls the method of the instance given to the constructor. */
virtual void operator() () { (_instance->*_function)(); }
protected:
/** The instance. */
T *_instance;
/** The method. */
void (T::*_function)(void);
};
/** Central message queue (singleton). Must be created before any other SDR object is constructed.
* The queue collects all buffers for processing and routes them to their destination. The queue
* loop can either be run in a separate thread by passing @c parallel=true to the factory method
* @c get. In this case, the @c exec method will return immediately. Otherwise, the queue loop
* will be executed in the thread calling @c exec which blocks until the queue is stopped by
* a call to @c stop. */
class Queue
{
public:
/** The internal used message type. */
class Message {
public:
/** Constructor. */
Message(const RawBuffer &buffer, SinkBase *sink, bool allow_overwrite)
: _buffer(buffer), _sink(sink), _allow_overwrite(allow_overwrite) { }
/** Copy constructor. */
Message(const Message &other)
: _buffer(other._buffer), _sink(other._sink), _allow_overwrite(other._allow_overwrite) { }
/** Assignment operator. */
const Message &operator= (const Message &other) {
_buffer = other._buffer;
_sink = other._sink;
_allow_overwrite = other._allow_overwrite;
return *this;
}
/** Returns the buffer of the message. **/
inline const RawBuffer &buffer() const { return _buffer; }
/** Returns the buffer of the message. **/
inline RawBuffer &buffer() { return _buffer; }
/** Returns the destination of the message. **/
inline SinkBase *sink() const { return _sink; }
/** If true, the sender allows to overwrite the content of the buffer. **/
inline bool allowOverwrite() const { return _allow_overwrite; }
protected:
/** The buffer being send. */
RawBuffer _buffer;
/** The destination. */
SinkBase *_sink;
/** If true, the sender allows to overwrite the buffer. */
bool _allow_overwrite;
};
protected:
/** Hidden constructor, use @c get to get the singleton instance. */
Queue();
public:
/** Destructor. */
virtual ~Queue();
/** Get a reference to the global instance of the queue. If @c parallel is @c true, the
* queue will be constructed in parallel mode, means the queue loop will be executed in a
* separate thread. Please note that this option is only used in the first call, when the
* singleton instance of the queue is created. */
static Queue &get();
/** Adds a buffer and its receiver to the queue. If @c allow_overwrite is @c true, the
* the receiver is allowed to overwrite the content of the buffer. */
void send(const RawBuffer &buffer, SinkBase *sink, bool allow_overwrite=false);
/** Enters the queue loop, if @c parallel=true was passed to @c get, @c exec will execute the
* queue loop in a separate thread and returns immediately. Otherwise, @c exec will block until
* the queue is stopped. */
void start();
/** Signals the queue to stop processing. */
void stop();
/** Wait for the queue to exit the queue loop. */
void wait();
/** Returns true if the queue loop is stopped. */
bool isStopped() const;
/** Returns true if the queue loop is running. */
bool isRunning() const;
/** Adds a callback to the idle event. The method gets called repeatedly while the queue looop
* is idle, means that there are no messages to be processed. This can be used to trigger an
* input source to read more data. */
template <class T>
void addIdle(T *instance, void (T::*function)(void)) {
_idle.push_back(new Delegate<T>(instance, function));
}
/** Adds a callback to the start event. The method gets called once the queue loop is started. */
template <class T>
void addStart(T *instance, void (T::*function)(void)) {
_onStart.push_back(new Delegate<T>(instance, function));
}
/** Adds a callback to the stop event. The method gets called once the queue loop is stopped. */
template <class T>
void addStop(T *instance, void (T::*function)(void)) {
_onStop.push_back(new Delegate<T>(instance, function));
}
protected:
/** The actual queue loop. */
void _main();
/** Emits the idle signal. */
void _signalIdle();
/** Emits the start signal. */
void _signalStart();
/** Emits the stop signal. */
void _signalStop();
protected:
/** While this is true, the queue loop is executed. */
bool _running;
/** If @c _parallel is true, the thread of the queue loop. */
pthread_t _thread;
/** The queue mutex. */
pthread_mutex_t _queue_lock;
/** The queue condition. */
pthread_cond_t _queue_cond;
/** The message queue. */
std::list<Message> _queue;
/** Idle event callbacks. */
std::list<DelegateInterface *> _idle;
/** Start event callbacks. */
std::list<DelegateInterface *> _onStart;
/** Stop event callbacks. */
std::list<DelegateInterface *> _onStop;
private:
/** The singleton instance. */
static Queue *_instance;
/** The pthread function. */
static void *__thread_start(void *ptr);
};
}
#endif // __SDR_QUEUE_HH__

@ -0,0 +1 @@
#include "resample.hh"

@ -0,0 +1,113 @@
#ifndef __SDR_RESAMPLE_HH__
#define __SDR_RESAMPLE_HH__
#include "config.hh"
#include "traits.hh"
#include "node.hh"
namespace sdr {
/** A linear interpolating resampling node. */
template<class Scalar>
class Resample: public Sink<Scalar>, public Source
{
public:
/** The computation (super) scalar. */
typedef typename Traits<Scalar>::SScalar SScalar;
public:
/** Constructor with target sample-rate. */
Resample(double sampleRate)
: Sink<Scalar>(), Source(), _sample_rate(sampleRate), _last_value(0), _last_count(0),
_incr((1<<_incr_mult))
{
// pass...
}
/** Configures the re-sampling node. */
virtual void config(const Config &src_cfg) {
// Requires type, sample rate and buffer size
if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.hasBufferSize()) { return; }
// Check type
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not confiure Resample node: Invalid input type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Compute sub/super sampling periods
_incr = size_t((1<<_incr_mult)*src_cfg.sampleRate()/_sample_rate);
// Allocate buffer
if (!_buffer.isEmpty()) { _buffer.unref(); }
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
// Propergate config
setConfig(Config(src_cfg.type(), _sample_rate, _buffer.size(), 1));
}
/** Performs the processing (actually dispatches the call to @c _process). */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
if (buffer.isEmpty()) { return; }
else if (allow_overwrite && ((_incr>>_incr_mult)>0)) {
// perform in-place if source allows it and we do a sub-sampling
_process(buffer, buffer);
} else if (_buffer.isUnused()) {
_process(buffer, _buffer);
} else {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "Resample: Drop buffer: Output buffer still in use.";
Logger::get().log(msg);
#endif
}
}
protected:
/** Performs the re-sampling. */
inline void _process(const Buffer<Scalar> &in, const Buffer<Scalar> &out) {
size_t j=0;
// calculate indices to interpolate:
int i1 = (_last_count>>_incr_mult), i2 = i1+1;
int r = _last_count - (i1<<_incr_mult);
while (i2 < in.size() || ((0 == r) && (i1 < in.size())) ) {
SScalar v1 = (i1 < 0) ? _last_value : in[i1];
// Linear interpolation
out[j] = v1 + ( (0 == r) ? 0 : ((r*(in[i2]-v1))>>_incr_mult) ); j++;
// Update tick count
_last_count += _incr;
// calculate indices to interpolate:
i1 = (_last_count>>_incr_mult); i2 = i1+1;
r = _last_count - (i1<<_incr_mult);
}
_last_value = in[in.size()-1];
// store offset as negative distance from last sample tick (_last_count-_incr)
// and tick-index of the first sample of the next buffer:
_last_count =
send(out.head(j), true);
}
protected:
/** The output sample-rate. */
double _sample_rate;
/** The last value. */
SScalar _last_value;
/** The number of samples left from the last buffer. */
int _last_count;
/** Ration between input sample rate and output sample rate times 256. */
int _incr;
/** Buffer. */
Buffer<Scalar> _buffer;
protected:
/** Increment multiplier. This is used to avoid round-off errors in the integer arithmetic to
* calculate the interpolation position. */
static const size_t _incr_mult = 8;
};
}
#endif // __SDR_RESAMPLE_HH__

@ -0,0 +1,125 @@
#include "rtlsource.hh"
#include "logger.hh"
using namespace sdr;
RTLSource::RTLSource(double frequency, double sample_rate)
: Source(), _frequency(frequency), _sample_rate(sample_rate), _agc_enabled(true),
_buffer_size(131072), _device(0)
{
{
LogMessage msg(LOG_DEBUG);
msg << "Found " << rtlsdr_get_device_count()
<< " RTL2832 devices, using No. 0.";
Logger::get().log(msg);
}
// Open device
if (0 < rtlsdr_get_device_count()) {
/// @bug Allow to select the device index.
if (rtlsdr_open(&_device, 0)) {
ConfigError err; err << "Can not open RTL2832 USB device " << 0; throw err;
}
} else {
ConfigError err; err << "Can not open RTL2832 USB device: No device found."; throw err;
}
{
LogMessage msg(LOG_DEBUG);
msg << "Using device: " << rtlsdr_get_device_name(0);
Logger::get().log(msg);
}
// Try to set center frequency
if (0 < _frequency) { setFrequency(_frequency); }
// Set sample rate
if (_sample_rate > 0) {
setSampleRate(sample_rate);
}
// Enable AGC/manual gain
enableAGC(_agc_enabled);
// Clear or allocate buffers
rtlsdr_reset_buffer(_device);
// Propergate config:
this->setConfig(Config(Config::typeId< std::complex<uint8_t> >(), _sample_rate, _buffer_size, 15));
}
RTLSource::~RTLSource() {
rtlsdr_close(_device);
}
void
RTLSource::setFrequency(double frequency) {
rtlsdr_set_center_freq(_device, uint32_t(frequency));
// Update center frequency
_frequency = double (rtlsdr_get_center_freq(_device));
}
void
RTLSource::setFreqCorrection(double ppm) {
rtlsdr_set_freq_correction(_device, ppm);
}
void
RTLSource::setSampleRate(double sample_rate) {
uint32_t sr = sample_rate;
if (sr < 225001) { sr = 225001; }
else if ((sr>300000) && (sr<900001)) { sr = 900001; }
else if (sr>2400000) { sr = 2400000; }
rtlsdr_set_sample_rate(_device, sr);
_sample_rate = rtlsdr_get_sample_rate(_device);
this->setConfig(Config(Config::Type_cu8, _sample_rate, _buffer_size, 15));
}
void
RTLSource::enableAGC(bool enable) {
_agc_enabled = enable;
if (_agc_enabled) {
rtlsdr_set_tuner_gain_mode(_device, !_agc_enabled);
rtlsdr_set_agc_mode(_device, _agc_enabled);
}
}
void
RTLSource::setGain(double gain) {
if (!_agc_enabled) {
rtlsdr_set_tuner_gain(_device, gain);
}
}
void
RTLSource::start() {
pthread_create(&_thread, 0, RTLSource::__rtl_srd_parallel_main, this);
}
void
RTLSource::stop() {
void *p;
// stop async reading of RTL device
rtlsdr_cancel_async(_device);
// Wait for blocked thread to exit
pthread_join(_thread, &p);
}
void *
RTLSource::__rtl_srd_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);
return 0;
}
void
RTLSource::__rtl_sdr_callback(unsigned char *buffer, uint32_t len, void *ctx) {
RTLSource *self = reinterpret_cast<RTLSource *>(ctx);
self->send(Buffer< std::complex<uint8_t> >((std::complex<uint8_t> *)buffer, len/2));
}

@ -0,0 +1,83 @@
#ifndef __SDR_RTLSOURCE_HH__
#define __SDR_RTLSOURCE_HH__
#include <rtl-sdr.h>
#include <pthread.h>
#include "node.hh"
namespace sdr {
/** Implements a @c uint_8 I/Q source for RTL2832 based TV dongles. */
class RTLSource: public Source
{
public:
/** Constructor.
*
* By default the gain is set to be automatically determined, this can be changed with the
* @c enableAGC and @c setGain methods.
*
* @param frequency Specifies the tuner frequency.
* @param sample_rate Specifies the sample rate. */
RTLSource(double frequency, double sample_rate=1000000);
/** Destructor. */
virtual ~RTLSource();
/** Returns the freuency of the tuner. */
inline double frequency() const { return _frequency; }
/** (Re-) Sets the tuner frequency. */
void setFrequency(double frequency);
/** Returns the frequency correction in parts per million (ppm). */
inline double freqCorrection() const { return rtlsdr_get_freq_correction(_device); }
/** (Re-) Sets the frequency correction in ppm. */
void setFreqCorrection(double ppm);
/** Returns the sample rate. */
inline double sampleRate() const { return _sample_rate; }
/** (Re-) sets the sample rate. This method also triggers the reconfiguration of all
* connected sinks. */
void setSampleRate(double sample_rate);
/** Returns true if AGC is enabled. */
inline bool agcEnabled() const { return _agc_enabled; }
/** Enable/Disable AGC. */
void enableAGC(bool enable);
/** Returns the tuner gain. */
inline double gain() const { return rtlsdr_get_tuner_gain(_device); }
/** (Re-) Sets the tuner gain. Has no effect in AGC mode. */
void setGain(double gain);
/** Starts the reception. */
void start();
/** Stops the reception. */
void stop();
protected:
/** Prallel routine to receive some data from the device. */
static void *__rtl_srd_parallel_main(void *ctx);
/** Callback to process received data. */
static void __rtl_sdr_callback(unsigned char *buffer, uint32_t len, void *ctx);
protected:
/** The current tuner frequency. */
double _frequency;
/** The current sample rate. */
double _sample_rate;
/** If true, the AGC is enabled. */
bool _agc_enabled;
/** The buffer size. */
size_t _buffer_size;
/** The RTL2832 device object. */
rtlsdr_dev_t *_device;
/** The thread object. */
pthread_t _thread;
};
}
#endif // __SDR_RTLSOURCE_HH__

@ -0,0 +1,37 @@
/** @mainpage A C++ library for software defined radio (SDR).
*/
#ifndef __SDR_HH__
#define __SDR_HH__
#include "config.hh"
#include "operators.hh"
#include "traits.hh"
#include "exception.hh"
#include "buffer.hh"
#include "node.hh"
#include "queue.hh"
#include "combine.hh"
#include "logger.hh"
#include "utils.hh"
#include "demod.hh"
#include "siggen.hh"
#include "buffernode.hh"
#include "wavfile.hh"
#include "firfilter.hh"
#include "autocast.hh"
#ifdef SDR_WITH_FFTW
#include "filternode.hh"
#endif
#ifdef SDR_WITH_PORTAUDIO
#include "portaudio.hh"
#endif
#ifdef SDR_WITH_RTLSDR
#include "rtlsource.hh"
#endif
#endif // __SDR_HH__

@ -0,0 +1,2 @@
#include "siggen.hh"

@ -0,0 +1,87 @@
#ifndef __SDR_SIGGEN_HH__
#define __SDR_SIGGEN_HH__
#include "node.hh"
namespace sdr {
/** Arbitrary function generator. */
template <class Scalar>
class SigGen: public Source
{
public:
/** Constructs the function generator. */
SigGen(double samplerate, size_t buffersize, double tmax=-1)
: Source(), _sampleRate(samplerate), _dt(1./_sampleRate), _t(0), _tMax(tmax), _scale(1),
_bufferSize(buffersize), _buffer(buffersize)
{
switch (Config::typeId<Scalar>()) {
case Config::Type_u8:
case Config::Type_s8:
case Config::Type_cu8:
case Config::Type_cs8:
_scale = 127; break;
case Config::Type_u16:
case Config::Type_s16:
case Config::Type_cu16:
case Config::Type_cs16:
_scale = 32000; break;
default:
_scale = 1; break;
}
this->setConfig(Config(Config::typeId<Scalar>(), samplerate, buffersize, 1));
}
/** Destructor. */
virtual ~SigGen() {}
/** Computes the next buffer. This function can be connected to the idle signal of the @c Queue. */
void next() {
// Stop processing once the max time elapsed
if ((_tMax>0) && (_t >= _tMax)) { Queue::get().stop(); return; }
// Assemble signal
for (size_t i=0; i<_bufferSize; i++) {
_buffer[i] = 0;
if (_signals.size() > 0) {
std::list< std::vector<double> >::iterator item = _signals.begin();
for (; item != _signals.end(); item++) {
_buffer[i] += (_scale*((*item)[1] * sin(2*M_PI*(*item)[0]*_t + (*item)[2]))/_signals.size());
}
}
_t += _dt;
}
// Send buffer
this->send(_buffer);
}
/** Add a sine function to the function generator. */
void addSine(double freq, double ampl=1, double phase=0) {
std::vector<double> tmp(3); tmp[0] = freq; tmp[1] = ampl; tmp[2] = phase;
_signals.push_back(tmp);
}
protected:
/** The sample rate of the function generator. */
double _sampleRate;
/** The sample period. */
double _dt;
/** The current time. */
double _t;
/** The maximum time. */
double _tMax;
/** The scaling of the signal. */
double _scale;
/** A list of functions. */
std::list< std::vector<double> > _signals;
/** The size of the output buffer. */
size_t _bufferSize;
/** The output buffer. */
Buffer<Scalar> _buffer;
};
}
#endif // __SDR_SIGGEN_HH__

@ -0,0 +1 @@
#include "streamsource.hh"

@ -0,0 +1,12 @@
#ifndef __SDR_STREAMSOURCE_HH__
#define __SDR_STREAMSOURCE_HH__
#include <istream>
#include "node.hh"
#include "queue.hh"
namespace sdr {
}
#endif // __SDR_STREAMSOURCE_HH__

@ -0,0 +1,35 @@
#include "traits.hh"
using namespace sdr;
const float Traits<uint8_t>::scale = 127;
const size_t Traits<uint8_t>::shift = 6;
const float Traits< std::complex<uint8_t> >::scale = 127;
const size_t Traits< std::complex<uint8_t> >::shift = 6;
const float Traits<int8_t>::scale = 127;
const size_t Traits<int8_t>::shift = 6;
const float Traits< std::complex<int8_t> >::scale = 127;
const size_t Traits< std::complex<int8_t> >::shift = 6;
const float Traits<uint16_t>::scale = 32767;
const size_t Traits<uint16_t>::shift = 15;
const float Traits< std::complex<uint16_t> >::scale = 32767;
const size_t Traits< std::complex<uint16_t> >::shift = 14;
const float Traits<int16_t>::scale = 32767;
const size_t Traits<int16_t>::shift = 15;
const float Traits< std::complex<int16_t> >::scale = 32767;
const size_t Traits< std::complex<int16_t> >::shift = 14;
const float Traits<float>::scale = 1;
const size_t Traits<float>::shift = 0;
const float Traits< std::complex<float> >::scale = 1;
const size_t Traits< std::complex<float> >::shift = 0;
const float Traits<double>::scale = 1;
const size_t Traits<double>::shift = 0;
const float Traits< std::complex<double> >::scale = 1;
const size_t Traits< std::complex<double> >::shift = 0;

@ -0,0 +1,208 @@
#ifndef __SDR_TRAITS_HH__
#define __SDR_TRAITS_HH__
#include <inttypes.h>
#include <complex>
#include "node.hh"
namespace sdr {
/** Forward declaration of type tratis template. */
template <class T> class Traits;
/** Template specialization of type tratis for uint8_t scalar. */
template <>
class Traits<uint8_t> {
public:
/** The Scalar type. */
typedef uint8_t Scalar;
/** The compute scalar type. */
typedef uint16_t SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** Shift from Scalar to SScalar. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_u8;
};
/** Template specialization of type tratis for complex uint8_t scalar. */
template <>
class Traits< std::complex<uint8_t> > {
public:
/** The Scalar type. */
typedef std::complex<uint8_t> Scalar;
/** The compute scalar type. */
typedef std::complex<uint16_t> SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_cu8;
};
/** Template specialization of type tratis for int8_t scalar. */
template <>
class Traits<int8_t> {
public:
/** The Scalar type. */
typedef int8_t Scalar;
/** The compute scalar type. */
typedef int16_t SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_s8;
};
/** Template specialization of type tratis for complex int8_t scalar. */
template <>
class Traits< std::complex<int8_t> > {
public:
/** The Scalar type. */
typedef std::complex<int8_t> Scalar;
/** The compute scalar type. */
typedef std::complex<int16_t> SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_cs8;
};
/** Template specialization of type traits for uint16_t scalar. */
template <>
class Traits<uint16_t> {
public:
/** The Scalar type. */
typedef uint16_t Scalar;
/** The compute scalar type. */
typedef uint32_t SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_u16;
};
/** Template specialization of type traits for complex uint16_t scalar. */
template <>
class Traits< std::complex<uint16_t> > {
public:
/** The Scalar type. */
typedef std::complex<uint16_t> Scalar;
/** The compute scalar type. */
typedef std::complex<uint32_t> SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_cu16;
};
/** Template specialization of type traits for int16_t scalar. */
template <>
class Traits<int16_t> {
public:
/** The Scalar type. */
typedef int16_t Scalar;
/** The compute scalar type. */
typedef int32_t SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_s16;
};
/** Template specialization of type traits for complex int16_t scalar. */
template <>
class Traits< std::complex<int16_t> > {
public:
/** The Scalar type. */
typedef std::complex<int16_t> Scalar;
/** The compute scalar type. */
typedef std::complex<int32_t> SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_cs16;
};
/** Template specialization of type traits for float scalar. */
template <>
class Traits<float> {
public:
/** The Scalar type. */
typedef float Scalar;
/** The compute scalar type. */
typedef float SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_f32;
};
/** Template specialization of type traits for complex float scalar. */
template <>
class Traits< std::complex<float> > {
public:
/** The Scalar type. */
typedef std::complex<float> Scalar;
/** The compute scalar type. */
typedef std::complex<float> SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_cf32;
};
/** Template specialization of type traits for float scalar. */
template <>
class Traits<double> {
public:
/** The Scalar type. */
typedef double Scalar;
/** The compute scalar type. */
typedef double SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_f64;
};
/** Template specialization of type traits for complex float scalar. */
template <>
class Traits< std::complex<double> > {
public:
/** The Scalar type. */
typedef std::complex<double> Scalar;
/** The compute (super) scalar type. */
typedef std::complex<double> SScalar;
/** The scaleing factor from floating point to integer. */
const static float scale;
/** The scaleing factor from floating point to integer expressed as 2^exp. */
const static size_t shift;
/** The type id of the scalar type. */
const static Config::Type scalarId = Config::Type_cf64;
};
}
#endif // TRAITS_HH

@ -0,0 +1,187 @@
#include "utils.hh"
using namespace sdr;
/* ********************************************************************************************* *
* Implementation of UnsignedToSinged
* ********************************************************************************************* */
UnsignedToSigned::UnsignedToSigned(float scale)
: SinkBase(), Source(), _scale(scale)
{
// pass...
}
UnsignedToSigned::~UnsignedToSigned() {
// pass...
}
void
UnsignedToSigned::config(const Config &src_cfg) {
// Requires type
if (!src_cfg.hasType()) { return; }
size_t scalar_size;
Config::Type out_type;
// Requires a unsigned integer or complex unsigned integer type as input
switch (src_cfg.type()) {
case Config::Type_u8:
scalar_size = 1; out_type = Config::Type_s8;
_process = &UnsignedToSigned::_process_int8;
break;
case Config::Type_cu8:
scalar_size = 2; out_type = Config::Type_cs8;
_process = &UnsignedToSigned::_process_int8;
break;
case Config::Type_u16:
scalar_size = 2; out_type = Config::Type_s16;
_process = &UnsignedToSigned::_process_int16;
break;
case Config::Type_cu16:
scalar_size = 4; out_type = Config::Type_cs16;
_process = &UnsignedToSigned::_process_int16;
break;
default: {
ConfigError err;
err << "Can not configure Unsigned2Signed node: Invalid input type " << src_cfg.type()
<< ", expected " << Config::Type_u8 << ", " << Config::Type_cu8 << ", " << Config::Type_u16
<< " or " << Config::Type_cu8;
throw err;
}
}
// unreference buffer if non-empty
if (!_buffer.isEmpty()) { _buffer.unref(); }
// Allocate buffer
_buffer = RawBuffer(scalar_size*src_cfg.bufferSize());
// Propergate config
this->setConfig(Config(
out_type, src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
}
void
UnsignedToSigned::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) {
if (allow_overwrite) {
(this->*_process)(buffer, buffer);
}
else if (_buffer.isUnused()) {
(this->*_process)(buffer, _buffer);
} else {
#ifdef SDR_DEBUG
std::cerr << "Unsigned2Signed: Drop buffer: Output buffer still in use." << std::endl;
#endif
}
}
void
UnsignedToSigned::_process_int8(const RawBuffer &in, const RawBuffer &out) {
size_t num = in.bytesLen();
uint8_t *in_ptr = (uint8_t *) in.data();
int8_t *out_ptr = (int8_t *) out.data();
//std::cerr << "UnsingedToSigned: Process " << Buffer<uint8_t>(in) << std::endl;
for (size_t i=0; i<num; i++, in_ptr++, out_ptr++) {
*out_ptr = int16_t(*in_ptr) - 128;
}
//std::cerr << " -> " << Buffer<int8_t>(RawBuffer(out, 0, num)) << std::endl;
this->send(RawBuffer(out, 0, num), true);
}
void
UnsignedToSigned::_process_int16(const RawBuffer &in, const RawBuffer &out) {
size_t num = in.bytesLen()/2;
uint16_t *in_ptr = (uint16_t *) in.data();
int16_t *out_ptr = (int16_t *) out.data();
for (size_t i=0; i<num; i++, in_ptr++, out_ptr++) {
*out_ptr = int32_t(*in_ptr) - 32768;
}
this->send(RawBuffer(out, 0, num), true);
}
/* ********************************************************************************************* *
* Implementation of SignedToUnsinged
* ********************************************************************************************* */
SignedToUnsigned::SignedToUnsigned()
: SinkBase(), Source()
{
// pass...
}
SignedToUnsigned::~SignedToUnsigned() {
// pass...
}
void
SignedToUnsigned::config(const Config &src_cfg) {
// Requires type
if (!src_cfg.hasType()) { return; }
size_t scalar_size;
Config::Type out_type;
// Requires a unsigned integer or complex unsigned integer type as input
switch (src_cfg.type()) {
case Config::Type_s8:
scalar_size = 1; out_type = Config::Type_u8;
_process = &SignedToUnsigned::_process_int8;
break;
case Config::Type_cs8:
scalar_size = 2; out_type = Config::Type_cu8;
_process = &SignedToUnsigned::_process_int8;
break;
case Config::Type_s16:
scalar_size = 2; out_type = Config::Type_u16;
_process = &SignedToUnsigned::_process_int16;
break;
case Config::Type_cs16:
scalar_size = 4; out_type = Config::Type_cu16;
_process = &SignedToUnsigned::_process_int16;
break;
default: {
ConfigError err;
err << "Can not configure SignedToUnsigned node: Invalid input type " << src_cfg.type()
<< ", expected " << Config::Type_s8 << ", " << Config::Type_cs8 << ", " << Config::Type_s16
<< " or " << Config::Type_cs8;
throw err;
}
}
// Allocate buffer
_buffer = RawBuffer(scalar_size*src_cfg.bufferSize());
// Propergate config
this->setConfig(Config(out_type, src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
}
void
SignedToUnsigned::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) {
if (allow_overwrite) {
(this->*_process)(buffer, buffer);
}
else if (_buffer.isUnused()) {
(this->*_process)(buffer, _buffer);
} else {
#ifdef SDR_DEBUG
std::cerr << "SignedToUnsigned: Drop buffer: Output buffer still in use." << std::endl;
#endif
}
}
void
SignedToUnsigned::_process_int8(const RawBuffer &in, const RawBuffer &out) {
size_t num = in.bytesLen();
for (size_t i=0; i<num; i++) {
((uint8_t *)out.data())[i] = int(((int8_t *)in.data())[i]) + 128;
}
this->send(RawBuffer(out, 0, num), true);
}
void
SignedToUnsigned::_process_int16(const RawBuffer &in, const RawBuffer &out) {
size_t num = in.bytesLen()/2;
for (size_t i=0; i<num; i++) {
((uint16_t *)out.data())[i] = int32_t( ((int16_t *)in.data())[i] ) + 32768;
}
this->send(RawBuffer(out, 0, num), true);
}

@ -0,0 +1,958 @@
#ifndef __SDR_UTILS_HH__
#define __SDR_UTILS_HH__
#include "config.hh"
#include "node.hh"
#include "traits.hh"
#include "operators.hh"
#include "logger.hh"
#include <ctime>
namespace sdr {
/** Extracts the real or imaginary part of a complex valued data stream. */
template <class Scalar>
class RealImagPart: public Sink< std::complex<Scalar> >, public Source
{
public:
/** Constructor. If @c select_real is @c true, the real part is selected, if @c select_real is
* @c false, the imaginary part is selected. */
RealImagPart(bool select_real, double scale=1.0)
: Sink< std::complex<Scalar> >(), Source(), _buffer(), _select_real(select_real), _scale(scale)
{
// pass...
}
/** Configures the node. */
virtual void config(const Config &src_cfg) {
// Needs full config
if ((Config::Type_UNDEFINED==src_cfg.type()) || (0 == src_cfg.bufferSize())) { return; }
// Assert complex type
if (src_cfg.type() != Config::typeId< std::complex<Scalar> >()) {
ConfigError err;
err << "Can not configure sink of RealPart: Invalid buffer type " << src_cfg.type()
<< " expected " << Config::typeId< std::complex<Scalar> >();
throw err;
}
// Resize buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
// propergate config
this->setConfig(Config(Config::typeId< Scalar >(), src_cfg.sampleRate(),
src_cfg.bufferSize(), 1));
if (_select_real) {
LogMessage msg(LOG_DEBUG);
msg << "Configured RealPart node:" << std::endl
<< " type: " << Config::typeId<Scalar>() << std::endl
<< " sample-rate: " << src_cfg.sampleRate() << std::endl
<< " buffer-size: " << src_cfg.bufferSize();
Logger::get().log(msg);
}
}
/** Processes the incomming data. */
virtual void process(const Buffer<std::complex<Scalar> > &buffer, bool allow_overwrite) {
// Convert
if (_select_real) {
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = _scale*buffer[i].real();
}
} else {
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = _scale*buffer[i].imag();
}
}
this->send(_buffer);
}
protected:
/** The output buffer. */
Buffer<Scalar> _buffer;
/** Real/Imag selection. */
bool _select_real;
/** The scale. */
double _scale;
};
/** Selects the real part of a complex signal. */
template <class Scalar>
class RealPart: public RealImagPart<Scalar>
{
public:
/** Constructor. */
RealPart(double scale=1.0)
: RealImagPart<Scalar>(true, scale)
{
// pass...
}
};
/** Selects the imaginary part of a complex signal. */
template <class Scalar>
class ImagPart: public RealImagPart<Scalar>
{
public:
/** Constructor. */
ImagPart(double scale=1.0)
: RealImagPart<Scalar>(false, scale)
{
// pass...
}
};
/** Tiny helper node to transform a real part into a complex, including
* a possible type-cast*/
template <class iScalar, class oScalar=iScalar>
class ToComplex: public Sink<iScalar>, public Source
{
public:
/** Constructor. */
ToComplex(double scale=1.0)
: Sink<iScalar>(), Source(), _scale(scale)
{
// pass...
}
/** Configures the node. */
virtual void config(const Config &src_cfg) {
// Requires at least type & buffer size
if (!src_cfg.hasType() || !src_cfg.hasBufferSize()) { return; }
// Check input type
if (Config::typeId<iScalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure ToComplex node: Invalid buffer type " << src_cfg.type()
<< ", expected " << Config::typeId<iScalar>();
throw err;
}
// Allocate buffer:
_buffer = Buffer< std::complex<oScalar> >(src_cfg.bufferSize());
// Propergate config
this->setConfig(
Config(Config::typeId< std::complex<oScalar> >(),
src_cfg.sampleRate(), src_cfg.bufferSize(), src_cfg.numBuffers()));
}
/** Casts the input real buffer into the complex output buffer. */
virtual void process(const Buffer<iScalar> &buffer, bool allow_overwrite) {
if (1.0 == _scale) {
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = std::complex<oScalar>(oScalar(buffer[i]));
}
} else {
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = std::complex<oScalar>(_scale*oScalar(buffer[i]));
}
}
// propergate buffer
this->send(_buffer.head(buffer.size()));
}
protected:
/** The scale. */
double _scale;
/** The output buffer. */
Buffer< std::complex<oScalar> > _buffer;
};
/** Explicit type cast node. */
template <class iScalar, class oScalar>
class Cast : public Sink<iScalar>, public Source
{
public:
/** Specifies the input super scalar. */
typedef typename Traits<iScalar>::SScalar iSScalar;
/** Specified the output super scalar. */
typedef typename Traits<oScalar>::SScalar oSScalar;
public:
/** Constructs a type-cast with optional scaleing. */
Cast(oScalar scale=1, iScalar shift=0)
: Sink<iScalar>(), Source(), _can_overwrite(false), _do_scale(false),
_scale(scale), _shift(shift), _buffer()
{
// pass...
}
/** Configures the type-cast node. */
virtual void config(const Config &src_cfg) {
// only check type
if (!src_cfg.hasType()) { return; }
if (src_cfg.type() != Config::typeId<iScalar>()) {
ConfigError err;
err << "Can not configure Cast: Invalid input type " << src_cfg.type()
<< ", expected " << Config::typeId<iScalar>();
throw err;
}
// unreference buffer if non-empty
if (! _buffer.isEmpty()) { _buffer.unref(); }
// allocate buffer
_buffer = Buffer<oScalar>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Configure Cast node:" << std::endl
<< " conversion: "<< Config::typeId<iScalar>()
<< " -> " << Config::typeId<oScalar>() << std::endl
<< " in-place " << (_can_overwrite ? "true" : "false") << std::endl
<< " scale: " << _scale;
Logger::get().log(msg);
_can_overwrite = sizeof(iScalar) >= sizeof(oScalar);
_do_scale = (_scale != oScalar(0));
// forward config
this->setConfig(Config(Config::typeId<oScalar>(), src_cfg.sampleRate(),
src_cfg.bufferSize(), 1));
}
/** Performs the type-cast node. */
virtual void process(const Buffer<iScalar> &buffer, bool allow_overwrite) {
if (allow_overwrite && _can_overwrite) {
_process(buffer, Buffer<oScalar>(buffer));
} else if (_buffer.isUnused()) {
_process(buffer, _buffer);
} else {
#ifdef SDR_DEBUG
std::cerr << "Cast: Drop buffer: Output buffer is still in use by "
<< _buffer.refCount() << std::endl;
#endif
}
}
/** Returns the scaling. */
inline double scale() const { return _scale; }
/** Sets the scaling. */
void setScale(double scale) {
_scale = scale;
_do_scale = (0 != _scale);
}
protected:
/** Internal used method to perform the type-case out-of-place. */
inline void _process(const Buffer<iScalar> &in, const Buffer<oScalar> &out) {
if (_do_scale) {
for (size_t i=0; i<in.size(); i++) {
out[i] = _scale*( cast<iScalar,oScalar>(in[i]) + cast<iScalar, oScalar>(_shift) );
}
} else {
for (size_t i=0; i<in.size(); i++) {
out[i] = in[i]+_shift;
}
}
this->send(out.head(in.size()));
}
protected:
/** If true, the type-cast (an scaleing) can be performed in-place. */
bool _can_overwrite;
/** If true, the output gets scaled. */
bool _do_scale;
/** The scaling. */
oScalar _scale;
/** Another scaling, using integer shift operation (faster). */
iScalar _shift;
/** The output buffer, unused if the type-cast is performed in-place . */
Buffer<oScalar> _buffer;
};
/** Performs a reinterprete cast from an unsinged value to a singed one. */
class UnsignedToSigned: public SinkBase, public Source
{
public:
/** Constructor with optional scaleing. */
UnsignedToSigned(float scale=1.0);
/** Destructor. */
virtual ~UnsignedToSigned();
/** Configures the cast node. */
virtual void config(const Config &src_cfg);
/** Performs the cast. */
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite);
protected:
/** Performs the cast for @c uint8 -> @c int8. */
void _process_int8(const RawBuffer &in, const RawBuffer &out);
/** Performs the cast for @c uint16 -> @c int16. */
void _process_int16(const RawBuffer &in, const RawBuffer &out);
protected:
/** Type-cast callback. */
void (UnsignedToSigned::*_process)(const RawBuffer &in, const RawBuffer &out);
/** The output buffer, unused if the cast can be performed in-place. */
RawBuffer _buffer;
/** Holds the scaleing. */
float _scale;
};
/** Performs a reinterprete cast from an unsinged value to a singed one. */
class SignedToUnsigned: public SinkBase, public Source
{
public:
/** Constructor with optional scaleing. */
SignedToUnsigned();
/** Destructor. */
virtual ~SignedToUnsigned();
/** Configures the cast node. */
virtual void config(const Config &src_cfg);
/** Performs the cast. */
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite);
protected:
/** Performs the int8 -> uint8 cast. */
void _process_int8(const RawBuffer &in, const RawBuffer &out);
/** Performs the int16 -> uint16 cast. */
void _process_int16(const RawBuffer &in, const RawBuffer &out);
protected:
/** Type-cast callback. */
void (SignedToUnsigned::*_process)(const RawBuffer &in, const RawBuffer &out);
/** The output buffer, unused if the cast is performed in-place. */
RawBuffer _buffer;
};
/** 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. */
template <class Scalar>
class FreqShift: public Sink< std::complex<Scalar> >, public Source
{
public:
/** Constructs a frequency shift node with optional scaleing of the result. */
FreqShift(double shift, Scalar scale=1.0)
: Sink< std::complex<Scalar> >(), Source(),
_shift(shift), _scale(scale), _factor(1), _sample_rate(0), _delta(1)
{
// pass...
}
/** Destructor. */
virtual ~FreqShift() {
// pass...
}
/** Returns the frequency shift. */
inline double shift() const { return _shift; }
/** Sets the frequency shift. */
void setShift(double shift) {
// Update delta
_shift = shift;
_delta = exp(std::complex<double>(0,2*M_PI*_shift/_sample_rate));
}
/** Configures the frequency shift node. */
virtual void config(const Config &src_cfg) {
// Requires type, samplerate & buffersize
if ((Config::Type_UNDEFINED==src_cfg.type()) || (0==src_cfg.sampleRate()) || (0==src_cfg.bufferSize())) { return; }
// Assert type
if (src_cfg.type() != Config::typeId< std::complex<Scalar> >()) {
ConfigError err;
err << "Can not configure FreqShift node: Invalid source type " << src_cfg.type()
<< ", expected " << Config::typeId< std::complex<Scalar> >();
throw err;
}
// Allocate buffer
_buffer = Buffer< std::complex<Scalar> >(src_cfg.bufferSize());
// Store sample rate
_sample_rate = src_cfg.sampleRate();
// Precalc delta
_delta = exp(std::complex<double>(0,2*M_PI*_shift/_sample_rate));
// reset factor
_factor = 1;
LogMessage msg(LOG_DEBUG);
msg << "Configure FreqShift node:" << std::endl
<< " shift: " << _shift << std::endl
<< " scale: " << _scale << std::endl
<< " sample-rate: " << src_cfg.sampleRate() << std::endl
<< " buffer-suize: " << src_cfg.bufferSize();
Logger::get().log(msg);
// Propergate config
this->setConfig(Config(Config::typeId< std::complex<Scalar> >(), src_cfg.sampleRate(),
src_cfg.bufferSize(), 1));
}
/** Performs the frequency shift. */
virtual void process(const Buffer<std::complex<Scalar> > &buffer, bool allow_overwrite) {
// Shift freq:
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = _scale*_factor*buffer[i]; _factor *= _delta;
}
// Send buffer
this->send(_buffer);
}
protected:
/** The output buffer. */
Buffer< std::complex<Scalar> > _buffer;
/** The frequency shift \f$f\f$ in Hz (\f$\omega=2\pi f\f$). */
double _shift;
/** The optional scale. */
Scalar _scale;
/** The current exponental factor, gets updated for every sample. */
std::complex<Scalar> _factor;
/** The current sample rate. */
double _sample_rate;
/** \f$\exp(i\omega t)\f$. */
std::complex<Scalar> _delta;
};
/** Reads raw samples from an imput stream, (ie a file). */
template<class Scalar>
class StreamSource: public Source
{
public:
/** Constructs a raw input source. */
StreamSource(std::istream &stream, double sample_rate=1, size_t buffersize=1024)
: Source(), _stream(stream), _buffer(buffersize)
{
// Assemble config for stream:
this->_config = Config(Config::typeId<Scalar>(), sample_rate, buffersize, 1);
}
/** Reads the next chunk. This function might be connected to the idle event of the Queue, then
* a new chunk gets read once all processing has been performed. */
void next() {
if(_stream.eof()) { Queue::get().stop(); return; }
int len = _stream.read(_buffer.ptr(), _buffer.storageSize());
if (len > 0) {
this->send(_buffer.head(len/sizeof(Scalar)));
}
}
protected:
/** The input stream. */
std::istream &_stream;
/** The output buffer. */
Buffer<Scalar> _buffer;
};
/** Serializes the incomming buffers as raw data. */
template <class Scalar>
class StreamSink: public Sink<Scalar>
{
public:
/** Constructor. */
StreamSink(std::ostream &stream)
: Sink<Scalar>(), _stream(stream)
{
// pass...
}
/** Configures the raw sink. */
virtual void config(const Config &src_cfg) {
if (Config::Type_UNDEFINED == src_cfg.type()) { return; }
if (src_cfg.type() != Config::typeId<Scalar>()) {
ConfigError err;
err << "Can not configure StreamSink: Invalid buffer type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// done...
}
/** Dumps the buffer into the stream as raw data. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
/// @bug Check if buffer was send completely:
_stream.write(buffer.data(), buffer.size()*sizeof(Scalar));
}
protected:
/** The output stream. */
std::ostream &_stream;
};
/** Simple averaging sub-sampler. */
template <class Scalar>
class SubSample: public Sink<Scalar>, public Source
{
public:
/** The super-scalar of the input type. */
typedef typename Traits<Scalar>::SScalar SScalar;
public:
/** Constructs a sub-sampler. */
SubSample(size_t n)
: Sink<Scalar>(), Source(),
_n(n), _oFs(0), _last(0), _left(0), _buffer()
{
// pass...
}
/** Constructs a sub-sampler by target sample rate. */
SubSample(double Fs)
: Sink<Scalar>(), Source(),
_n(1), _oFs(Fs), _last(0), _left(0), _buffer()
{
// pass...
}
/** Configures the sub-sampler. */
virtual void config(const Config &src_cfg) {
// Requires type and buffer size
if (!src_cfg.hasType() || !src_cfg.hasBufferSize()) { return; }
// check buffer type
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure SubSample node: Invalid buffer type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// If target sample rate is specified, determine _n by _oFs
if (_oFs > 0) {
_n = std::max(1.0, src_cfg.sampleRate()/_oFs);
}
// Determine buffer size
size_t out_size = src_cfg.bufferSize()/_n;
if (src_cfg.bufferSize() % _n) { out_size += 1; }
LogMessage msg(LOG_DEBUG);
msg << "Configure SubSample node:" << std::endl
<< " by: " << _n << std::endl
<< " type: " << src_cfg.type() << std::endl
<< " sample-rate: " << src_cfg.sampleRate()
<< " -> " << src_cfg.sampleRate()/_n << std::endl
<< " buffer-size: " << src_cfg.bufferSize()
<< " -> " << out_size;
Logger::get().log(msg);
// Resize buffer
_buffer = Buffer<Scalar>(out_size);
// Propergate config
this->setConfig(Config(src_cfg.type(), src_cfg.sampleRate()/_n, out_size, 1));
}
/** Performs the sub-sampling on the given buffer. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
if (allow_overwrite) {
_process(buffer, buffer);
} else if (_buffer.isUnused()) {
_process(buffer, _buffer);
} else {
#ifdef SDR_DEBUG
LogMessage msg(LOG_WARNING);
msg << "SubSample: Drop buffer, output buffer still in use.";
Logger::get().log(msg);
#endif
}
}
protected:
/** Performs the sub-sampling from @c in into @c out. */
void _process(const Buffer<Scalar> &in, const Buffer<Scalar> &out) {
size_t j=0;
for (size_t i=0; i<in.size(); i++, _left++) {
_last += in[i];
if (_n <= _left) {
out[j] = _last/SScalar(_n); j++; _last=0; _left=0;
}
}
this->send(out.head(j), true);
}
protected:
/** The sub-sampling, n=1 means no sub-sampling at all. */
size_t _n;
/** Target sample rate. */
double _oFs;
/** The last value. */
SScalar _last;
/** How many samples are left. */
size_t _left;
/** The output buffer, unused if the sub-sampling is performed in-place. */
Buffer<Scalar> _buffer;
};
/** Simple scaling node. */
template <class Scalar>
class Scale : public Sink<Scalar>, public Source
{
public:
/** Constructs the scaling node. */
Scale(float scale=1, Scalar shift=0)
: Sink<Scalar>(), Source(), _scale(scale), _shift(shift)
{
// pass...
}
/** Destructor. */
virtual ~Scale() {
// pass...
}
/** Configures the scaleing node. */
virtual void config(const Config &src_cfg) {
// Check for type & buffer size
if (!src_cfg.hasType() || !src_cfg.bufferSize()) { return; }
// Check type
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure Scale node: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
// Done, propergate config
this->setConfig(src_cfg);
}
/** Performs the scaleing. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
if ((1 == _scale) && (Scalar(0) == _shift)) {
this->send(buffer, allow_overwrite);
} else if (allow_overwrite) {
// Scale inplace
for (size_t i=0; i<buffer.size(); i++) { buffer[i] *= _scale; }
this->send(buffer, allow_overwrite);
} else if (_buffer.isUnused()) {
// Scale out-of-place
for (size_t i=0; i<buffer.size(); i++) { _buffer[i] = _scale*_buffer[i]; }
this->send(_buffer.head(buffer.size()), true);
}
// else if buffer is still in use -> drop buffer...
}
protected:
/** The output buffer, unused if the scaling is performed in-place. */
Buffer<Scalar> _buffer;
/** The scaling. */
float _scale;
/** Alternative formulation for the scaling, using integer shift operators. */
Scalar _shift;
};
/** An automatic gain control widget. */
template <class Scalar>
class AGC: public Sink<Scalar>, public Source
{
public:
/** Constructor. */
AGC(double tau=0.1, double target=0)
: Sink<Scalar>(), Source(), _enabled(true), _tau(tau), _lambda(0), _sd(0),
_target(target), _gain(1), _sample_rate(0)
{
if (0 == target) {
// Determine target by scalar type
switch (Config::typeId<Scalar>()) {
case Config::Type_u8:
case Config::Type_s8:
case Config::Type_cu8:
case Config::Type_cs8:
_target = 127; break;
case Config::Type_u16:
case Config::Type_s16:
case Config::Type_cu16:
case Config::Type_cs16:
_target = 32000; break;
case Config::Type_f32:
case Config::Type_f64:
case Config::Type_cf32:
case Config::Type_cf64:
_target = 1.; break;
case Config::Type_UNDEFINED: {
ConfigError err; err << "Can not configure AGC node: Unsupported type."; throw err;
}
}
}
_sd = _target;
}
/** Returns true, if the AGC is enabled. */
inline bool enabled() const {
return _enabled;
}
/** Enable/disable the AGC node. */
inline void enable(bool enabled) {
_enabled = enabled;
}
/** Returns the current gain factor. */
inline double gain() const {
return _gain;
}
/** Resets the current gain factor. */
inline void setGain(double gain) {
_gain = gain;
}
/** Returns the time-constant of the AGC. */
inline double tau() const {
return _tau;
}
/** Sets the time-constant of the AGC. */
inline void setTau(double tau) {
// calc decay factor
_tau = tau;
_lambda = exp(-1./(_tau*_sample_rate));
}
/** Configures the AGC node. */
virtual void config(const Config &src_cfg) {
// Need sample rate & type
if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.hasBufferSize()) { return; }
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure AGC node: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// calc decay factor
_sample_rate = src_cfg.sampleRate();
_lambda = exp(-1./(_tau*src_cfg.sampleRate()));
// reset variance
_sd = _target;
// Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Configured AGC:" << std::endl
<< " type: " << src_cfg.type() << std::endl
<< " sample-rate: " << src_cfg.sampleRate() << std::endl
<< " tau: " << _tau << std::endl
<< " lambda [1/s]: " << pow(_lambda, src_cfg.sampleRate()) << std::endl
<< " lambda [1/sam]: " << _lambda << std::endl
<< " target value: " << _target;
Logger::get().log(msg);
// Propergate config
this->setConfig(Config(Config::typeId<Scalar>(), src_cfg.sampleRate(),
src_cfg.bufferSize(), 1));
}
/** Performs the amplification and adjusts the gain. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
// Simply forward buffer if disabled
if ((! _enabled) && (0 == _gain) ) {
this->send(buffer, allow_overwrite); return;
}
// Update signal ampl
for (size_t i=0; i<buffer.size(); i++) {
_sd = _lambda*_sd + (1-_lambda)*std::abs(buffer[i]);
if (_enabled) { _gain = _target/(4*_sd); }
_buffer[i] = _gain*buffer[i];
}
this->send(_buffer);
}
protected:
/** If true, the automatic gain adjustment is enabled. */
bool _enabled;
/** The time-constant of the AGC. */
double _tau;
/** One over the time-constant. */
double _lambda;
/** The averaged std. dev. of the input signal. */
double _sd;
/** The target level of the output signal. */
double _target;
/** The current gain factor. */
double _gain;
/** The current sample-rate. */
double _sample_rate;
/** The output buffer. */
Buffer<Scalar> _buffer;
};
/** Keeps a copy of the last buffer received. */
template <class Scalar>
class DebugStore: public Sink<Scalar>
{
public:
/** Constrctor. */
DebugStore() : Sink<Scalar>(), _buffer(), _view() { }
virtual ~DebugStore() {
_buffer.unref();
}
/** Configures the node. */
virtual void config(const Config &src_cfg) {
// Requires type and buffer size
if (!src_cfg.hasType() || !src_cfg.hasBufferSize()) { return; }
// Check type
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure DebugStore node: Invalid input type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Allocate buffer
_buffer = Buffer<Scalar>(src_cfg.bufferSize());
}
/** Stores the given buffer. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
size_t N = std::min(buffer.size(), _buffer.size());
memcpy(_buffer.ptr(), buffer.data(), N*sizeof(Scalar));
// Store view
_view = _buffer.head(N);
}
/** Retunrs a reference to the last received buffer. */
inline const Buffer<Scalar> &buffer() const { return _view; }
/** Clears the buffer. */
inline void clear() { _view = Buffer<Scalar>(); }
public:
/** A pre-allocated buffer, that will hold the last data received. */
Buffer<Scalar> _buffer;
/** A view to the last data received. */
Buffer<Scalar> _view;
};
/** Dumps buffers in a human readable form. */
template <class Scalar>
class DebugDump: public Sink<Scalar>
{
public:
/** Constructor. */
DebugDump(std::ostream &stream=std::cerr)
: Sink<Scalar>(), _stream(stream)
{
// pass...
}
/** Destructor. */
virtual ~DebugDump() {
// pass...
}
/** Configures the dump-node. */
virtual void config(const Config &src_cfg) {
// Requires type
if (!src_cfg.hasType()) { return; }
// check type
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure DebugDump sink: Invalid input type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Done...
}
/** Dumps the received buffer. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
_stream << buffer << std::endl;
}
protected:
/** A reference to the output stream. */
std::ostream &_stream;
};
/** A Gaussian White Noise source. */
template <class Scalar>
class GWNSource: public Source
{
public:
/** Constructor. */
GWNSource(double sample_rate, size_t buffer_size=1024)
: Source(), _sample_rate(sample_rate), _buffer_size(buffer_size), _buffer(_buffer_size),
_mean(0)
{
// Seed RNG
std::srand(std::time(0));
// Determine mean
switch (Config::typeId<Scalar>()) {
case Config::Type_u8:
case Config::Type_cu8:
case Config::Type_u16:
case Config::Type_cu16:
_mean = 1;
break;
default:
_mean = 0;
break;
}
// Propergate config
setConfig(Config(Config::typeId<Scalar>(), _sample_rate, _buffer_size, 1));
}
/** Destructor. */
virtual ~GWNSource() {
_buffer.unref();
}
/** Samples and emits the next chunk of data. This function can be connected to the idle event
* of the @c Queue instance, such that a new chunk gets emitted, once all previous chunks are
* processed. */
void next() {
double a,b;
for (size_t i=0; i<_buffer_size; i+=2) {
_getNormal(a,b);
_buffer[i] = Scalar(Traits<Scalar>::scale*(a+_mean));
_buffer[i+1] = Scalar(Traits<Scalar>::scale*(b+_mean));
}
if (_buffer_size%2) {
_getNormal(a,b); _buffer[_buffer_size-1] = Scalar(Traits<Scalar>::scale*(a+_mean));
}
this->send(_buffer, true);
}
protected:
/** Sample two std. normal distributed RVs. */
void _getNormal(double &a, double &b) {
// Obtain pair of std. normal floating point values
double x = 2*(double(std::rand())/RAND_MAX) - 1;
double y = 2*(double(std::rand())/RAND_MAX) - 1;
double s = std::sqrt(x*x + y*y);
while (s >= 1) {
x = 2*(double(std::rand())/RAND_MAX) - 1;
y = 2*(double(std::rand())/RAND_MAX) - 1;
s = std::sqrt(x*x + y*y);
}
a = x*std::sqrt(-2*log(s)/s);
b = y*std::sqrt(-2*log(s)/s);
}
protected:
/** The sample rate. */
double _sample_rate;
/** The size of the buffer. */
size_t _buffer_size;
/** The output buffer. */
Buffer<Scalar> _buffer;
/** The mean value of the GWN. */
double _mean;
};
}
#endif // __SDR_UTILS_HH__

@ -0,0 +1,235 @@
#include "wavfile.hh"
#include "config.hh"
#include <cstring>
using namespace sdr;
WavSource::WavSource(size_t buffer_size)
: Source(), _file(), _buffer(), _buffer_size(buffer_size),
_frame_count(0), _type(Config::Type_UNDEFINED), _sample_rate(0), _frames_left(0)
{
// pass..
}
WavSource::WavSource(const std::string &filename, size_t buffer_size)
: Source(), _file(), _buffer(), _buffer_size(buffer_size),
_frame_count(0), _type(Config::Type_UNDEFINED), _sample_rate(0), _frames_left(0)
{
open(filename);
}
WavSource::~WavSource() {
_file.close();
}
bool
WavSource::isOpen() const {
return _file.is_open();
}
void
WavSource::open(const std::string &filename)
{
if (_file.is_open()) { _file.close(); }
_file.open(filename.c_str(), std::ios_base::in);
if (! _file.is_open()) { return; }
// Read header and configure source
char str[5]; str[4] = 0;
uint16_t val2; uint32_t val4;
uint16_t n_chanels;
uint32_t sample_rate;
uint16_t block_align, bits_per_sample;
uint32_t chunk_offset, chunk_size;
/*
* Read Header
*/
chunk_offset = 0;
_file.read(str, 4);
if (0 != strncmp(str, "RIFF", 4)) {
RuntimeError err;
err << "File '" << filename << "' is not a WAV file.";
throw err;
}
_file.read((char *)&chunk_size, 4); // Read file-size (unused)
_file.read(str, 4); // Read "WAVE" (unused)
if (0 != strncmp(str, "WAVE", 4)) {
RuntimeError err;
err << "File '" << filename << "' is not a WAV file.";
throw err;
}
/*
* Read Format part
*/
chunk_offset = 12;
_file.read(str, 4); // Read "fmt " (unused)
if (0 != strncmp(str, "fmt ", 4)) {
RuntimeError err;
err << "'File 'fmt' header missing in file " << filename << "' @" << _file.tellg();
throw err;
}
_file.read((char *)&chunk_size, 4); // Subheader size (unused)
_file.read((char *)&val2, 2); // Format
if (1 != val2) {
RuntimeError err;
err << "Unsupported WAV data format: " << val2
<< " of file " << filename << ". Expected " << 1;
throw err;
}
_file.read((char *)&n_chanels, 2); // Read # chanels
if ((1 != n_chanels) && (2 != n_chanels)) {
RuntimeError err;
err << "Unsupported number of chanels: " << n_chanels
<< " of file " << filename << ". Expected 1 or 2.";
throw err;
}
_file.read((char *)&sample_rate, 4); // Read sample-rate
_file.read((char *)&val4, 4); // Read byte-rate (unused)
_file.read((char *)&block_align, 2);
_file.read((char *)&bits_per_sample, 2);
// Check sample format
if ((16 != bits_per_sample) && (8 != bits_per_sample)){
RuntimeError err;
err << "Unsupported sample format: " << bits_per_sample
<< "b of file " << filename << ". Expected 16b or 8b.";
throw err;
}
if (block_align != n_chanels*(bits_per_sample/8)) {
RuntimeError err;
err << "Unsupported alignment: " << block_align
<< "byte of file " << filename << ". Expected " << (bits_per_sample/8) << "byte.";
throw err;
}
// Seek to end of header
chunk_offset += 8+chunk_size;
_file.seekg(chunk_offset);
// Search for data chunk
_file.read(str, 4);
while ((0 != strncmp(str, "data", 4)) || _file.eof()) {
// read chunk size
_file.read((char*)&chunk_size, 4);
chunk_offset += (8+chunk_size);
_file.seekg(chunk_offset);
_file.read(str, 4);
}
if (_file.eof()) {
RuntimeError err;
err << "WAV file '" << filename << "' contains no 'data' chunk.";
throw err;
}
/*
* Read data part.
*/
_file.read((char *)&chunk_size, 4); // read frame count
// Configure source
_frame_count = chunk_size/(2*n_chanels);
if ((1 == n_chanels) && (8 == bits_per_sample)) { _type = Config::Type_u8; }
else if ((1==n_chanels) && (16 == bits_per_sample)) { _type = Config::Type_u16; }
else if ((2==n_chanels) && ( 8 == bits_per_sample)) { _type = Config::Type_cu8; }
else if ((2==n_chanels) && (16 == bits_per_sample)) { _type = Config::Type_cu16; }
else {
ConfigError err; err << "Can not configure WavSource: Unsupported PCM type."; throw err;
}
_sample_rate = sample_rate;
_frames_left = _frame_count;
#ifdef SDR_DEBUG
std::cerr << "Configured WavSource:" << std::endl
<< " file: " << filename << 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
<< " buffer-size: " << _buffer_size << std::endl;
#endif
// unreference buffer if not empty
if (! _buffer.isEmpty()) { _buffer.unref(); }
// Allocate buffer and propergate config
switch (_type) {
case Config::Type_u8:
_buffer = Buffer<uint8_t>(_buffer_size);
this->setConfig(Config(Config::Type_u8, _sample_rate, _buffer_size, 1));
break;
case Config::Type_u16:
_buffer = Buffer<uint16_t>(_buffer_size);
this->setConfig(Config(Config::Type_u16, _sample_rate, _buffer_size, 1));
break;
case Config::Type_cu8:
_buffer = Buffer< std::complex<uint8_t> >(_buffer_size);
this->setConfig(Config(Config::Type_cu8, _sample_rate, _buffer_size, 1));
break;
case Config::Type_cu16:
_buffer = Buffer< std::complex<uint16_t> >(_buffer_size);
this->setConfig(Config(Config::Type_cu16, _sample_rate, _buffer_size, 1));
break;
default: {
ConfigError err; err << "Can not configure WavSource: Unsupported PCM type."; throw err;
}
}
}
void
WavSource::close() {
_file.close(); _frames_left = 0;
}
bool
WavSource::isReal() const {
return (Config::Type_u8 == _type) || (Config::Type_u16 == _type);
}
void
WavSource::next() {
if ((0 == _frames_left)) {
// Close file
_file.close();
// and signal queue to stop
Queue::get().stop();
return;
}
// Determine the number of frames to read
size_t n_frames = std::min(_frames_left, _buffer_size);
switch (_type) {
case Config::Type_u8:
_file.read(_buffer.ptr(), n_frames*sizeof(uint8_t));
_frames_left -= n_frames;
this->send(RawBuffer(_buffer, 0, n_frames*sizeof(uint8_t)), true);
break;
case Config::Type_u16:
_file.read(_buffer.ptr(), n_frames*sizeof(uint16_t));
_frames_left -= n_frames;
this->send(RawBuffer(_buffer, 0, n_frames*sizeof(uint16_t)), true);
break;
case Config::Type_cu8:
_file.read(_buffer.ptr(), 2*n_frames*sizeof(uint8_t));
_frames_left -= n_frames;
this->send(RawBuffer(_buffer, 0, 2*n_frames*sizeof(uint8_t)), true);
break;
case Config::Type_cu16:
_file.read(_buffer.ptr(), 2*n_frames*sizeof(uint16_t));
_frames_left -= n_frames;
this->send(RawBuffer(_buffer, 0, 2*n_frames*sizeof(uint16_t)), true);
break;
default:
break;
}
}

@ -0,0 +1,174 @@
#ifndef __SDR_WAVFILE_HH__
#define __SDR_WAVFILE_HH__
#include "node.hh"
#include <fstream>
namespace sdr {
/** Stores the received buffers into a WAV file. */
template <class Scalar>
class WavSink: public Sink<Scalar>
{
public:
/** Constructor, @c filename specifies the file name, the WAV data is stored into.
* @throws ConfigError If the specified file can not be opened for output. */
WavSink(const std::string &filename)
: Sink<Scalar>(), _file(filename.c_str(), std::ios_base::out|std::ios_base::binary),
_frameCount(0), _sampleRate(0)
{
if (!_file.is_open()) {
ConfigError err;
err << "Can not open wav file for output: " << filename;
throw err;
}
// Fill first 36+8 bytes with 0, the headers are written once close() gets called.
for (size_t i=0; i<44; i++) { _file.write("\x00", 1); }
// check format
switch (Config::typeId<Scalar>()) {
case Config::Type_u8:
case Config::Type_s8:
_bitsPerSample = 8;
_numChanels = 1;
break;
case Config::Type_cu8:
case Config::Type_cs8:
_bitsPerSample = 8;
_numChanels = 2;
break;
case Config::Type_u16:
case Config::Type_s16:
_bitsPerSample = 16;
_numChanels = 1;
break;
case Config::Type_cu16:
case Config::Type_cs16:
_bitsPerSample = 16;
_numChanels = 2;
break;
default:
ConfigError err;
err << "WAV format only allows (real) integer typed data.";
throw err;
}
}
/** Destructor, closes the file if not done yet. */
virtual ~WavSink() {
if (_file.is_open()) {
this->close();
}
}
/** Configures the sink. */
virtual void config(const Config &src_cfg) {
// Requires type, samplerate
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Check if type matches
if (Config::typeId<Scalar>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure WavSink: Invalid buffer type " << src_cfg.type()
<< ", expected " << Config::typeId<Scalar>();
throw err;
}
// Store sample rate
_sampleRate = src_cfg.sampleRate();
}
/** Completes the WAV header and closes the file. */
void close() {
if (! _file.is_open()) { return; }
uint32_t val4;
uint16_t val2;
_file.seekp(0);
_file.write("RIFF", 4);
val4 = (uint32_t)(36u+2u*_frameCount); _file.write((char *)&val4, 4);
_file.write("WAVE", 4);
_file.write("fmt ", 4);
val4 = 16; _file.write((char *)&val4, 4); // sub header size = 16
val2 = 1; _file.write((char *)&val2, 2); // format PCM = 1
_file.write((char *)&_numChanels, 2); // num chanels = 1
_file.write((char *)&_sampleRate, 4);
val4 = _numChanels*_sampleRate*(_bitsPerSample/8);
_file.write((char *)&val4, 4); // byte rate
val2 = _numChanels*(_bitsPerSample/8);
_file.write((char *)&val2, 2); // block align
_file.write((char *)&_bitsPerSample, 2); // bits per sample
_file.write("data", 4);
val4 = _numChanels*_frameCount*(_bitsPerSample/8); _file.write((char *)&val4, 4);
_file.close();
}
/** Writes some data into the WAV file. */
virtual void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
if (! _file.is_open()) { return; }
_file.write(buffer.data(), buffer.size()*sizeof(Scalar));
_frameCount += buffer.size();
}
protected:
/** The file output stream. */
std::fstream _file;
/** The number of bits per sample (depends on the template type). */
uint16_t _bitsPerSample;
/** The total number of frame counts. */
uint32_t _frameCount;
/** The sample rate. */
uint32_t _sampleRate;
/** The number of chanels. */
uint16_t _numChanels;
};
/** 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. */
class WavSource: public Source
{
public:
/** Constructor, @c buffer_size specified the output buffer size. */
WavSource(size_t buffer_size=1024);
/** Constructor with file name, @c buffer_size specified the output buffer size. */
WavSource(const std::string &filename, size_t buffer_size=1024);
/** Destructor. */
virtual ~WavSource();
/** Returns @c true if the file is open. */
bool isOpen() const;
/** Open a new file. */
void open(const std::string &filename);
/** Close the current file. */
void close();
/** Returns true, if the input is real (stereo files are handled as I/Q signals). */
bool isReal() const;
/** Read the next data. */
void next();
protected:
/** The input file stream. */
std::fstream _file;
/** The output buffer. */
RawBuffer _buffer;
/** The current buffer size. */
size_t _buffer_size;
/** The number of available frames. */
size_t _frame_count;
/** The type of the data in the WAV file. */
Config::Type _type;
/** The sample rate. */
double _sample_rate;
/** The number of frames left to be read. */
size_t _frames_left;
};
}
#endif // __SDR_WAVFILE_HH__
Loading…
Cancel
Save