LimeSDR output (1)

This commit is contained in:
f4exb 2017-04-22 06:40:12 +02:00
parent 4ae889c06a
commit f447c9f9bd
10 changed files with 2487 additions and 0 deletions

View File

@ -0,0 +1,55 @@
project(limesdroutput)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(limesdroutput_SOURCES
limesdroutputgui.cpp
limesdroutput.cpp
limesdroutputplugin.cpp
limesdroutputsettings.cpp
limesdroutputthread.cpp
)
set(limesdroutput_HEADERS
limesdroutputgui.h
limesdroutput.h
limesdroutputplugin.h
limesdroutputsettings.h
limesdroutputthread.h
)
set(limesdroutput_FORMS
limesdroutputgui.ui
)
include_directories(
.
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/devices
${LIMESUITE_INCLUDE_DIR}
)
#include(${QT_USE_FILE})
add_definitions(${QT_DEFINITIONS})
add_definitions(-DQT_PLUGIN)
add_definitions(-DQT_SHARED)
#qt4_wrap_cpp(limesdroutput_HEADERS_MOC ${limesdroutput_HEADERS})
qt5_wrap_ui(limesdroutput_FORMS_HEADERS ${limesdroutput_FORMS})
add_library(outputlimesdr SHARED
${limesdroutput_SOURCES}
${limesdroutput_HEADERS_MOC}
${limesdroutput_FORMS_HEADERS}
)
target_link_libraries(outputlimesdr
${QT_LIBRARIES}
${LIMESUITE_LIBRARY}
sdrbase
limesdrdevice
)
qt5_use_modules(inputlimesdr Core Widgets)
install(TARGETS inputlimesdr DESTINATION lib/plugins/samplesource)

View File

@ -0,0 +1,845 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// //
// 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 as version 3 of the License, or //
// //
// 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QMutexLocker>
#include <QDebug>
#include <cstddef>
#include <string.h>
#include "lime/LimeSuite.h"
#include "device/devicesourceapi.h"
#include "device/devicesinkapi.h"
#include "dsp/dspcommands.h"
#include "limesdrinputthread.h"
#include "limesdr/devicelimesdrparam.h"
#include "limesdr/devicelimesdr.h"
#include "limesdroutput.h"
MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgConfigureLimeSDR, Message)
MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgGetStreamInfo, Message)
MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgSetReferenceConfig, Message)
MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgReportLimeSDRToGUI, Message)
MESSAGE_CLASS_DEFINITION(LimeSDROutput::MsgReportStreamInfo, Message)
LimeSDROutput::LimeSDROutput(DeviceSinkAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_settings(),
m_limeSDROutputThread(0),
m_deviceDescription(),
m_running(false),
m_firstConfig(true)
{
m_streamId.handle = 0;
openDevice();
}
LimeSDROutput::~LimeSDROutput()
{
if (m_running) stop();
closeDevice();
}
bool LimeSDROutput::openDevice()
{
// look for Tx buddies and get reference to common parameters
// if there is a channel left take the first available
if (m_deviceAPI->getSinkBuddies().size() > 0) // look sink sibling first
{
qDebug("LimeSDROutput::openDevice: look in Ix buddies");
DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0];
m_deviceShared = *((DeviceLimeSDRShared *) sinkBuddy->getBuddySharedPtr()); // copy shared data
DeviceLimeSDRParams *deviceParams = m_deviceShared.m_deviceParams; // get device parameters
if (deviceParams == 0)
{
qCritical("LimeSDROutput::openDevice: cannot get device parameters from Tx buddy");
return false; // the device params should have been created by the buddy
}
else
{
qDebug("LimeSDROutput::openDevice: getting device parameters from Tx buddy");
}
if (m_deviceAPI->getSinkBuddies().size() == deviceParams->m_nbTxChannels)
{
qCritical("LimeSDROutput::openDevice: no more Tx channels available in device");
return false; // no more Tx channels available in device
}
else
{
qDebug("LimeSDROutput::openDevice: at least one more Tx channel is available in device");
}
// look for unused channel number
char *busyChannels = new char[deviceParams->m_nbTxChannels];
memset(busyChannels, 0, deviceParams->m_nbTxChannels);
for (int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++)
{
DeviceSinkAPI *buddy = m_deviceAPI->getSinkBuddies()[i];
DeviceLimeSDRShared *buddyShared = (DeviceLimeSDRShared *) buddy->getBuddySharedPtr();
busyChannels[buddyShared->m_channel] = 1;
if (buddyShared->m_thread) { // suspend Tx buddy's thread for proper stream allocation later
((LimeSDROutputThread *) buddyShared->m_thread)->stopWork();
}
}
std::size_t ch = 0;
for (;ch < deviceParams->m_nbTxChannels; ch++)
{
if (busyChannels[ch] == 0) {
break; // first available is the good one
}
}
m_deviceShared.m_channel = ch;
delete[] busyChannels;
}
// look for Rx buddies and get reference to common parameters
// take the first Rx channel
else if (m_deviceAPI->getSourceBuddies().size() > 0) // then source
{
qDebug("LimeSDROutput::openDevice: look in Rx buddies");
DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0];
m_deviceShared = *((DeviceLimeSDRShared *) sourceBuddy->getBuddySharedPtr()); // copy parameters
if (m_deviceShared.m_deviceParams == 0)
{
qCritical("LimeSDROutput::openDevice: cannot get device parameters from Rx buddy");
return false; // the device params should have been created by the buddy
}
else
{
qDebug("LimeSDROutput::openDevice: getting device parameters from Rx buddy");
}
m_deviceShared.m_channel = 0; // take first channel
}
// There are no buddies then create the first LimeSDR common parameters
// open the device this will also populate common fields
// take the first Tx channel
else
{
qDebug("LimeSDROutput::openDevice: open device here");
m_deviceShared.m_deviceParams = new DeviceLimeSDRParams();
char serial[256];
strcpy(serial, qPrintable(m_deviceAPI->getSampleSinkSerial()));
m_deviceShared.m_deviceParams->open(serial);
m_deviceShared.m_channel = 0; // take first channel
}
m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API
// acquire the channel
if (LMS_EnableChannel(m_deviceShared.m_deviceParams->getDevice(), LMS_CH_TX, m_deviceShared.m_channel, true) != 0)
{
qCritical("LimeSDROutput::openDevice: cannot enable Tx channel %lu", m_deviceShared.m_channel);
return false;
}
else
{
qDebug("LimeSDROutput::openDevice: Tx channel %lu enabled", m_deviceShared.m_channel);
}
// set up the stream
m_streamId.channel = m_deviceShared.m_channel; // channel number
m_streamId.fifoSize = 1024 * 128; // fifo size in samples
m_streamId.throughputVsLatency = 1.0; // optimize for max throughput
m_streamId.isTx = true; // TX channel
m_streamId.dataFmt = lms_stream_t::LMS_FMT_I12; // 12-bit integers
if (LMS_SetupStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId) != 0)
{
qCritical("LimeSDROutput::start: cannot setup the stream on Tx channel %lu", m_deviceShared.m_channel);
return false;
}
else
{
qDebug("LimeSDROutput::start: stream set up on Tx channel %lu", m_deviceShared.m_channel);
}
// resume Tx buddy's threads
for (int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++)
{
DeviceSinkAPI *buddy = m_deviceAPI->getSinkBuddies()[i];
DeviceLimeSDRShared *buddyShared = (DeviceLimeSDRShared *) buddy->getBuddySharedPtr();
if (buddyShared->m_thread) {
((LimeSDROutputThread *) buddyShared->m_thread)->startWork();
}
}
return true;
}
void LimeSDROutput::closeDevice()
{
if (m_deviceShared.m_deviceParams->getDevice() == 0) { // was never open
return;
}
// suspend Tx buddy's threads
for (int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++)
{
DeviceSinkAPI *buddy = m_deviceAPI->getSinkBuddies()[i];
DeviceLimeSDRShared *buddyShared = (DeviceLimeSDRShared *) buddy->getBuddySharedPtr();
if (buddyShared->m_thread) {
((LimeSDROutputThread *) buddyShared->m_thread)->stopWork();
}
}
// destroy the stream
LMS_DestroyStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId);
m_streamId.handle = 0;
// release the channel
if (LMS_EnableChannel(m_deviceShared.m_deviceParams->getDevice(), LMS_CH_TX, m_deviceShared.m_channel, false) != 0)
{
qWarning("LimeSDROutput::closeDevice: cannot disable Tx channel %lu", m_deviceShared.m_channel);
}
m_deviceShared.m_channel = -1;
// resume Tx buddy's threads
for (int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++)
{
DeviceSourceAPI *buddy = m_deviceAPI->getSinkBuddies()[i];
DeviceLimeSDRShared *buddyShared = (DeviceLimeSDRShared *) buddy->getBuddySharedPtr();
if (buddyShared->m_thread) {
((LimeSDROutputThread *) buddyShared->m_thread)->startWork();
}
}
// No buddies so effectively close the device
if ((m_deviceAPI->getSourceBuddies().size() == 0) && (m_deviceAPI->getSinkBuddies().size() == 0))
{
m_deviceShared.m_deviceParams->close();
delete m_deviceShared.m_deviceParams;
m_deviceShared.m_deviceParams = 0;
}
}
bool LimeSDROutput::start()
{
if (!m_deviceShared.m_deviceParams->getDevice()) {
return false;
}
if (m_running) stop();
// start / stop streaming is done in the thread.
if ((m_limeSDROutputThread = new LimeSDROutputThread(&m_streamId, &m_sampleSourceFifo)) == 0)
{
qFatal("LimeSDROutput::start: cannot create thread");
stop();
return false;
}
else
{
qDebug("LimeSDROutput::start: thread created");
}
m_limeSDROutputThread->setLog2Interpolation(m_settings.m_log2SoftInterp);
m_limeSDROutputThread->startWork();
m_deviceShared.m_thread = (void *) m_limeSDROutputThread;
m_running = true;
return true;
}
void LimeSDROutput::stop()
{
if (m_limeSDROutputThread != 0)
{
m_limeSDROutputThread->stopWork();
delete m_limeSDROutputThread;
m_limeSDROutputThread = 0;
}
m_deviceShared.m_thread = 0;
m_running = false;
}
const QString& LimeSDROutput::getDeviceDescription() const
{
return m_deviceDescription;
}
int LimeSDROutput::getSampleRate() const
{
int rate = m_settings.m_devSampleRate;
return (rate / (1<<m_settings.m_log2SoftInterp));
}
quint64 LimeSDROutput::getCenterFrequency() const
{
return m_settings.m_centerFrequency;
}
std::size_t LimeSDROutput::getChannelIndex()
{
return m_deviceShared.m_channel;
}
void LimeSDROutput::getLORange(float& minF, float& maxF, float& stepF) const
{
lms_range_t range = m_deviceShared.m_deviceParams->m_loRangeTx;
minF = range.min;
maxF = range.max;
stepF = range.step;
qDebug("LimeSDROutput::getLORange: min: %f max: %f step: %f", range.min, range.max, range.step);
}
void LimeSDROutput::getSRRange(float& minF, float& maxF, float& stepF) const
{
lms_range_t range = m_deviceShared.m_deviceParams->m_srRangeTx;
minF = range.min;
maxF = range.max;
stepF = range.step;
qDebug("LimeSDROutput::getSRRange: min: %f max: %f step: %f", range.min, range.max, range.step);
}
void LimeSDROutput::getLPRange(float& minF, float& maxF, float& stepF) const
{
lms_range_t range = m_deviceShared.m_deviceParams->m_lpfRangeTx;
minF = range.min;
maxF = range.max;
stepF = range.step;
qDebug("LimeSDROutput::getLPRange: min: %f max: %f step: %f", range.min, range.max, range.step);
}
uint32_t LimeSDROutput::getHWLog2Interp() const
{
return m_deviceShared.m_deviceParams->m_log2OvSRTx;
}
bool LimeSDROutput::handleMessage(const Message& message)
{
if (MsgConfigureLimeSDR::match(message))
{
MsgConfigureLimeSDR& conf = (MsgConfigureLimeSDR&) message;
qDebug() << "LimeSDROutput::handleMessage: MsgConfigureLimeSDR";
if (!applySettings(conf.getSettings(), m_firstConfig))
{
qDebug("LimeSDROutput::handleMessage config error");
}
else
{
m_firstConfig = false;
}
return true;
}
else if (MsgSetReferenceConfig::match(message))
{
MsgSetReferenceConfig& conf = (MsgSetReferenceConfig&) message;
qDebug() << "LimeSDROutput::handleMessage: MsgSetReferenceConfig";
m_settings = conf.getSettings();
m_deviceShared.m_ncoFrequency = m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0; // for buddies
m_deviceShared.m_centerFrequency = m_settings.m_centerFrequency; // for buddies
return true;
}
else if (MsgGetStreamInfo::match(message))
{
// qDebug() << "LimeSDROutput::handleMessage: MsgGetStreamInfo";
lms_stream_status_t status;
if (m_streamId.handle && (LMS_GetStreamStatus(&m_streamId, &status) == 0))
{
MsgReportStreamInfo *report = MsgReportStreamInfo::create(
true, // Success
status.active,
status.fifoFilledCount,
status.fifoSize,
status.underrun,
status.overrun,
status.droppedPackets,
status.sampleRate,
status.linkRate,
status.timestamp);
m_deviceAPI->getDeviceOutputMessageQueue()->push(report);
}
else
{
MsgReportStreamInfo *report = MsgReportStreamInfo::create(
false, // Success
false, // status.active,
0, // status.fifoFilledCount,
16384, // status.fifoSize,
0, // status.underrun,
0, // status.overrun,
0, // status.droppedPackets,
0, // status.sampleRate,
0, // status.linkRate,
0); // status.timestamp);
m_deviceAPI->getDeviceOutputMessageQueue()->push(report);
}
return true;
}
else
{
return false;
}
}
bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool force)
{
bool forwardChangeOwnDSP = false;
bool forwardChangeTxDSP = false;
bool forwardChangeAllDSP = false;
bool suspendOwnThread = false;
bool suspendTxThread = false;
bool suspendAllThread = false;
bool doCalibration = false;
// QMutexLocker mutexLocker(&m_mutex);
// determine if buddies threads or own thread need to be suspended
if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force)
{
suspendAllThread = true;
}
if ((m_settings.m_log2HardInterp != settings.m_log2HardInterp) ||
(m_settings.m_centerFrequency != settings.m_centerFrequency) || force)
{
suspendTxThread = true;
}
if ((m_settings.m_gain != settings.m_gain) ||
(m_settings.m_lpfBW != settings.m_lpfBW) ||
(m_settings.m_lpfFIRBW != settings.m_lpfFIRBW) ||
(m_settings.m_lpfFIREnable != settings.m_lpfFIREnable) ||
(m_settings.m_ncoEnable != settings.m_ncoEnable) ||
(m_settings.m_ncoFrequency != settings.m_ncoFrequency) || force)
{
suspendOwnThread = true;
}
// suspend buddies threads or own thread
if (suspendAllThread)
{
const std::vector<DeviceSourceAPI*>& sourceBuddies = m_deviceAPI->getSourceBuddies();
std::vector<DeviceSourceAPI*>::const_iterator itSource = sourceBuddies.begin();
for (; itSource != sourceBuddies.end(); ++itSource)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSource)->getBuddySharedPtr();
if (buddySharedPtr->m_thread)
{
((LimeSDRInputThread *) buddySharedPtr->m_thread)->stopWork();
}
}
const std::vector<DeviceSinkAPI*>& sinkBuddies = m_deviceAPI->getSinkBuddies();
std::vector<DeviceSinkAPI*>::const_iterator itSink = sinkBuddies.begin();
for (; itSink != sinkBuddies.end(); ++itSink)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
if (buddySharedPtr->m_thread)
{
((LimeSDROutputThread *) buddySharedPtr->m_thread)->stopWork();
}
}
if (m_limeSDROutputThread) {
m_limeSDROutputThread->stopWork();
}
}
else if (suspendTxThread)
{
const std::vector<DeviceSinkAPI*>& sinkBuddies = m_deviceAPI->getSinkBuddies();
std::vector<DeviceSinkAPI*>::const_iterator itSink = sinkBuddies.begin();
for (; itSink != sinkBuddies.end(); ++itSink)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
if (buddySharedPtr->m_thread)
{
((LimeSDROutputThread *) buddySharedPtr->m_thread)->stopWork();
}
}
if (m_limeSDROutputThread) {
m_limeSDROutputThread->stopWork();
}
}
else if (suspendOwnThread)
{
if (m_limeSDROutputThread) {
m_limeSDROutputThread->stopWork();
}
}
// apply settings
if ((m_settings.m_gain != settings.m_gain) || force)
{
m_settings.m_gain = settings.m_gain;
if (m_deviceShared.m_deviceParams->getDevice() != 0)
{
if (LMS_SetGaindB(m_deviceShared.m_deviceParams->getDevice(),
LMS_CH_TX,
m_deviceShared.m_channel,
m_settings.m_gain) < 0)
{
qDebug("LimeSDROutput::applySettings: LMS_SetGaindB() failed");
}
else
{
doCalibration = true;
qDebug() << "LimeSDROutput::applySettings: Gain set to " << m_settings.m_gain;
}
}
}
if ((m_settings.m_devSampleRate != settings.m_devSampleRate)
|| (m_settings.m_log2HardInterp != settings.m_log2HardInterp) || force)
{
forwardChangeTxDSP = m_settings.m_log2HardInterp != settings.m_log2HardInterp;
forwardChangeAllDSP = m_settings.m_devSampleRate != settings.m_devSampleRate;
m_settings.m_devSampleRate = settings.m_devSampleRate;
m_settings.m_log2HardInterp = settings.m_log2HardInterp;
if (m_deviceShared.m_deviceParams->getDevice() != 0)
{
if (LMS_SetSampleRateDir(m_deviceShared.m_deviceParams->getDevice(),
LMS_CH_TX,
m_settings.m_devSampleRate,
1<<m_settings.m_log2HardInterp) < 0)
{
qCritical("LimeSDROutput::applySettings: could not set sample rate to %d with oversampling of %d",
m_settings.m_devSampleRate,
1<<m_settings.m_log2HardInterp);
}
else
{
m_deviceShared.m_deviceParams->m_log2OvSRTx = m_settings.m_log2HardInterp;
m_deviceShared.m_deviceParams->m_sampleRate = m_settings.m_devSampleRate;
doCalibration = true;
qDebug("LimeSDROutput::applySettings: set sample rate set to %d with oversampling of %d",
m_settings.m_devSampleRate,
1<<m_settings.m_log2HardInterp);
}
}
}
if ((m_settings.m_lpfBW != settings.m_lpfBW) || force)
{
m_settings.m_lpfBW = settings.m_lpfBW;
if (m_deviceShared.m_deviceParams->getDevice() != 0)
{
if (LMS_SetLPFBW(m_deviceShared.m_deviceParams->getDevice(),
LMS_CH_TX,
m_deviceShared.m_channel,
m_settings.m_lpfBW) < 0)
{
qCritical("LimeSDROutput::applySettings: could not set LPF to %f Hz", m_settings.m_lpfBW);
}
else
{
doCalibration = true;
qDebug("LimeSDROutput::applySettings: LPF set to %f Hz", m_settings.m_lpfBW);
}
}
}
if ((m_settings.m_lpfFIRBW != settings.m_lpfFIRBW) ||
(m_settings.m_lpfFIREnable != settings.m_lpfFIREnable) || force)
{
m_settings.m_lpfFIRBW = settings.m_lpfFIRBW;
m_settings.m_lpfFIREnable = settings.m_lpfFIREnable;
if (m_deviceShared.m_deviceParams->getDevice() != 0)
{
if (LMS_SetGFIRLPF(m_deviceShared.m_deviceParams->getDevice(),
LMS_CH_TX,
m_deviceShared.m_channel,
m_settings.m_lpfFIREnable,
m_settings.m_lpfFIRBW) < 0)
{
qCritical("LimeSDROutput::applySettings: could %s and set LPF FIR to %f Hz",
m_settings.m_lpfFIREnable ? "enable" : "disable",
m_settings.m_lpfFIRBW);
}
else
{
doCalibration = true;
qDebug("LimeSDROutput::applySettings: %sd and set LPF FIR to %f Hz",
m_settings.m_lpfFIREnable ? "enable" : "disable",
m_settings.m_lpfFIRBW);
}
}
}
if ((m_settings.m_ncoFrequency != settings.m_ncoFrequency) ||
(m_settings.m_ncoEnable != settings.m_ncoEnable) || force)
{
m_settings.m_ncoFrequency = settings.m_ncoFrequency;
m_settings.m_ncoEnable = settings.m_ncoEnable;
if (m_deviceShared.m_deviceParams->getDevice() != 0)
{
if (DeviceLimeSDR::setNCOFrequency(m_deviceShared.m_deviceParams->getDevice(),
LMS_CH_TX,
m_deviceShared.m_channel,
m_settings.m_ncoEnable,
m_settings.m_ncoFrequency))
{
doCalibration = true;
forwardChangeOwnDSP = true;
m_deviceShared.m_ncoFrequency = m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0; // for buddies
qDebug("LimeSDROutput::applySettings: %sd and set NCO to %d Hz",
m_settings.m_ncoEnable ? "enable" : "disable",
m_settings.m_ncoFrequency);
}
else
{
qCritical("LimeSDROutput::applySettings: could not %s and set NCO to %d Hz",
m_settings.m_ncoEnable ? "enable" : "disable",
m_settings.m_ncoFrequency);
}
}
}
if ((m_settings.m_log2SoftInterp != settings.m_log2SoftInterp) || force)
{
m_settings.m_log2SoftInterp = settings.m_log2SoftInterp;
forwardChangeOwnDSP = true;
if (m_limeSDROutputThread != 0)
{
m_limeSDROutputThread->setLog2Interpolation(m_settings.m_log2SoftInterp);
qDebug() << "LimeSDROutput::applySettings: set soft decimation to " << (1<<m_settings.m_log2SoftInterp);
}
}
if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || force)
{
m_settings.m_centerFrequency = settings.m_centerFrequency;
forwardChangeTxDSP = true;
if (m_deviceShared.m_deviceParams->getDevice() != 0)
{
if (LMS_SetLOFrequency(m_deviceShared.m_deviceParams->getDevice(),
LMS_CH_TX,
m_deviceShared.m_channel, // same for both channels anyway but switches antenna port automatically
m_settings.m_centerFrequency) < 0)
{
qCritical("LimeSDROutput::applySettings: could not set frequency to %lu", m_settings.m_centerFrequency);
}
else
{
doCalibration = true;
m_deviceShared.m_centerFrequency = m_settings.m_centerFrequency; // for buddies
qDebug("LimeSDROutput::applySettings: frequency set to %lu", m_settings.m_centerFrequency);
}
}
}
if (doCalibration)
{
if (LMS_Calibrate(m_deviceShared.m_deviceParams->getDevice(),
LMS_CH_TX,
m_deviceShared.m_channel,
m_settings.m_lpfBW,
0) < 0)
{
qCritical("LimeSDROutput::applySettings: calibration failed on Rx channel %lu", m_deviceShared.m_channel);
}
else
{
qDebug("LimeSDROutput::applySettings: calibration successful on Rx channel %lu", m_deviceShared.m_channel);
}
}
// resume buddies threads or own thread
if (suspendAllThread)
{
const std::vector<DeviceSourceAPI*>& sourceBuddies = m_deviceAPI->getSourceBuddies();
std::vector<DeviceSourceAPI*>::const_iterator itSource = sourceBuddies.begin();
for (; itSource != sourceBuddies.end(); ++itSource)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSource)->getBuddySharedPtr();
if (buddySharedPtr->m_thread)
{
((LimeSDRInputThread *) buddySharedPtr->m_thread)->startWork();
}
}
const std::vector<DeviceSinkAPI*>& sinkBuddies = m_deviceAPI->getSinkBuddies();
std::vector<DeviceSinkAPI*>::const_iterator itSink = sinkBuddies.begin();
for (; itSink != sinkBuddies.end(); ++itSink)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
if (buddySharedPtr->m_thread)
{
((LimeSDROutputThread *) buddySharedPtr->m_thread)->startWork();
}
}
if (m_limeSDROutputThread) {
m_limeSDROutputThread->startWork();
}
}
else if (suspendTxThread)
{
const std::vector<DeviceSinkAPI*>& sinkBuddies = m_deviceAPI->getSinkBuddies();
std::vector<DeviceSinkAPI*>::const_iterator itSink = sinkBuddies.begin();
for (; itSink != sinkBuddies.end(); ++itSink)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
if (buddySharedPtr->m_thread)
{
((LimeSDROutputThread *) buddySharedPtr->m_thread)->startWork();
}
}
if (m_limeSDROutputThread) {
m_limeSDROutputThread->startWork();
}
}
else if (suspendOwnThread)
{
if (m_limeSDROutputThread) {
m_limeSDROutputThread->startWork();
}
}
// forward changes to buddies or oneself
if (forwardChangeAllDSP)
{
qDebug("LimeSDROutput::applySettings: forward change to all buddies");
int sampleRate = m_settings.m_devSampleRate/(1<<m_settings.m_log2SoftInterp);
int ncoShift = m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0;
// send to self first
DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, m_settings.m_centerFrequency + ncoShift);
m_deviceAPI->getDeviceInputMessageQueue()->push(notif);
// send to sink buddies
const std::vector<DeviceSinkAPI*>& sinkBuddies = m_deviceAPI->getSinkBuddies();
std::vector<DeviceSinkAPI*>::const_iterator itSink = sinkBuddies.begin();
for (; itSink != sinkBuddies.end(); ++itSink)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
uint64_t buddyCenterFreq = buddySharedPtr->m_centerFrequency;
int buddyNCOFreq = buddySharedPtr->m_ncoFrequency;
DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, buddyCenterFreq + buddyNCOFreq); // do not change center frequency
(*itSink)->getDeviceInputMessageQueue()->push(notif);
MsgReportLimeSDRToGUI *report = MsgReportLimeSDRToGUI::create(
m_settings.m_centerFrequency,
m_settings.m_devSampleRate,
m_settings.m_log2HardInterp);
(*itSink)->getDeviceOutputMessageQueue()->push(report);
}
// send to source buddies
const std::vector<DeviceSourceAPI*>& sourceBuddies = m_deviceAPI->getSourceBuddies();
std::vector<DeviceSourceAPI*>::const_iterator itSource = sourceBuddies.begin();
for (; itSource != sourceBuddies.end(); ++itSource)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSource)->getBuddySharedPtr();
int buddyNCOFreq = buddySharedPtr->m_ncoFrequency;
DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, m_settings.m_centerFrequency + buddyNCOFreq);
(*itSource)->getDeviceInputMessageQueue()->push(notif);
MsgReportLimeSDRToGUI *report = MsgReportLimeSDRToGUI::create(
m_settings.m_centerFrequency,
m_settings.m_devSampleRate,
m_settings.m_log2HardInterp);
(*itSource)->getDeviceOutputMessageQueue()->push(report);
}
}
else if (forwardChangeTxDSP)
{
qDebug("LimeSDROutput::applySettings: forward change to Tx buddies");
int sampleRate = m_settings.m_devSampleRate/(1<<m_settings.m_log2SoftInterp);
int ncoShift = m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0;
// send to self first
DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, m_settings.m_centerFrequency + ncoShift);
m_deviceAPI->getDeviceInputMessageQueue()->push(notif);
// send to sink buddies
const std::vector<DeviceSinkAPI*>& sinkBuddies = m_deviceAPI->getSinkBuddies();
std::vector<DeviceSinkAPI*>::const_iterator itSink = sinkBuddies.begin();
for (; itSink != sinkBuddies.end(); ++itSink)
{
DeviceLimeSDRShared *buddySharedPtr = (DeviceLimeSDRShared *) (*itSink)->getBuddySharedPtr();
uint64_t buddyCenterFreq = buddySharedPtr->m_centerFrequency;
int buddyNCOFreq = buddySharedPtr->m_ncoFrequency;
DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, buddyCenterFreq + buddyNCOFreq); // do not change center frequency
(*itSink)->getDeviceInputMessageQueue()->push(notif);
MsgReportLimeSDRToGUI *report = MsgReportLimeSDRToGUI::create(
m_settings.m_centerFrequency,
m_settings.m_devSampleRate,
m_settings.m_log2HardInterp);
(*itSink)->getDeviceOutputMessageQueue()->push(report);
}
}
else if (forwardChangeOwnDSP)
{
qDebug("LimeSDROutput::applySettings: forward change to self only");
int sampleRate = m_settings.m_devSampleRate/(1<<m_settings.m_log2SoftInterp);
int ncoShift = m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0;
DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, m_settings.m_centerFrequency + ncoShift);
m_deviceAPI->getDeviceInputMessageQueue()->push(notif);
}
qDebug() << "LimeSDROutput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz"
<< " device stream sample rate: " << m_settings.m_devSampleRate << "S/s"
<< " sample rate with soft decimation: " << m_settings.m_devSampleRate/(1<<m_settings.m_log2SoftInterp) << "S/s";
return true;
}

View File

@ -0,0 +1,231 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// //
// 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 as version 3 of the License, or //
// //
// 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUT_H_
#define PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUT_H_
#include <QString>
#include <stdint.h>
#include "dsp/devicesamplesink.h"
#include "limesdr/devicelimesdrshared.h"
#include "limesdroutputsettings.h"
class DeviceSinkAPI;
class LimeSDROutputThread;
struct DeviceLimeSDRParams;
class LimeSDROutput : public DeviceSampleSink
{
public:
class MsgConfigureLimeSDR : public Message {
MESSAGE_CLASS_DECLARATION
public:
const LimeSDROutputSettings& getSettings() const { return m_settings; }
static MsgConfigureLimeSDR* create(const LimeSDROutputSettings& settings)
{
return new MsgConfigureLimeSDR(settings);
}
private:
LimeSDROutputSettings m_settings;
MsgConfigureLimeSDR(const LimeSDROutputSettings& settings) :
Message(),
m_settings(settings)
{ }
};
class MsgSetReferenceConfig : public Message {
MESSAGE_CLASS_DECLARATION
public:
const LimeSDROutputSettings& getSettings() const { return m_settings; }
static MsgSetReferenceConfig* create(const LimeSDROutputSettings& settings)
{
return new MsgSetReferenceConfig(settings);
}
private:
LimeSDROutputSettings m_settings;
MsgSetReferenceConfig(const LimeSDROutputSettings& settings) :
Message(),
m_settings(settings)
{ }
};
class MsgGetStreamInfo : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgGetStreamInfo* create()
{
return new MsgGetStreamInfo();
}
private:
MsgGetStreamInfo() :
Message()
{ }
};
class MsgReportLimeSDRToGUI : public Message {
MESSAGE_CLASS_DECLARATION
public:
float getCenterFrequency() const { return m_centerFrequency; }
int getSampleRate() const { return m_sampleRate; }
uint32_t getLog2HardInterp() const { return m_log2HardInterp; }
static MsgReportLimeSDRToGUI* create(float centerFrequency, int sampleRate, uint32_t log2HardInterp)
{
return new MsgReportLimeSDRToGUI(centerFrequency, sampleRate, log2HardInterp);
}
private:
float m_centerFrequency;
int m_sampleRate;
uint32_t m_log2HardInterp;
MsgReportLimeSDRToGUI(float centerFrequency, int sampleRate, uint32_t log2HardInterp) :
Message(),
m_centerFrequency(centerFrequency),
m_sampleRate(sampleRate),
m_log2HardInterp(log2HardInterp)
{ }
};
class MsgReportStreamInfo : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getSuccess() const { return m_success; }
bool getActive() const { return m_active; }
uint32_t getFifoFilledCount() const { return m_fifoFilledCount; }
uint32_t getFifoSize() const { return m_fifoSize; }
uint32_t getUnderrun() const { return m_underrun; }
uint32_t getOverrun() const { return m_overrun; }
uint32_t getDroppedPackets() const { return m_droppedPackets; }
float getSampleRate() const { return m_sampleRate; }
float getLinkRate() const { return m_linkRate; }
uint64_t getTimestamp() const { return m_timestamp; }
static MsgReportStreamInfo* create(
bool success,
bool active,
uint32_t fifoFilledCount,
uint32_t fifoSize,
uint32_t underrun,
uint32_t overrun,
uint32_t droppedPackets,
float sampleRate,
float linkRate,
uint64_t timestamp
)
{
return new MsgReportStreamInfo(
success,
active,
fifoFilledCount,
fifoSize,
underrun,
overrun,
droppedPackets,
sampleRate,
linkRate,
timestamp
);
}
private:
bool m_success;
// everything from lms_stream_status_t
bool m_active; //!< Indicates whether the stream is currently active
uint32_t m_fifoFilledCount; //!< Number of samples in FIFO buffer
uint32_t m_fifoSize; //!< Size of FIFO buffer
uint32_t m_underrun; //!< FIFO underrun count
uint32_t m_overrun; //!< FIFO overrun count
uint32_t m_droppedPackets; //!< Number of dropped packets by HW
float m_sampleRate; //!< Sampling rate of the stream
float m_linkRate; //!< Combined data rate of all stream of the same direction (TX or RX)
uint64_t m_timestamp; //!< Current HW timestamp
MsgReportStreamInfo(
bool success,
bool active,
uint32_t fifoFilledCount,
uint32_t fifoSize,
uint32_t underrun,
uint32_t overrun,
uint32_t droppedPackets,
float sampleRate,
float linkRate,
uint64_t timestamp
) :
Message(),
m_success(success),
m_active(active),
m_fifoFilledCount(fifoFilledCount),
m_fifoSize(fifoSize),
m_underrun(underrun),
m_overrun(overrun),
m_droppedPackets(droppedPackets),
m_sampleRate(sampleRate),
m_linkRate(linkRate),
m_timestamp(timestamp)
{ }
};
LimeSDROutput(DeviceSinkAPI *deviceAPI);
virtual ~LimeSDROutput();
virtual bool start();
virtual void stop();
virtual const QString& getDeviceDescription() const;
virtual int getSampleRate() const;
virtual quint64 getCenterFrequency() const;
virtual bool handleMessage(const Message& message);
std::size_t getChannelIndex();
void getLORange(float& minF, float& maxF, float& stepF) const;
void getSRRange(float& minF, float& maxF, float& stepF) const;
void getLPRange(float& minF, float& maxF, float& stepF) const;
uint32_t getHWLog2Interp() const;
private:
DeviceSinkAPI *m_deviceAPI;
QMutex m_mutex;
LimeSDROutputSettings m_settings;
LimeSDROutputThread* m_limeSDROutputThread;
QString m_deviceDescription;
bool m_running;
DeviceLimeSDRShared m_deviceShared;
bool m_firstConfig;
lms_stream_t m_streamId;
bool openDevice();
void closeDevice();
bool applySettings(const LimeSDROutputSettings& settings, bool force);
};
#endif /* PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUT_H_ */

View File

@ -0,0 +1,825 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LimeSDROutputGUI</class>
<widget class="QWidget" name="LimeSDROutputGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>290</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>290</height>
</size>
</property>
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>LimeSDR Input</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_freq">
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QVBoxLayout" name="deviceUILayout">
<item>
<layout class="QHBoxLayout" name="deviceButtonsLayout">
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>start/stop acquisition</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrbase/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/stop.png</normalon>:/play.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="deviceRateLayout">
<item>
<widget class="QLabel" name="deviceRateLabel">
<property name="toolTip">
<string>I/Q sample rate kS/s</string>
</property>
<property name="text">
<string>00000k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="freqLeftSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="ValueDial" name="centerFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>20</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>SizeVerCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Main center frequency in kHz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="freqUnits">
<property name="text">
<string> kHz</string>
</property>
</widget>
</item>
<item>
<spacer name="freqRightlSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="channelNumberText">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Channel number</string>
</property>
<property name="text">
<string>#0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="ButtonSwitch" name="ncoEnable">
<property name="toolTip">
<string>Enable the TSP NCO</string>
</property>
<property name="text">
<string>NCO</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ncoReset">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="toolTip">
<string>Reset the NCO to zero frequency</string>
</property>
<property name="text">
<string>R</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDial" name="ncoFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="toolTip">
<string>Center frequency with NCO engaged (kHz)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="ncoUnits">
<property name="text">
<string>kHz</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_lna">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="ncoSampleRateLayout">
<property name="topMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="hwInterpLabel">
<property name="text">
<string>Hw</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="hwInterp">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>TSP hardware interpolation factor</string>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>32</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="swInterpLabel">
<property name="text">
<string>Sw</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="swInterp">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Software interpolation factor</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>32</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="samplerateLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SR</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDial" name="sampleRate" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="toolTip">
<string>Device to host sample rate</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="samplerateUnit">
<property name="text">
<string>S/s</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_freq">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="lpfLayout">
<property name="topMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="lpfLabel">
<property name="text">
<string>LP</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDial" name="lpf" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="toolTip">
<string>Analog lowpass filer bandwidth (kHz)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lpfUnits">
<property name="text">
<string>kHz</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="ButtonSwitch" name="lpFIREnable">
<property name="toolTip">
<string>Enable or disable TSP digital FIR lowpass filters</string>
</property>
<property name="text">
<string>FIR</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDial" name="lpFIR" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="toolTip">
<string>Digital FIR lowpass filers bandwidth (kHz)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lpFIRUnits">
<property name="text">
<string>kHz</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="gainLayout">
<property name="topMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="gainLabel">
<property name="text">
<string>Gain</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="gain">
<property name="toolTip">
<string>Global gain setting (dB)</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>70</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>20</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="gainText">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Global gain (dB)</string>
</property>
<property name="text">
<string>20dB</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="statusLayout">
<property name="topMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="streamStatusLabel">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Green when stream is reporting data</string>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../../sdrbase/resources/res.qrc">:/stream.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="underrunLabel">
<property name="minimumSize">
<size>
<width>12</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Red if underruns</string>
</property>
<property name="styleSheet">
<string notr="true">background:rgb(79,79,79);</string>
</property>
<property name="text">
<string>U</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="overrunLabel">
<property name="minimumSize">
<size>
<width>12</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Red if overruns</string>
</property>
<property name="styleSheet">
<string notr="true">background:rgb(79,79,79);</string>
</property>
<property name="text">
<string>O</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="droppedLabel">
<property name="minimumSize">
<size>
<width>12</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Red if dropped packets</string>
</property>
<property name="styleSheet">
<string notr="true">background:rgb(79,79,79);</string>
</property>
<property name="text">
<string>D</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="streamLinkRateText">
<property name="minimumSize">
<size>
<width>90</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Stream link rate (MB/s)</string>
</property>
<property name="text">
<string>000.000 MB/s</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="fifoBar">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>FIFO fill status</string>
</property>
<property name="styleSheet">
<string notr="true">QProgressBar{border: 2px solid rgb(79, 79, 79); text-align: center;}
QToolTip{background-color: white; color: black;}</string>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_vga2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="padLayout">
<item>
<spacer name="verticalPadSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ValueDial</class>
<extends>QWidget</extends>
<header>gui/valuedial.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrbase/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,130 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB //
// //
// 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 as version 3 of the License, or //
// //
// 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include <QAction>
#include <regex>
#include <string>
#include "lime/LimeSuite.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "device/devicesinkapi.h"
#include "limesdroutputgui.h"
#include "limesdroutputplugin.h"
const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = {
QString("LimeSDR Output"),
QString("3.4.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
const QString LimeSDROutputPlugin::m_hardwareID = "LimeSDR";
const QString LimeSDROutputPlugin::m_deviceTypeID = LIMESDROUTPUT_DEVICE_TYPE_ID;
LimeSDROutputPlugin::LimeSDROutputPlugin(QObject* parent) :
QObject(parent)
{
}
const PluginDescriptor& LimeSDROutputPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void LimeSDROutputPlugin::initPlugin(PluginAPI* pluginAPI)
{
pluginAPI->registerSampleSource(m_deviceTypeID, this);
}
PluginInterface::SamplingDevices LimeSDROutputPlugin::enumSampleSinks()
{
lms_info_str_t* deviceList;
int nbDevices;
SamplingDevices result;
if ((nbDevices = LMS_GetDeviceList(0)) <= 0)
{
qDebug("LimeSDROutputPlugin::enumSampleSources: Could not find any LimeSDR device");
return result; // empty result
}
deviceList = new lms_info_str_t[nbDevices];
if (LMS_GetDeviceList(deviceList) < 0)
{
qDebug("LimeSDROutputPlugin::enumSampleSources: Could not obtain LimeSDR devices information");
delete[] deviceList;
return result; // empty result
}
else
{
for (int i = 0; i < nbDevices; i++)
{
std::string serial("N/D");
findSerial((const char *) deviceList[i], serial);
qDebug("LimeSDROutputPlugin::enumSampleSources: device #%d: %s", i, (char *) deviceList[i]);
QString displayedName(QString("LimeSDR[%1] %2").arg(i).arg(serial.c_str()));
result.append(SamplingDevice(displayedName,
m_hardwareID,
m_deviceTypeID,
QString(deviceList[i]),
i));
}
}
delete[] deviceList;
return result;
}
PluginGUI* LimeSDROutputPlugin::createSampleSinkPluginGUI(const QString& sinkId,QWidget **widget, DeviceSinkAPI *deviceAPI)
{
if(sinkId == m_deviceTypeID)
{
LimeSDROutputGUI* gui = new LimeSDROutputGUI(deviceAPI);
*widget = gui;
return gui;
}
else
{
return 0;
}
}
bool LimeSDROutputPlugin::findSerial(const char *lmsInfoStr, std::string& serial)
{
std::regex serial_reg("serial=([0-9,A-F]+)");
std::string input(lmsInfoStr);
std::smatch result;
std::regex_search(input, result, serial_reg);
if (result[1].str().length()>0)
{
serial = result[1].str();
return true;
}
else
{
return false;
}
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// //
// 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 as version 3 of the License, or //
// //
// 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTPLUGIN_H_
#define PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTPLUGIN_H_
#include <QObject>
#include "plugin/plugininterface.h"
class PluginAPI;
#define LIMESDROUTPUT_DEVICE_TYPE_ID "sdrangel.samplesink.limesdr"
class LimeSDROutputPlugin : public QObject, public PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID LIMESDROUTPUT_DEVICE_TYPE_ID)
public:
explicit LimeSDROutputPlugin(QObject* parent = 0);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual SamplingDevices enumSampleSinks();
virtual PluginGUI* createSampleSinkPluginGUI(const QString& sinkId, QWidget **widget, DeviceSinkAPI *deviceAPI);
static const QString m_hardwareID;
static const QString m_deviceTypeID;
private:
static const PluginDescriptor m_pluginDescriptor;
static bool findSerial(const char *lmsInfoStr, std::string& serial);
};
#endif /* PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTPLUGIN_H_ */

View File

@ -0,0 +1,91 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// //
// 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 as version 3 of the License, or //
// //
// 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "util/simpleserializer.h"
#include "limesdroutputsettings.h"
LimeSDROutputSettings::LimeSDROutputSettings()
{
resetToDefaults();
}
void LimeSDROutputSettings::resetToDefaults()
{
m_centerFrequency = 435000*1000;
m_devSampleRate = 5000000;
m_log2HardInterp = 3;
m_log2SoftInterp = 0;
m_lpfBW = 4.5e6f;
m_lpfFIREnable = false;
m_lpfFIRBW = 2.5e6f;
m_gain = 30;
m_ncoEnable = false;
m_ncoFrequency = 0;
}
QByteArray LimeSDROutputSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_devSampleRate);
s.writeU32(2, m_log2HardInterp);
s.writeU32(5, m_log2SoftInterp);
s.writeFloat(7, m_lpfBW);
s.writeBool(8, m_lpfFIREnable);
s.writeFloat(9, m_lpfFIRBW);
s.writeU32(10, m_gain);
s.writeBool(11, m_ncoEnable);
s.writeS32(12, m_ncoFrequency);
return s.final();
}
bool LimeSDROutputSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
int intval;
d.readS32(1, &m_devSampleRate, 5000000);
d.readU32(2, &m_log2HardInterp, 2);
d.readU32(5, &m_log2SoftInterp, 0);
d.readFloat(7, &m_lpfBW, 1.5e6);
d.readBool(8, &m_lpfFIREnable, false);
d.readFloat(9, &m_lpfFIRBW, 1.5e6);
d.readU32(10, &m_gain, 0);
d.readBool(11, &m_ncoEnable, false);
d.readS32(12, &m_ncoFrequency, 0);
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,54 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// //
// 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 as version 3 of the License, or //
// //
// 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTSETTINGS_H_
#define PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTSETTINGS_H_
#include <QByteArray>
#include <stdint.h>
/**
* These are the settings individual to each hardware channel or software Tx chain
* Plus the settings to be saved in the presets
*/
struct LimeSDROutputSettings
{
typedef enum {
FC_POS_INFRA = 0,
FC_POS_SUPRA,
FC_POS_CENTER
} fcPos_t;
// global settings to be saved
uint64_t m_centerFrequency;
int m_devSampleRate;
uint32_t m_log2HardInterp;
// channel settings
uint32_t m_log2SoftInterp;
float m_lpfBW; //!< LMS amalog lowpass filter bandwidth (Hz)
bool m_lpfFIREnable; //!< Enable LMS digital lowpass FIR filters
float m_lpfFIRBW; //!< LMS digital lowpass FIR filters bandwidth (Hz)
uint32_t m_gain; //!< Optimally distributed gain (dB)
bool m_ncoEnable; //!< Enable TSP NCO and mixing
int m_ncoFrequency; //!< Actual NCO frequency (the resulting frequency with mixing is displayed)
LimeSDROutputSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUTSETTINGS_H_ */

View File

@ -0,0 +1,142 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// //
// 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 as version 3 of the License, or //
// //
// 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <errno.h>
#include "limesdroutputthread.h"
#include "limesdroutputsettings.h"
LimeSDROutputThread::LimeSDROutputThread(lms_stream_t* stream, SampleSourceFifo* sampleFifo, QObject* parent) :
QThread(parent),
m_running(false),
m_stream(stream),
m_sampleFifo(sampleFifo),
m_log2Interp(0),
m_fcPos(LimeSDROutputSettings::FC_POS_CENTER)
{
m_sampleFifo->resize(16*LIMESDROUTPUT_BLOCKSIZE);
}
LimeSDROutputThread::~LimeSDROutputThread()
{
stopWork();
}
void LimeSDROutputThread::startWork()
{
if (m_running) return; // return if running already
m_startWaitMutex.lock();
start();
while(!m_running)
m_startWaiter.wait(&m_startWaitMutex, 100);
m_startWaitMutex.unlock();
}
void LimeSDROutputThread::stopWork()
{
if (!m_running) return; // return if not running
m_running = false;
wait();
}
void LimeSDROutputThread::setLog2Interpolation(unsigned int log2_interp)
{
m_log2Interp = log2_interp;
}
void LimeSDROutputThread::setFcPos(int fcPos)
{
m_fcPos = fcPos;
}
void LimeSDROutputThread::run()
{
int res;
lms_stream_meta_t metadata; //Use metadata for additional control over sample receive function behaviour
metadata.flushPartialPacket = false; //Do not discard data remainder when read size differs from packet size
metadata.waitForTimestamp = false; //Do not wait for specific timestamps
m_running = true;
m_startWaiter.wakeAll();
if (LMS_StartStream(m_stream) < 0) {
qCritical("LimeSDROutputThread::run: could not start stream");
} else {
qDebug("LimeSDROutputThread::run: stream started");
}
while (m_running)
{
callback(m_buf, 2 * res);
if ((res = LMS_SendStream(m_stream, (void *) m_buf, LIMESDR_BLOCKSIZE, &metadata, 1000)) < 0)
{
qCritical("LimeSDROutputThread::run write error: %s", strerror(errno));
break;
}
}
if (LMS_StopStream(m_stream) < 0) {
qCritical("LimeSDROutputThread::run: could not stop stream");
} else {
qDebug("LimeSDROutputThread::run: stream stopped");
}
m_running = false;
}
// Interpolate according to specified log2 (ex: log2=4 => decim=16)
void LimeSDROutputThread::callback(const qint16* buf, qint32 len)
{
SampleVector::iterator beginRead;
m_sampleFifo->readAdvance(beginRead, len/(1<<m_log2Interp));
beginRead -= len;
if (m_log2Interp == 0)
{
m_interpolators.interpolate1(&beginRead, buf, len*2);
}
else
{
switch (m_log2Interp)
{
case 1:
m_interpolators.interpolate2_cen(&beginRead, buf, len*2);
break;
case 2:
m_interpolators.interpolate4_cen(&beginRead, buf, len*2);
break;
case 3:
m_interpolators.interpolate8_cen(&beginRead, buf, len*2);
break;
case 4:
m_interpolators.interpolate16_cen(&beginRead, buf, len*2);
break;
case 5:
m_interpolators.interpolate32_cen(&beginRead, buf, len*2);
break;
case 6:
m_interpolators.interpolate64_cen(&beginRead, buf, len*2);
break;
default:
break;
}
}
}

View File

@ -0,0 +1,64 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// //
// 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 as version 3 of the License, or //
// //
// 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTTHREAD_H_
#define PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTTHREAD_H_
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include "lime/LimeSuite.h"
#include "dsp/samplesourcefifo.h"
#include "dsp/interpolators.h"
#define LIMESDROUTPUT_BLOCKSIZE (1<<14) //complex samples per buffer ~10k (16k)
class LimeSDROutputThread : public QThread
{
Q_OBJECT
public:
LimeSDROutputThread(lms_stream_t* stream, SampleSourceFifo* sampleFifo, QObject* parent = 0);
~LimeSDROutputThread();
void startWork();
void stopWork();
void setLog2Interpolation(unsigned int log2_ioterp);
void setFcPos(int fcPos);
private:
QMutex m_startWaitMutex;
QWaitCondition m_startWaiter;
bool m_running;
lms_stream_t* m_stream;
qint16 m_buf[2*LIMESDROUTPUT_BLOCKSIZE]; //must hold I+Q values of each sample hence 2xcomplex size
SampleSourceFifo* m_sampleFifo;
unsigned int m_log2Interp; // soft decimation
int m_fcPos;
Interpolators<qint16, SDR_SAMP_SZ, 12> m_interpolators;
void run();
void callback(const qint16* buf, qint32 len);
};
#endif /* PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUTTHREAD_H_ */