/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Charles J. Cliffe
 * Copyright (c) 2020 Franco Venturi - changes for SDRplay API version 3
 *                                     and Dual Tuner for RSPduo

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "SoapySDRPlay.hpp"

#if defined(_M_X64) || defined(_M_IX86)
#define strcasecmp _stricmp
#elif defined (__GNUC__)
#include <strings.h>
#endif

std::unordered_map<std::string, sdrplay_api_DeviceT*> SoapySDRPlay::selectedRSPDevices;


std::set<std::string> &SoapySDRPlay_getClaimedSerials(void)
{
   static std::set<std::string> serials;
   return serials;
}

SoapySDRPlay::SoapySDRPlay(const SoapySDR::Kwargs &args)
{
    if (args.count("serial") == 0) throw std::runtime_error("no available RSP devices found");

    selectDevice(args.at("serial"),
                 args.count("mode") ? args.at("mode") : "",
                 args.count("antenna") ? args.at("antenna") : "");

    // keep all the default settings:
    // - rf: 200MHz
    // - fs: 2MHz
    // - decimation: off
    // - IF: 0kHz (zero IF)
    // - bw: 200kHz
    // - attenuation: 50dB
    // - LNA state: 0
    // - AGC: 50Hz
    // - DC correction: on
    // - IQ balance: on

    // change the default AGC set point to -30dBfs
    chParams->ctrlParams.agc.setPoint_dBfs = -30;

    // process additional device string arguments
    for (std::pair<std::string, std::string> arg : args) {
        // ignore 'driver', 'label', 'mode', 'serial', and 'soapy'
        if (arg.first == "driver" || arg.first == "label" ||
            arg.first == "mode" || arg.first == "serial" ||
            arg.first == "soapy") {
            continue;
        }
        writeSetting(arg.first, arg.second);
    }

    // streaming settings
    // this may change later according to format
    shortsPerWord = 1;
    bufferLength = bufferElems * elementsPerSample * shortsPerWord;

    _streams[0] = 0;
    _streams[1] = 0;
    _streamsRefCount[0] = 0;
    _streamsRefCount[1] = 0;
    useShort = true;

    streamActive = false;

    device_unavailable = false;

    cacheKey = serNo;
    if (hwVer == SDRPLAY_RSPduo_ID) cacheKey += "@" + args.at("mode");
    SoapySDRPlay_getClaimedSerials().insert(cacheKey);
}

SoapySDRPlay::~SoapySDRPlay(void)
{
    SoapySDRPlay_getClaimedSerials().erase(cacheKey);
    std::lock_guard <std::mutex> lock(_general_state_mutex);

    releaseDevice();

    _streams[0] = 0;
    _streams[1] = 0;
    _streamsRefCount[0] = 0;
    _streamsRefCount[1] = 0;
}

/*******************************************************************
 * Identification API
 ******************************************************************/

std::string SoapySDRPlay::getDriverKey(void) const
{
    return "SDRplay";
}

std::string SoapySDRPlay::getHardwareKey(void) const
{
    if (hwVer == SDRPLAY_RSP1_ID) return "RSP1";
    if (hwVer == SDRPLAY_RSP1A_ID) return "RSP1A";
    if (hwVer == SDRPLAY_RSP1B_ID) return "RSP1B";
    if (hwVer == SDRPLAY_RSP2_ID) return "RSP2";
    if (hwVer == SDRPLAY_RSPduo_ID) return "RSPduo";
    if (hwVer == SDRPLAY_RSPdx_ID) return "RSPdx";
    if (hwVer == SDRPLAY_RSPdxR2_ID) return "RSPdx-R2";
    return "UNKNOWN";
}

SoapySDR::Kwargs SoapySDRPlay::getHardwareInfo(void) const
{
    // key/value pairs for any useful information
    // this also gets printed in --probe
    SoapySDR::Kwargs hwArgs;

    float ver = SoapySDRPlay::sdrplay_api::get_version();
    hwArgs["sdrplay_api_api_version"] = std::to_string(ver);
    hwArgs["sdrplay_api_hw_version"] = std::to_string(device.hwVer);

    return hwArgs;
}

/*******************************************************************
 * Channels API
 ******************************************************************/

size_t SoapySDRPlay::getNumChannels(const int dir) const
{
    if (device.hwVer == SDRPLAY_RSPduo_ID && device.rspDuoMode == sdrplay_api_RspDuoMode_Dual_Tuner) {
        return (dir == SOAPY_SDR_RX) ? 2 : 0;
    }
    return (dir == SOAPY_SDR_RX) ? 1 : 0;
}

/*******************************************************************
 * Antenna API
 ******************************************************************/

std::vector<std::string> SoapySDRPlay::listAntennas(const int direction, const size_t channel) const
{
    std::vector<std::string> antennas;

    if (direction == SOAPY_SDR_TX) {
        return antennas;
    }

    if (device.hwVer == SDRPLAY_RSP1_ID || device.hwVer == SDRPLAY_RSP1A_ID || device.hwVer == SDRPLAY_RSP1B_ID) {
        antennas.push_back("RX");
    }
    else if (device.hwVer == SDRPLAY_RSP2_ID) {
        antennas.push_back("Antenna A");
        antennas.push_back("Antenna B");
        antennas.push_back("Hi-Z");
    }
    else if (device.hwVer == SDRPLAY_RSPdx_ID) {
        antennas.push_back("Antenna A");
        antennas.push_back("Antenna B");
        antennas.push_back("Antenna C");
    }
    else if (device.hwVer == SDRPLAY_RSPdxR2_ID) {
        antennas.push_back("Antenna A");
        antennas.push_back("Antenna B");
        antennas.push_back("Antenna C");
    }
    else if (device.hwVer == SDRPLAY_RSPduo_ID) {
        if (device.rspDuoMode == sdrplay_api_RspDuoMode_Single_Tuner ||
            device.rspDuoMode == sdrplay_api_RspDuoMode_Master) {
            antennas.push_back("Tuner 1 50 ohm");
            antennas.push_back("Tuner 1 Hi-Z");
            antennas.push_back("Tuner 2 50 ohm");
        }
        else if (device.rspDuoMode == sdrplay_api_RspDuoMode_Dual_Tuner) {
            if (channel == 0) {
                // No Hi-Z antenna in Dual Tuner mode
                // For diversity reception you would want the two tuner inputs
                // to be the same otherwise there is a mismatch in the gain
                // control.
                antennas.push_back("Tuner 1 50 ohm");
            }
            else if (channel == 1) {
                antennas.push_back("Tuner 2 50 ohm");
            }
        }
        else if (device.rspDuoMode == sdrplay_api_RspDuoMode_Slave) {
            if (device.tuner == sdrplay_api_Tuner_A) {
                antennas.push_back("Tuner 1 50 ohm");
                antennas.push_back("Tuner 1 Hi-Z");
            }
            else if (device.tuner == sdrplay_api_Tuner_B) {
                antennas.push_back("Tuner 2 50 ohm");
            }
        }
    }
    return antennas;
}

void SoapySDRPlay::setAntenna(const int direction, const size_t channel, const std::string &name)
{
    // Check direction
    if ((direction != SOAPY_SDR_RX) || (device.hwVer == SDRPLAY_RSP1_ID) || (device.hwVer == SDRPLAY_RSP1A_ID) || (device.hwVer == SDRPLAY_RSP1B_ID)) {
        return;
    }

    std::lock_guard <std::mutex> lock(_general_state_mutex);

    if (device.hwVer == SDRPLAY_RSP2_ID)
    {
        bool changeToAntennaA_B = false;

        if (name == "Antenna A")
        {
            chParams->rsp2TunerParams.antennaSel = sdrplay_api_Rsp2_ANTENNA_A;
            changeToAntennaA_B = true;
        }
        else if (name == "Antenna B")
        {
            chParams->rsp2TunerParams.antennaSel = sdrplay_api_Rsp2_ANTENNA_B;
            changeToAntennaA_B = true;
        }
        else if (name == "Hi-Z")
        {
            chParams->rsp2TunerParams.amPortSel = sdrplay_api_Rsp2_AMPORT_1;

            if (streamActive)
            {
                sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp2_AmPortSelect, sdrplay_api_Update_Ext1_None);
            }
        }

        if (changeToAntennaA_B)
        {

            //if we are currently High_Z, make the switch first.
            if (chParams->rsp2TunerParams.amPortSel == sdrplay_api_Rsp2_AMPORT_1)
            {
                chParams->rsp2TunerParams.amPortSel = sdrplay_api_Rsp2_AMPORT_2;

                if (streamActive)
                {
                    sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp2_AmPortSelect, sdrplay_api_Update_Ext1_None);
                }
            }
            else
            {
                if (streamActive)
                {
                    sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp2_AntennaControl, sdrplay_api_Update_Ext1_None);
                }
            }
        }
    }
    else if (device.hwVer == SDRPLAY_RSPdx_ID)
    {
        if (name == "Antenna A")
        {
            deviceParams->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_A;
        }
        else if (name == "Antenna B")
        {
            deviceParams->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_B;
        }
        else if (name == "Antenna C")
        {
            deviceParams->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_C;
        }

        if (streamActive)
        {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_AntennaControl);
        }
    }
    else if (device.hwVer == SDRPLAY_RSPdxR2_ID)
    {
        if (name == "Antenna A")
        {
            deviceParams->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_A;
        }
        else if (name == "Antenna B")
        {
            deviceParams->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_B;
        }
        else if (name == "Antenna C")
        {
            deviceParams->devParams->rspDxParams.antennaSel = sdrplay_api_RspDx_ANTENNA_C;
        }

        if (streamActive)
        {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_AntennaControl);
        }
    }
    else if (device.hwVer == SDRPLAY_RSPduo_ID)
    {
        bool changeToTunerA_B = false;
        bool changeAmPort = false;
        bool isTunerChangeAllowed = device.rspDuoMode == sdrplay_api_RspDuoMode_Single_Tuner || device.rspDuoMode == sdrplay_api_RspDuoMode_Master;

        if (name == "Tuner 1 50 ohm")
        {
            changeAmPort = chParams->rspDuoTunerParams.tuner1AmPortSel != sdrplay_api_RspDuo_AMPORT_2;
            chParams->rspDuoTunerParams.tuner1AmPortSel = sdrplay_api_RspDuo_AMPORT_2;
            changeToTunerA_B = isTunerChangeAllowed && device.tuner != sdrplay_api_Tuner_A;
        }
        else if (name == "Tuner 2 50 ohm")
        {
            changeAmPort = chParams->rspDuoTunerParams.tuner1AmPortSel != sdrplay_api_RspDuo_AMPORT_2;
            changeToTunerA_B = isTunerChangeAllowed && device.tuner != sdrplay_api_Tuner_B;
        }
        else if (name == "Tuner 1 Hi-Z")
        {
            changeAmPort = chParams->rspDuoTunerParams.tuner1AmPortSel != sdrplay_api_RspDuo_AMPORT_1;
            chParams->rspDuoTunerParams.tuner1AmPortSel = sdrplay_api_RspDuo_AMPORT_1;
            changeToTunerA_B = isTunerChangeAllowed && device.tuner != sdrplay_api_Tuner_A;
        }

        if (!changeToTunerA_B)
        {
            if (changeAmPort)
            {
                //if we are currently High_Z, make the switch first.
                if (streamActive)
                {
                    sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_RspDuo_AmPortSelect, sdrplay_api_Update_Ext1_None);
                }
            }
        }
        else
        {
            if (streamActive)
            {
                if (device.rspDuoMode == sdrplay_api_RspDuoMode_Single_Tuner)
                {
                    sdrplay_api_ErrT err;
                    err = sdrplay_api_SwapRspDuoActiveTuner(device.dev,
                               &device.tuner, chParams->rspDuoTunerParams.tuner1AmPortSel);
                    if (err != sdrplay_api_Success)
                    {
                        SoapySDR_logf(SOAPY_SDR_WARNING, "SwapRspDuoActiveTuner Error: %s", sdrplay_api_GetErrorString(err));
                    }
                    chParams = device.tuner == sdrplay_api_Tuner_B ?
                               deviceParams->rxChannelB : deviceParams->rxChannelA;
                }
                else if (device.rspDuoMode == sdrplay_api_RspDuoMode_Master)
                {
                    // not sure what is the best way to handle this case - fv
                    SoapySDR_log(SOAPY_SDR_WARNING, "tuner change not allowed in RSPduo Master mode while the device is streaming");
                }
            }
            else
            {
                // preserve all the device and tuner settings
                // when changing tuner/antenna
                sdrplay_api_DevParamsT devParams = *deviceParams->devParams;
                sdrplay_api_RxChannelParamsT rxChannelParams = *chParams;
                sdrplay_api_TunerSelectT other_tuner = (device.tuner == sdrplay_api_Tuner_A) ? sdrplay_api_Tuner_B : sdrplay_api_Tuner_A;
                selectDevice(other_tuner, device.rspDuoMode,
                             device.rspDuoSampleFreq, nullptr);
                // restore device and tuner settings
                *deviceParams->devParams = devParams;
                *chParams = rxChannelParams;
            }
        }
    }
}

std::string SoapySDRPlay::getAntenna(const int direction, const size_t channel) const
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

    if (direction == SOAPY_SDR_TX)
    {
        return "";
    }

    if (device.hwVer == SDRPLAY_RSP2_ID)
    {
        if (chParams->rsp2TunerParams.amPortSel == sdrplay_api_Rsp2_AMPORT_1) {
            return "Hi-Z";
        }
        else if (chParams->rsp2TunerParams.antennaSel == sdrplay_api_Rsp2_ANTENNA_A) {
            return "Antenna A";
        }
        else {
            return "Antenna B";
        }
    }
    else if (device.hwVer == SDRPLAY_RSPduo_ID)
    {
        if (device.tuner == sdrplay_api_Tuner_A ||
                (device.tuner == sdrplay_api_Tuner_Both && channel == 0)) {
            if (chParams->rspDuoTunerParams.tuner1AmPortSel == sdrplay_api_RspDuo_AMPORT_1) {
                return "Tuner 1 Hi-Z";
            } else {
                return "Tuner 1 50 ohm";
            }
        } else if (device.tuner == sdrplay_api_Tuner_B ||
                  (device.tuner == sdrplay_api_Tuner_Both && channel == 1)) {
                return "Tuner 2 50 ohm";
        }
    }
    else if (device.hwVer == SDRPLAY_RSPdx_ID)
    {
        if (deviceParams->devParams->rspDxParams.antennaSel == sdrplay_api_RspDx_ANTENNA_A) {
            return "Antenna A";
        }
        else if (deviceParams->devParams->rspDxParams.antennaSel == sdrplay_api_RspDx_ANTENNA_B) {
            return "Antenna B";
        }
        else if (deviceParams->devParams->rspDxParams.antennaSel == sdrplay_api_RspDx_ANTENNA_C) {
            return "Antenna C";
        }
    }
    else if (device.hwVer == SDRPLAY_RSPdxR2_ID)
    {
        if (deviceParams->devParams->rspDxParams.antennaSel == sdrplay_api_RspDx_ANTENNA_A) {
            return "Antenna A";
        }
        else if (deviceParams->devParams->rspDxParams.antennaSel == sdrplay_api_RspDx_ANTENNA_B) {
            return "Antenna B";
        }
        else if (deviceParams->devParams->rspDxParams.antennaSel == sdrplay_api_RspDx_ANTENNA_C) {
            return "Antenna C";
        }
    }

    return "RX";
}

/*******************************************************************
 * Frontend corrections API
 ******************************************************************/

bool SoapySDRPlay::hasDCOffsetMode(const int direction, const size_t channel) const
{
    return true;
}

void SoapySDRPlay::setDCOffsetMode(const int direction, const size_t channel, const bool automatic)
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

    //enable/disable automatic DC removal
    chParams->ctrlParams.dcOffset.DCenable = (unsigned char)automatic;
    chParams->ctrlParams.dcOffset.IQenable = (unsigned char)automatic;
}

bool SoapySDRPlay::getDCOffsetMode(const int direction, const size_t channel) const
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

    return (bool)chParams->ctrlParams.dcOffset.DCenable;
}

bool SoapySDRPlay::hasDCOffset(const int direction, const size_t channel) const
{
    //is a specific DC removal value configurable?
    return false;
}

bool SoapySDRPlay::hasFrequencyCorrection(const int direction, const size_t channel) const {
    return true;
}

void SoapySDRPlay::setFrequencyCorrection(const int direction, const size_t channel, const double value) {
    setFrequency(direction, channel, "CORR", value);
}

double SoapySDRPlay::getFrequencyCorrection(const int direction, const size_t channel) const {
    return getFrequency(direction, channel, "CORR");
}

/*******************************************************************
 * Gain API
 ******************************************************************/

std::vector<std::string> SoapySDRPlay::listGains(const int direction, const size_t channel) const
{
    //list available gain elements,
    //the functions below have a "name" parameter
    std::vector<std::string> results;

    results.push_back("IFGR");
    results.push_back("RFGR");

    return results;
}

bool SoapySDRPlay::hasGainMode(const int direction, const size_t channel) const
{
    return true;
}

void SoapySDRPlay::setGainMode(const int direction, const size_t channel, const bool automatic)
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

    sdrplay_api_AgcControlT agc_control = automatic ? sdrplay_api_AGC_CTRL_EN : sdrplay_api_AGC_DISABLE;
    if (chParams->ctrlParams.agc.enable != agc_control)
    {
        chParams->ctrlParams.agc.enable = agc_control;
        if (streamActive)
        {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Ctrl_Agc, sdrplay_api_Update_Ext1_None);
        }
    }
}

bool SoapySDRPlay::getGainMode(const int direction, const size_t channel) const
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

    return chParams->ctrlParams.agc.enable != sdrplay_api_AGC_DISABLE;
}

void SoapySDRPlay::setGain(const int direction, const size_t channel, const std::string &name, const double value)
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

   bool doUpdate = false;

   if (name == "IFGR")
   {
      if (chParams->ctrlParams.agc.enable == sdrplay_api_AGC_DISABLE)
      {
         //apply the change if the required value is different from gRdB
         if (chParams->tunerParams.gain.gRdB != (int)value)
         {
            chParams->tunerParams.gain.gRdB = (int)value;
            doUpdate = true;
         }
      }
      else
      {
         SoapySDR_log(SOAPY_SDR_WARNING, "Not updating IFGR gain because AGC is enabled");
      }
   }
   else if (name == "RFGR")
   {
      if (chParams->tunerParams.gain.LNAstate != (int)value) {
          chParams->tunerParams.gain.LNAstate = (int)value;
          doUpdate = true;
      }
   }
   if ((doUpdate == true) && (streamActive))
   {
      gr_changed = 0;
      sdrplay_api_ErrT err = sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Tuner_Gr, sdrplay_api_Update_Ext1_None);
      if (err != sdrplay_api_Success)
      {
         SoapySDR_logf(SOAPY_SDR_WARNING, "sdrplay_api_Update(Tuner_Gr) Error: %s", sdrplay_api_GetErrorString(err));
         return;
      }
      for (int i = 0; i < updateTimeout; ++i)
      {
         if (gr_changed != 0) {
            break;
         }
         std::this_thread::sleep_for(std::chrono::milliseconds(1));
      }
      if (gr_changed == 0)
      {
         SoapySDR_log(SOAPY_SDR_WARNING, "Gain reduction update timeout.");
      }
   }
}

double SoapySDRPlay::getGain(const int direction, const size_t channel, const std::string &name) const
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

   if (name == "IFGR")
   {
       return chParams->tunerParams.gain.gRdB;
   }
   else if (name == "RFGR")
   {
      return chParams->tunerParams.gain.LNAstate;
   }

   return 0;
}

SoapySDR::Range SoapySDRPlay::getGainRange(const int direction, const size_t channel, const std::string &name) const
{
   if (name == "IFGR")
   {
      return SoapySDR::Range(20, 59);
   }
   else if ((name == "RFGR") && (device.hwVer == SDRPLAY_RSP1_ID))
   {
      return SoapySDR::Range(0, 3);
   }
   else if ((name == "RFGR") && (device.hwVer == SDRPLAY_RSP2_ID))
   {
      return SoapySDR::Range(0, 8);
   }
   else if ((name == "RFGR") && (device.hwVer == SDRPLAY_RSPduo_ID))
   {
      return SoapySDR::Range(0, 9);
   }
   else if ((name == "RFGR") && (device.hwVer == SDRPLAY_RSP1A_ID))
   {
      return SoapySDR::Range(0, 9);
   }
   else if ((name == "RFGR") && (device.hwVer == SDRPLAY_RSP1B_ID))
   {
      return SoapySDR::Range(0, 9);
   }
   else if ((name == "RFGR") && (device.hwVer == SDRPLAY_RSPdx_ID))
   {
      return SoapySDR::Range(0, 27);
   }
   else if ((name == "RFGR") && (device.hwVer == SDRPLAY_RSPdxR2_ID))
   {
      return SoapySDR::Range(0, 27);
   }
    return SoapySDR::Range(20, 59);
}

/*******************************************************************
 * Frequency API
 ******************************************************************/

void SoapySDRPlay::setFrequency(const int direction,
                                 const size_t channel,
                                 const double frequency,
                                 const SoapySDR::Kwargs &args)
{
    // default to RF
    setFrequency(direction, channel, "RF", frequency, args);
}

void SoapySDRPlay::setFrequency(const int direction,
                                 const size_t channel,
                                 const std::string &name,
                                 const double frequency,
                                 const SoapySDR::Kwargs &args)
{
   std::lock_guard <std::mutex> lock(_general_state_mutex);


   if (direction == SOAPY_SDR_RX)
   {
      if (name == "RF")
      {
         SoapySDR::RangeList frequencyRange = getFrequencyRange(direction, channel, name);
         if (!(frequency >= frequencyRange.front().minimum() && frequency <= frequencyRange.back().maximum()))
         {
            SoapySDR_logf(SOAPY_SDR_WARNING, "RF center frequency out of range - frequency=%lg", frequency);
            return;
         }
         if (chParams->tunerParams.rfFreq.rfHz != (uint32_t)frequency)
         {
            chParams->tunerParams.rfFreq.rfHz = (uint32_t)frequency;
            if (streamActive)
            {
               rf_changed = 0;
               sdrplay_api_ErrT err = sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Tuner_Frf, sdrplay_api_Update_Ext1_None);
               if (err != sdrplay_api_Success)
               {
                  SoapySDR_logf(SOAPY_SDR_WARNING, "sdrplay_api_Update(Tuner_FrF) Error: %s", sdrplay_api_GetErrorString(err));
                  return;
               }
               for (int i = 0; i < updateTimeout; ++i)
               {
                  if (rf_changed != 0) {
                     break;
                  }
                  std::this_thread::sleep_for(std::chrono::milliseconds(1));
               }
               if (rf_changed == 0)
               {
                  SoapySDR_log(SOAPY_SDR_WARNING, "RF center frequency update timeout.");
               }
            }
         }
      }
      // can't set ppm for RSPduo slaves
      else if ((name == "CORR") && deviceParams->devParams &&
              (deviceParams->devParams->ppm != frequency))
      {
         deviceParams->devParams->ppm = frequency;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Dev_Ppm, sdrplay_api_Update_Ext1_None);
         }
      }
   }
}

double SoapySDRPlay::getFrequency(const int direction, const size_t channel) const
{
    // default to RF
    return getFrequency(direction, channel, "RF");
}

double SoapySDRPlay::getFrequency(const int direction, const size_t channel, const std::string &name) const
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

    if (name == "RF")
    {
        return (double)chParams->tunerParams.rfFreq.rfHz;
    }
    else if (name == "CORR")
    {
        if (deviceParams->devParams)
        {
            return deviceParams->devParams->ppm;
        } else {
            return 0;
        }
    }

    return 0;
}

std::vector<std::string> SoapySDRPlay::listFrequencies(const int direction, const size_t channel) const
{
    std::vector<std::string> names;
    names.push_back("RF");
    names.push_back("CORR");
    return names;
}

SoapySDR::RangeList SoapySDRPlay::getFrequencyRange(const int direction, const size_t channel) const
{
    return getFrequencyRange(direction, channel, "RF");
}

SoapySDR::RangeList SoapySDRPlay::getFrequencyRange(const int direction, const size_t channel, const std::string &name) const
{
    SoapySDR::RangeList results;
    if (name == "RF")
    {
        if(device.hwVer == SDRPLAY_RSP1_ID)
        {
            results.push_back(SoapySDR::Range(10000, 2000000000));
        }
        else
        {
            results.push_back(SoapySDR::Range(1000, 2000000000));
        }
    }
    return results;
}

SoapySDR::ArgInfoList SoapySDRPlay::getFrequencyArgsInfo(const int direction, const size_t channel) const
{
    SoapySDR::ArgInfoList freqArgs;

    return freqArgs;
}

/*******************************************************************
 * Sample Rate API
 ******************************************************************/

/* input_sample_rate:  sample rate used by the SDR
 * output_sample_rate: sample rate as seen by the client app
 *                     (<= input_sample_rate because of decimation)
 */

void SoapySDRPlay::setSampleRate(const int direction, const size_t channel, const double output_sample_rate)
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

    SoapySDR_logf(SOAPY_SDR_DEBUG, "Requested output sample rate: %lf", output_sample_rate);

    if (direction == SOAPY_SDR_RX)
    {
       unsigned int decM;
       unsigned int decEnable;
       sdrplay_api_If_kHzT ifType;
       double input_sample_rate = getInputSampleRateAndDecimation(output_sample_rate, &decM, &decEnable, &ifType);
       if (input_sample_rate < 0) {
           SoapySDR_logf(SOAPY_SDR_WARNING, "invalid sample rate. Sample rate unchanged.");
           return;
       }

       sdrplay_api_Bw_MHzT bwType = getBwEnumForRate(output_sample_rate);

       sdrplay_api_ReasonForUpdateT reasonForUpdate = sdrplay_api_Update_None;
       bool waitForUpdate = false;
       if (deviceParams->devParams && input_sample_rate != deviceParams->devParams->fsFreq.fsHz)
       {
          deviceParams->devParams->fsFreq.fsHz = input_sample_rate;
          reasonForUpdate = (sdrplay_api_ReasonForUpdateT)(reasonForUpdate | sdrplay_api_Update_Dev_Fs);
          waitForUpdate = true;
       }
       if (ifType != chParams->tunerParams.ifType)
       {
          chParams->tunerParams.ifType = ifType;
          reasonForUpdate = (sdrplay_api_ReasonForUpdateT)(reasonForUpdate | sdrplay_api_Update_Tuner_IfType);
       }
       if (decM != chParams->ctrlParams.decimation.decimationFactor)
       {
          chParams->ctrlParams.decimation.enable = decEnable;
          chParams->ctrlParams.decimation.decimationFactor = decM;
          if (ifType == sdrplay_api_IF_Zero) {
              chParams->ctrlParams.decimation.wideBandSignal = 1;
          }
          else {
              chParams->ctrlParams.decimation.wideBandSignal = 0;
          }
          reasonForUpdate = (sdrplay_api_ReasonForUpdateT)(reasonForUpdate | sdrplay_api_Update_Ctrl_Decimation);
       }
       if (bwType != chParams->tunerParams.bwType)
       {
          chParams->tunerParams.bwType = bwType;
          reasonForUpdate = (sdrplay_api_ReasonForUpdateT)(reasonForUpdate | sdrplay_api_Update_Tuner_BwType);
       }
       if (reasonForUpdate != sdrplay_api_Update_None)
       {
          if (_streams[0]) { _streams[0]->reset = true; }
          if (_streams[1]) { _streams[1]->reset = true; }
          if (streamActive)
          {
             // beware that when the fs change crosses the boundary between
             // 2,685,312 and 2,685,313 the rx_callbacks stop for some
             // reason
             fs_changed = 0;
             sdrplay_api_ErrT err = sdrplay_api_Update(device.dev, device.tuner, reasonForUpdate, sdrplay_api_Update_Ext1_None);
             if (err != sdrplay_api_Success)
             {
                 SoapySDR_logf(SOAPY_SDR_WARNING, "sdrplay_api_Update(%08x) Error: %s", reasonForUpdate, sdrplay_api_GetErrorString(err));
                 return;
             }
             if (waitForUpdate)
             {
                for (int i = 0; i < updateTimeout; ++i)
                {
                   if (fs_changed != 0) {
                      break;
                   }
                   std::this_thread::sleep_for(std::chrono::milliseconds(1));
                }
                if (fs_changed == 0)
                {
                   SoapySDR_log(SOAPY_SDR_WARNING, "Sample rate update timeout.");
                }
             }
          }
       }
    }
}

double SoapySDRPlay::getSampleRate(const int direction, const size_t channel) const
{
   double fsHz = deviceParams->devParams ? deviceParams->devParams->fsFreq.fsHz : device.rspDuoSampleFreq;
   if ((fsHz == 6.0e6 && chParams->tunerParams.ifType == sdrplay_api_IF_1_620) ||
       (fsHz == 8.0e6 && chParams->tunerParams.ifType == sdrplay_api_IF_2_048))
   {
      fsHz = 2.0e6;
   }
   else if (!(fsHz >= 2.0e6 &&
              chParams->tunerParams.ifType == sdrplay_api_IF_Zero &&
              (device.hwVer != SDRPLAY_RSPduo_ID || device.rspDuoMode == sdrplay_api_RspDuoMode_Single_Tuner)
           ))
   {
      SoapySDR_logf(SOAPY_SDR_ERROR, "Invalid sample rate and/or IF setting - fsHz=%lf ifType=%d hwVer=%d rspDuoMode=%d rspDuoSampleFreq=%lf", fsHz, chParams->tunerParams.ifType, device.hwVer, device.rspDuoMode, device.rspDuoSampleFreq);
      throw std::runtime_error("Invalid sample rate and/or IF setting");
   }

   if (!chParams->ctrlParams.decimation.enable)
   {
      return fsHz;
   }
   else
   {
      return fsHz / chParams->ctrlParams.decimation.decimationFactor;
   }
}

std::vector<double> SoapySDRPlay::listSampleRates(const int direction, const size_t channel) const
{
    std::vector<double> output_sample_rates;

    if (device.hwVer == SDRPLAY_RSPduo_ID && device.rspDuoMode != sdrplay_api_RspDuoMode_Single_Tuner)
    {
        output_sample_rates.push_back(62500);
        output_sample_rates.push_back(125000);
        output_sample_rates.push_back(250000);
        output_sample_rates.push_back(500000);
        output_sample_rates.push_back(1000000);
        output_sample_rates.push_back(2000000);
        return output_sample_rates;
    }

    output_sample_rates.push_back(62500);
    output_sample_rates.push_back(96000);
    output_sample_rates.push_back(125000);
    output_sample_rates.push_back(192000);
    output_sample_rates.push_back(250000);
    output_sample_rates.push_back(384000);
    output_sample_rates.push_back(500000);
    output_sample_rates.push_back(768000);
    output_sample_rates.push_back(1000000);
    output_sample_rates.push_back(2000000);
    output_sample_rates.push_back(2048000);
    output_sample_rates.push_back(3000000);
    output_sample_rates.push_back(4000000);
    output_sample_rates.push_back(5000000);
    output_sample_rates.push_back(6000000);
    output_sample_rates.push_back(7000000);
    output_sample_rates.push_back(8000000);
    output_sample_rates.push_back(9000000);
    output_sample_rates.push_back(10000000);
    return output_sample_rates;
}

SoapySDR::RangeList SoapySDRPlay::getSampleRateRange(const int direction, const size_t channel) const
{
    SoapySDR::RangeList output_sample_rates;

    if (device.hwVer == SDRPLAY_RSPduo_ID && device.rspDuoMode != sdrplay_api_RspDuoMode_Single_Tuner)
    {
        output_sample_rates.push_back(SoapySDR::Range(62500, 62500));
        output_sample_rates.push_back(SoapySDR::Range(125000, 125000));
        output_sample_rates.push_back(SoapySDR::Range(250000, 250000));
        output_sample_rates.push_back(SoapySDR::Range(500000, 500000));
        output_sample_rates.push_back(SoapySDR::Range(1000000, 1000000));
        output_sample_rates.push_back(SoapySDR::Range(2000000, 2000000));
        return output_sample_rates;
    }

    output_sample_rates.push_back(SoapySDR::Range(62500, 62500));
    output_sample_rates.push_back(SoapySDR::Range(96000, 96000));
    output_sample_rates.push_back(SoapySDR::Range(125000, 125000));
    output_sample_rates.push_back(SoapySDR::Range(192000, 192000));
    output_sample_rates.push_back(SoapySDR::Range(250000, 250000));
    output_sample_rates.push_back(SoapySDR::Range(384000, 384000));
    output_sample_rates.push_back(SoapySDR::Range(500000, 500000));
    output_sample_rates.push_back(SoapySDR::Range(768000, 768000));
    output_sample_rates.push_back(SoapySDR::Range(1000000, 1000000));
    output_sample_rates.push_back(SoapySDR::Range(2000000, 10660000));
    return output_sample_rates;
}

double SoapySDRPlay::getInputSampleRateAndDecimation(uint32_t output_sample_rate, unsigned int *decM, unsigned int *decEnable, sdrplay_api_If_kHzT *ifType) const
{
    sdrplay_api_If_kHzT lif = sdrplay_api_IF_1_620;
    double lif_input_sample_rate = 6000000;
    if (device.hwVer == SDRPLAY_RSPduo_ID && device.rspDuoSampleFreq == 8000000)
    {
        lif = sdrplay_api_IF_2_048;
        lif_input_sample_rate = 8000000;
    }

    // all RSPs should support these sample rates
    switch (output_sample_rate) {
        case 62500:
            *ifType = lif; *decM = 32; *decEnable = 1;
            return lif_input_sample_rate;
        case 125000:
            *ifType = lif; *decM = 16; *decEnable = 1;
            return lif_input_sample_rate;
        case 250000:
            *ifType = lif; *decM =  8; *decEnable = 1;
            return lif_input_sample_rate;
        case 500000:
            *ifType = lif; *decM =  4; *decEnable = 1;
            return lif_input_sample_rate;
        case 1000000:
            *ifType = lif; *decM =  2; *decEnable = 1;
            return lif_input_sample_rate;
        case 2000000:
            *ifType = lif; *decM =  1; *decEnable = 0;
            return lif_input_sample_rate;
    }

    if (device.hwVer == SDRPLAY_RSPduo_ID && device.rspDuoMode != sdrplay_api_RspDuoMode_Single_Tuner)
    {
        return -1;
    }

    if (output_sample_rate <= 2000000)
    {
        switch (output_sample_rate) {
            case 96000:
                *ifType = sdrplay_api_IF_Zero; *decM = 32; *decEnable = 1;
                return output_sample_rate * *decM;
            case 192000:
                *ifType = sdrplay_api_IF_Zero; *decM = 16; *decEnable = 1;
                return output_sample_rate * *decM;
            case 384000:
                *ifType = sdrplay_api_IF_Zero; *decM =  8; *decEnable = 1;
                return output_sample_rate * *decM;
            case 768000:
                *ifType = sdrplay_api_IF_Zero; *decM =  4; *decEnable = 1;
                return output_sample_rate * *decM;
            default:
                return -1;
        }
    }

    // rate should be > 2 MHz so just return output_sample_rate
    *decM = 1; *decEnable = 0;
    *ifType = sdrplay_api_IF_Zero;
    return output_sample_rate;
}

/*******************************************************************
* Bandwidth API
******************************************************************/

void SoapySDRPlay::setBandwidth(const int direction, const size_t channel, const double bw_in)
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

   if (direction == SOAPY_SDR_RX)
   {
      // gqrx uses the value 0 for the default; in this case set it to the
      // maximum value compatible with the sample rate
      sdrplay_api_Bw_MHzT bwType = getBwEnumForRate(bw_in > 0 ? bw_in : getSampleRate(direction, channel));
      if (chParams->tunerParams.bwType != bwType)
      {
         chParams->tunerParams.bwType = bwType;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Tuner_BwType, sdrplay_api_Update_Ext1_None);
         }
      }
   }
}

double SoapySDRPlay::getBandwidth(const int direction, const size_t channel) const
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

   if (direction == SOAPY_SDR_RX)
   {
      return getBwValueFromEnum(chParams->tunerParams.bwType);
   }
   return 0;
}

std::vector<double> SoapySDRPlay::listBandwidths(const int direction, const size_t channel) const
{
   std::vector<double> bandwidths;
   bandwidths.push_back(200000);
   bandwidths.push_back(300000);
   bandwidths.push_back(600000);
   bandwidths.push_back(1536000);
   if (!(device.hwVer == SDRPLAY_RSPduo_ID &&
         (device.rspDuoMode == sdrplay_api_RspDuoMode_Dual_Tuner ||
          device.rspDuoMode == sdrplay_api_RspDuoMode_Master ||
          device.rspDuoMode == sdrplay_api_RspDuoMode_Slave))) {
       bandwidths.push_back(5000000);
       bandwidths.push_back(6000000);
       bandwidths.push_back(7000000);
       bandwidths.push_back(8000000);
   }
   return bandwidths;
}

SoapySDR::RangeList SoapySDRPlay::getBandwidthRange(const int direction, const size_t channel) const
{
   SoapySDR::RangeList results;
   //call into the older deprecated listBandwidths() call
   for (auto &bw : this->listBandwidths(direction, channel))
   {
     results.push_back(SoapySDR::Range(bw, bw));
   }
   return results;
}


sdrplay_api_Bw_MHzT SoapySDRPlay::getBwEnumForRate(double output_sample_rate)
{
   if      (output_sample_rate <  300000) return sdrplay_api_BW_0_200;
   else if (output_sample_rate <  600000) return sdrplay_api_BW_0_300;
   else if (output_sample_rate < 1536000) return sdrplay_api_BW_0_600;
   else if (output_sample_rate < 5000000) return sdrplay_api_BW_1_536;
   else if (output_sample_rate < 6000000) return sdrplay_api_BW_5_000;
   else if (output_sample_rate < 7000000) return sdrplay_api_BW_6_000;
   else if (output_sample_rate < 8000000) return sdrplay_api_BW_7_000;
   else                                   return sdrplay_api_BW_8_000;
}


double SoapySDRPlay::getBwValueFromEnum(sdrplay_api_Bw_MHzT bwEnum)
{
   if      (bwEnum == sdrplay_api_BW_0_200) return 200000;
   else if (bwEnum == sdrplay_api_BW_0_300) return 300000;
   else if (bwEnum == sdrplay_api_BW_0_600) return 600000;
   else if (bwEnum == sdrplay_api_BW_1_536) return 1536000;
   else if (bwEnum == sdrplay_api_BW_5_000) return 5000000;
   else if (bwEnum == sdrplay_api_BW_6_000) return 6000000;
   else if (bwEnum == sdrplay_api_BW_7_000) return 7000000;
   else if (bwEnum == sdrplay_api_BW_8_000) return 8000000;
   else return 0;
}

/*******************************************************************
* Settings API
******************************************************************/

unsigned char SoapySDRPlay::stringToHWVer(std::string hwVer)
{
   if (strcasecmp(hwVer.c_str(), "RSP1") == 0)
   {
      return SDRPLAY_RSP1_ID;
   }
   else if (strcasecmp(hwVer.c_str(), "RSP1A") == 0)
   {
      return SDRPLAY_RSP1A_ID;
   }
   else if (strcasecmp(hwVer.c_str(), "RSP1B") == 0)
   {
      return SDRPLAY_RSP1B_ID;
   }
   else if (strcasecmp(hwVer.c_str(), "RSP2") == 0)
   {
      return SDRPLAY_RSP2_ID;
   }
   else if (strcasecmp(hwVer.c_str(), "RSPduo") == 0)
   {
      return SDRPLAY_RSPduo_ID;
   }
   else if (strcasecmp(hwVer.c_str(), "RSPdx") == 0)
   {
      return SDRPLAY_RSPdx_ID;
   }
   else if (strcasecmp(hwVer.c_str(), "RSPdx-R2") == 0)
   {
      return SDRPLAY_RSPdxR2_ID;
   }
   return 0;
}

std::string SoapySDRPlay::HWVertoString(unsigned char hwVer)
{
   switch (hwVer)
   {
   case SDRPLAY_RSP1_ID:
      return "RSP1";
      break;
   case SDRPLAY_RSP1A_ID:
      return "RSP1A";
      break;
   case SDRPLAY_RSP1B_ID:
      return "RSP1B";
      break;
   case SDRPLAY_RSP2_ID:
      return "RSP2";
      break;
   case SDRPLAY_RSPduo_ID:
      return "RSPduo";
      break;
   case SDRPLAY_RSPdx_ID:
      return "RSPdx";
      break;
   case SDRPLAY_RSPdxR2_ID:
      return "RSPdx-R2";
      break;
   }
   return "";
}

sdrplay_api_RspDuoModeT SoapySDRPlay::stringToRSPDuoMode(std::string rspDuoMode)
{
   if (strcasecmp(rspDuoMode.c_str(), "Single Tuner") == 0)
   {
      return sdrplay_api_RspDuoMode_Single_Tuner;
   }
   else if (strcasecmp(rspDuoMode.c_str(), "Dual Tuner") == 0)
   {
      return sdrplay_api_RspDuoMode_Dual_Tuner;
   }
   else if (strcasecmp(rspDuoMode.c_str(), "Master") == 0)
   {
      return sdrplay_api_RspDuoMode_Master;
   }
   else if (strcasecmp(rspDuoMode.c_str(), "Slave") == 0)
   {
      return sdrplay_api_RspDuoMode_Slave;
   }
   return sdrplay_api_RspDuoMode_Unknown;
}

std::string SoapySDRPlay::RSPDuoModetoString(sdrplay_api_RspDuoModeT rspDuoMode)
{
   switch (rspDuoMode)
   {
   case sdrplay_api_RspDuoMode_Unknown:
      return "";
      break;
   case sdrplay_api_RspDuoMode_Single_Tuner:
      return "Single Tuner";
      break;
   case sdrplay_api_RspDuoMode_Dual_Tuner:
      return "Dual Tuner";
      break;
   case sdrplay_api_RspDuoMode_Master:
      return "Master";
      break;
   case sdrplay_api_RspDuoMode_Slave:
      return "Slave";
      break;
   }
   return "";
}

SoapySDR::ArgInfoList SoapySDRPlay::getSettingInfo(void) const
{
    SoapySDR::ArgInfoList setArgs;

    // call selectDevice() because CubicSDR may think the device is
    // already selected - fv
    // here we need to cast away the constness of this, since selectDevice()
    // makes changes to its members
    SoapySDRPlay *non_const_this = const_cast<SoapySDRPlay*>(this);
    non_const_this->selectDevice();

#ifdef RF_GAIN_IN_MENU
    if (device.hwVer == SDRPLAY_RSP2_ID)
    {
       SoapySDR::ArgInfo RfGainArg;
       RfGainArg.key = "rfgain_sel";
       RfGainArg.value = "4";
       RfGainArg.name = "RF Gain Select";
       RfGainArg.description = "RF Gain Select";
       RfGainArg.type = SoapySDR::ArgInfo::STRING;
       RfGainArg.options.push_back("0");
       RfGainArg.options.push_back("1");
       RfGainArg.options.push_back("2");
       RfGainArg.options.push_back("3");
       RfGainArg.options.push_back("4");
       RfGainArg.options.push_back("5");
       RfGainArg.options.push_back("6");
       RfGainArg.options.push_back("7");
       RfGainArg.options.push_back("8");
       setArgs.push_back(RfGainArg);
    }
    else if (device.hwVer == SDRPLAY_RSPduo_ID)
    {
       SoapySDR::ArgInfo RfGainArg;
       RfGainArg.key = "rfgain_sel";
       RfGainArg.value = "4";
       RfGainArg.name = "RF Gain Select";
       RfGainArg.description = "RF Gain Select";
       RfGainArg.type = SoapySDR::ArgInfo::STRING;
       RfGainArg.options.push_back("0");
       RfGainArg.options.push_back("1");
       RfGainArg.options.push_back("2");
       RfGainArg.options.push_back("3");
       RfGainArg.options.push_back("4");
       RfGainArg.options.push_back("5");
       RfGainArg.options.push_back("6");
       RfGainArg.options.push_back("7");
       RfGainArg.options.push_back("8");
       RfGainArg.options.push_back("9");
       setArgs.push_back(RfGainArg);
    }
    else if (device.hwVer == SDRPLAY_RSP1A_ID)
    {
       SoapySDR::ArgInfo RfGainArg;
       RfGainArg.key = "rfgain_sel";
       RfGainArg.value = "4";
       RfGainArg.name = "RF Gain Select";
       RfGainArg.description = "RF Gain Select";
       RfGainArg.type = SoapySDR::ArgInfo::STRING;
       RfGainArg.options.push_back("0");
       RfGainArg.options.push_back("1");
       RfGainArg.options.push_back("2");
       RfGainArg.options.push_back("3");
       RfGainArg.options.push_back("4");
       RfGainArg.options.push_back("5");
       RfGainArg.options.push_back("6");
       RfGainArg.options.push_back("7");
       RfGainArg.options.push_back("8");
       RfGainArg.options.push_back("9");
       setArgs.push_back(RfGainArg);
    }
    else if (device.hwVer == SDRPLAY_RSP1B_ID)
    {
       SoapySDR::ArgInfo RfGainArg;
       RfGainArg.key = "rfgain_sel";
       RfGainArg.value = "4";
       RfGainArg.name = "RF Gain Select";
       RfGainArg.description = "RF Gain Select";
       RfGainArg.type = SoapySDR::ArgInfo::STRING;
       RfGainArg.options.push_back("0");
       RfGainArg.options.push_back("1");
       RfGainArg.options.push_back("2");
       RfGainArg.options.push_back("3");
       RfGainArg.options.push_back("4");
       RfGainArg.options.push_back("5");
       RfGainArg.options.push_back("6");
       RfGainArg.options.push_back("7");
       RfGainArg.options.push_back("8");
       RfGainArg.options.push_back("9");
       setArgs.push_back(RfGainArg);
    }
    else if (device.hwVer == SDRPLAY_RSPdx_ID)
    {
       SoapySDR::ArgInfo RfGainArg;
       RfGainArg.key = "rfgain_sel";
       RfGainArg.value = "4";
       RfGainArg.name = "RF Gain Select";
       RfGainArg.description = "RF Gain Select";
       RfGainArg.type = SoapySDR::ArgInfo::STRING;
       RfGainArg.options.push_back("0");
       RfGainArg.options.push_back("1");
       RfGainArg.options.push_back("2");
       RfGainArg.options.push_back("3");
       RfGainArg.options.push_back("4");
       RfGainArg.options.push_back("5");
       RfGainArg.options.push_back("6");
       RfGainArg.options.push_back("7");
       RfGainArg.options.push_back("8");
       RfGainArg.options.push_back("9");
       RfGainArg.options.push_back("10");
       RfGainArg.options.push_back("11");
       RfGainArg.options.push_back("12");
       RfGainArg.options.push_back("13");
       RfGainArg.options.push_back("14");
       RfGainArg.options.push_back("15");
       RfGainArg.options.push_back("16");
       RfGainArg.options.push_back("17");
       RfGainArg.options.push_back("18");
       RfGainArg.options.push_back("19");
       RfGainArg.options.push_back("20");
       RfGainArg.options.push_back("21");
       RfGainArg.options.push_back("22");
       RfGainArg.options.push_back("23");
       RfGainArg.options.push_back("24");
       RfGainArg.options.push_back("25");
       RfGainArg.options.push_back("26");
       RfGainArg.options.push_back("27");
       setArgs.push_back(RfGainArg);
    }
    else if (device.hwVer == SDRPLAY_RSPdxR2_ID)
    {
       SoapySDR::ArgInfo RfGainArg;
       RfGainArg.key = "rfgain_sel";
       RfGainArg.value = "4";
       RfGainArg.name = "RF Gain Select";
       RfGainArg.description = "RF Gain Select";
       RfGainArg.type = SoapySDR::ArgInfo::STRING;
       RfGainArg.options.push_back("0");
       RfGainArg.options.push_back("1");
       RfGainArg.options.push_back("2");
       RfGainArg.options.push_back("3");
       RfGainArg.options.push_back("4");
       RfGainArg.options.push_back("5");
       RfGainArg.options.push_back("6");
       RfGainArg.options.push_back("7");
       RfGainArg.options.push_back("8");
       RfGainArg.options.push_back("9");
       RfGainArg.options.push_back("10");
       RfGainArg.options.push_back("11");
       RfGainArg.options.push_back("12");
       RfGainArg.options.push_back("13");
       RfGainArg.options.push_back("14");
       RfGainArg.options.push_back("15");
       RfGainArg.options.push_back("16");
       RfGainArg.options.push_back("17");
       RfGainArg.options.push_back("18");
       RfGainArg.options.push_back("19");
       RfGainArg.options.push_back("20");
       RfGainArg.options.push_back("21");
       RfGainArg.options.push_back("22");
       RfGainArg.options.push_back("23");
       RfGainArg.options.push_back("24");
       RfGainArg.options.push_back("25");
       RfGainArg.options.push_back("26");
       RfGainArg.options.push_back("27");
       setArgs.push_back(RfGainArg);
    }
    else
    {
       SoapySDR::ArgInfo RfGainArg;
       RfGainArg.key = "rfgain_sel";
       RfGainArg.value = "1";
       RfGainArg.name = "RF Gain Select";
       RfGainArg.description = "RF Gain Select";
       RfGainArg.type = SoapySDR::ArgInfo::STRING;
       RfGainArg.options.push_back("0");
       RfGainArg.options.push_back("1");
       RfGainArg.options.push_back("2");
       RfGainArg.options.push_back("3");
       setArgs.push_back(RfGainArg);
    }
#endif

    SoapySDR::ArgInfo IQcorrArg;
    IQcorrArg.key = "iqcorr_ctrl";
    IQcorrArg.value = "true";
    IQcorrArg.name = "IQ Correction";
    IQcorrArg.description = "IQ Correction Control";
    IQcorrArg.type = SoapySDR::ArgInfo::BOOL;
    setArgs.push_back(IQcorrArg);

    SoapySDR::ArgInfo SetPointArg;
    SetPointArg.key = "agc_setpoint";
    SetPointArg.value = "-30";
    SetPointArg.name = "AGC Setpoint";
    SetPointArg.description = "AGC Setpoint (dBfs)";
    SetPointArg.type = SoapySDR::ArgInfo::INT;
    SetPointArg.range = SoapySDR::Range(-60, 0);
    setArgs.push_back(SetPointArg);

    if (device.hwVer == SDRPLAY_RSP2_ID) // RSP2/RSP2pro
    {
       SoapySDR::ArgInfo ExtRefArg;
       ExtRefArg.key = "extref_ctrl";
       ExtRefArg.value = "true";
       ExtRefArg.name = "ExtRef Enable";
       ExtRefArg.description = "External Reference Control";
       ExtRefArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(ExtRefArg);

       SoapySDR::ArgInfo BiasTArg;
       BiasTArg.key = "biasT_ctrl";
       BiasTArg.value = "true";
       BiasTArg.name = "BiasT Enable";
       BiasTArg.description = "BiasT Control";
       BiasTArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(BiasTArg);

       SoapySDR::ArgInfo RfNotchArg;
       RfNotchArg.key = "rfnotch_ctrl";
       RfNotchArg.value = "true";
       RfNotchArg.name = "RfNotch Enable";
       RfNotchArg.description = "RF Notch Filter Control";
       RfNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(RfNotchArg);
    }
    else if (device.hwVer == SDRPLAY_RSPduo_ID) // RSPduo
    {
       SoapySDR::ArgInfo ExtRefArg;
       ExtRefArg.key = "extref_ctrl";
       ExtRefArg.value = "true";
       ExtRefArg.name = "ExtRef Enable";
       ExtRefArg.description = "External Reference Control";
       ExtRefArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(ExtRefArg);

       SoapySDR::ArgInfo BiasTArg;
       BiasTArg.key = "biasT_ctrl";
       BiasTArg.value = "true";
       BiasTArg.name = "BiasT Enable";
       BiasTArg.description = "BiasT Control";
       BiasTArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(BiasTArg);

       SoapySDR::ArgInfo RfNotchArg;
       RfNotchArg.key = "rfnotch_ctrl";
       RfNotchArg.value = "true";
       RfNotchArg.name = "RfNotch Enable";
       RfNotchArg.description = "RF Notch Filter Control";
       RfNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(RfNotchArg);

       SoapySDR::ArgInfo DabNotchArg;
       DabNotchArg.key = "dabnotch_ctrl";
       DabNotchArg.value = "true";
       DabNotchArg.name = "DabNotch Enable";
       DabNotchArg.description = "DAB Notch Filter Control";
       DabNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(DabNotchArg);
    }
    else if (device.hwVer == SDRPLAY_RSP1A_ID || device.hwVer == SDRPLAY_RSP1B_ID) // RSP1A and RSP1B
    {
       SoapySDR::ArgInfo BiasTArg;
       BiasTArg.key = "biasT_ctrl";
       BiasTArg.value = "true";
       BiasTArg.name = "BiasT Enable";
       BiasTArg.description = "BiasT Control";
       BiasTArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(BiasTArg);

       SoapySDR::ArgInfo RfNotchArg;
       RfNotchArg.key = "rfnotch_ctrl";
       RfNotchArg.value = "true";
       RfNotchArg.name = "RfNotch Enable";
       RfNotchArg.description = "RF Notch Filter Control";
       RfNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(RfNotchArg);

       SoapySDR::ArgInfo DabNotchArg;
       DabNotchArg.key = "dabnotch_ctrl";
       DabNotchArg.value = "true";
       DabNotchArg.name = "DabNotch Enable";
       DabNotchArg.description = "DAB Notch Filter Control";
       DabNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(DabNotchArg);
    }
    else if (device.hwVer == SDRPLAY_RSPdx_ID) // RSPdx
    {
       SoapySDR::ArgInfo BiasTArg;
       BiasTArg.key = "biasT_ctrl";
       BiasTArg.value = "true";
       BiasTArg.name = "BiasT Enable";
       BiasTArg.description = "BiasT Control";
       BiasTArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(BiasTArg);

       SoapySDR::ArgInfo RfNotchArg;
       RfNotchArg.key = "rfnotch_ctrl";
       RfNotchArg.value = "true";
       RfNotchArg.name = "RfNotch Enable";
       RfNotchArg.description = "RF Notch Filter Control";
       RfNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(RfNotchArg);

       SoapySDR::ArgInfo DabNotchArg;
       DabNotchArg.key = "dabnotch_ctrl";
       DabNotchArg.value = "true";
       DabNotchArg.name = "DabNotch Enable";
       DabNotchArg.description = "DAB Notch Filter Control";
       DabNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(DabNotchArg);

       SoapySDR::ArgInfo HDRArg;
       HDRArg.key = "hdr_ctrl";
       HDRArg.value = "true";
       HDRArg.name = "HDR Enable";
       HDRArg.description = "RSPdx HDR Control";
       HDRArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(HDRArg);
    }
    else if (device.hwVer == SDRPLAY_RSPdxR2_ID) // RSPdx-R2
    {
       SoapySDR::ArgInfo BiasTArg;
       BiasTArg.key = "biasT_ctrl";
       BiasTArg.value = "true";
       BiasTArg.name = "BiasT Enable";
       BiasTArg.description = "BiasT Control";
       BiasTArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(BiasTArg);

       SoapySDR::ArgInfo RfNotchArg;
       RfNotchArg.key = "rfnotch_ctrl";
       RfNotchArg.value = "true";
       RfNotchArg.name = "RfNotch Enable";
       RfNotchArg.description = "RF Notch Filter Control";
       RfNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(RfNotchArg);

       SoapySDR::ArgInfo DabNotchArg;
       DabNotchArg.key = "dabnotch_ctrl";
       DabNotchArg.value = "true";
       DabNotchArg.name = "DabNotch Enable";
       DabNotchArg.description = "DAB Notch Filter Control";
       DabNotchArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(DabNotchArg);

       SoapySDR::ArgInfo HDRArg;
       HDRArg.key = "hdr_ctrl";
       HDRArg.value = "true";
       HDRArg.name = "HDR Enable";
       HDRArg.description = "RSPdx HDR Control";
       HDRArg.type = SoapySDR::ArgInfo::BOOL;
       setArgs.push_back(HDRArg);
    }

    return setArgs;
}

void SoapySDRPlay::writeSetting(const std::string &key, const std::string &value)
{
   std::lock_guard <std::mutex> lock(_general_state_mutex);

#ifdef RF_GAIN_IN_MENU
   if (key == "rfgain_sel")
   {
      chParams->tunerParams.gain.LNAstate = static_cast<unsigned char>(stoul(value));
      if (streamActive)
      {
         gr_changed = 0;
         sdrplay_api_ErrT err = sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Tuner_Gr, sdrplay_api_Update_Ext1_None);
         if (err != sdrplay_api_Success)
         {
            SoapySDR_logf(SOAPY_SDR_WARNING, "sdrplay_api_Update(Tuner_Gr) Error: %s", sdrplay_api_GetErrorString(err));
            return;
         }
         for (int i = 0; i < updateTimeout; ++i)
         {
            if (gr_changed != 0) {
               break;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
         }
         if (gr_changed == 0)
         {
            SoapySDR_log(SOAPY_SDR_WARNING, "Gain reduction update timeout.");
         }
      }
   }
   else
#endif
   if (key == "iqcorr_ctrl")
   {
      if (value == "false") chParams->ctrlParams.dcOffset.IQenable = 0;
      else                  chParams->ctrlParams.dcOffset.IQenable = 1;
      chParams->ctrlParams.dcOffset.DCenable = 1;
      if (streamActive)
      {
         sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Ctrl_DCoffsetIQimbalance, sdrplay_api_Update_Ext1_None);
      }
   }
   else if (key == "agc_setpoint")
   {
      chParams->ctrlParams.agc.setPoint_dBfs = stoi(value);
      if (streamActive)
      {
         sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Ctrl_Agc, sdrplay_api_Update_Ext1_None);
      }
   }
   else if (key == "extref_ctrl")
   {
      unsigned char extRef;
      if (value == "false") extRef = 0;
      else                  extRef = 1;
      if (device.hwVer == SDRPLAY_RSP2_ID)
      {
         deviceParams->devParams->rsp2Params.extRefOutputEn = extRef;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp2_ExtRefControl, sdrplay_api_Update_Ext1_None);
         }
      }
      if (device.hwVer == SDRPLAY_RSPduo_ID)
      {
         // can't get extRefOutputEn for RSPduo slaves
         if (deviceParams->devParams)
         {
           deviceParams->devParams->rspDuoParams.extRefOutputEn = extRef;
           if (streamActive)
           {
             sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_RspDuo_ExtRefControl, sdrplay_api_Update_Ext1_None);
           }
         }
      }
   }
   else if (key == "biasT_ctrl")
   {
      unsigned char biasTen;
      if (value == "false") biasTen = 0;
      else                  biasTen = 1;
      if (device.hwVer == SDRPLAY_RSP2_ID)
      {
         chParams->rsp2TunerParams.biasTEnable = biasTen;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp2_BiasTControl, sdrplay_api_Update_Ext1_None);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPduo_ID)
      {
         chParams->rspDuoTunerParams.biasTEnable = biasTen;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_RspDuo_BiasTControl, sdrplay_api_Update_Ext1_None);
         }
      }
      else if (device.hwVer == SDRPLAY_RSP1A_ID || device.hwVer == SDRPLAY_RSP1B_ID)
      {
         chParams->rsp1aTunerParams.biasTEnable = biasTen;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp1a_BiasTControl, sdrplay_api_Update_Ext1_None);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPdx_ID)
      {
         deviceParams->devParams->rspDxParams.biasTEnable = biasTen;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_BiasTControl);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPdxR2_ID)
      {
         deviceParams->devParams->rspDxParams.biasTEnable = biasTen;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_BiasTControl);
         }
      }
   }
   else if (key == "rfnotch_ctrl")
   {
      unsigned char notchEn;
      if (value == "false") notchEn = 0;
      else                  notchEn = 1;
      if (device.hwVer == SDRPLAY_RSP2_ID)
      {
         chParams->rsp2TunerParams.rfNotchEnable = notchEn;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp2_RfNotchControl, sdrplay_api_Update_Ext1_None);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPduo_ID)
      {
        if (device.tuner == sdrplay_api_Tuner_A && chParams->rspDuoTunerParams.tuner1AmPortSel == sdrplay_api_RspDuo_AMPORT_1)
        {
          chParams->rspDuoTunerParams.tuner1AmNotchEnable = notchEn;
          if (streamActive)
          {
             sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_RspDuo_Tuner1AmNotchControl, sdrplay_api_Update_Ext1_None);
          }
        }
        if (chParams->rspDuoTunerParams.tuner1AmPortSel == sdrplay_api_RspDuo_AMPORT_2)
        {
          chParams->rspDuoTunerParams.rfNotchEnable = notchEn;
          if (streamActive)
          {
             sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_RspDuo_RfNotchControl, sdrplay_api_Update_Ext1_None);
          }
        }
      }
      else if (device.hwVer == SDRPLAY_RSP1A_ID || device.hwVer == SDRPLAY_RSP1B_ID)
      {
         deviceParams->devParams->rsp1aParams.rfNotchEnable = notchEn;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp1a_RfNotchControl, sdrplay_api_Update_Ext1_None);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPdx_ID)
      {
         deviceParams->devParams->rspDxParams.rfNotchEnable = notchEn;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_RfNotchControl);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPdxR2_ID)
      {
         deviceParams->devParams->rspDxParams.rfNotchEnable = notchEn;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_RfNotchControl);
         }
      }
   }
   else if (key == "dabnotch_ctrl")
   {
      unsigned char dabNotchEn;
      if (value == "false") dabNotchEn = 0;
      else                  dabNotchEn = 1;
      if (device.hwVer == SDRPLAY_RSPduo_ID)
      {
         chParams->rspDuoTunerParams.rfDabNotchEnable = dabNotchEn;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_RspDuo_RfDabNotchControl, sdrplay_api_Update_Ext1_None);
         }
      }
      if (device.hwVer == SDRPLAY_RSP1A_ID || device.hwVer == SDRPLAY_RSP1B_ID)
      {
         deviceParams->devParams->rsp1aParams.rfDabNotchEnable = dabNotchEn;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_Rsp1a_RfDabNotchControl, sdrplay_api_Update_Ext1_None);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPdx_ID)
      {
         deviceParams->devParams->rspDxParams.rfDabNotchEnable = dabNotchEn;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_RfDabNotchControl);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPdxR2_ID)
      {
         deviceParams->devParams->rspDxParams.rfDabNotchEnable = dabNotchEn;
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_RfDabNotchControl);
         }
      }
   }
   else if (key == "hdr_ctrl")
   {
      unsigned char hdrEn;
      if (value == "false") hdrEn = 0;
      else                  hdrEn = 1;
      if (device.hwVer == SDRPLAY_RSPdx_ID)
      {
         deviceParams->devParams->rspDxParams.hdrEnable = hdrEn;
         SoapySDR_logf(SOAPY_SDR_INFO, "--> rspDxParams.hdrEnable=%d", deviceParams->devParams->rspDxParams.hdrEnable);
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_HdrEnable);
         }
      }
      else if (device.hwVer == SDRPLAY_RSPdxR2_ID)
      {
         deviceParams->devParams->rspDxParams.hdrEnable = hdrEn;
         SoapySDR_logf(SOAPY_SDR_INFO, "--> rspDxParams.hdrEnable=%d", deviceParams->devParams->rspDxParams.hdrEnable);
         if (streamActive)
         {
            sdrplay_api_Update(device.dev, device.tuner, sdrplay_api_Update_None, sdrplay_api_Update_RspDx_HdrEnable);
         }
      }
   }
}

std::string SoapySDRPlay::readSetting(const std::string &key) const
{
    std::lock_guard <std::mutex> lock(_general_state_mutex);

#ifdef RF_GAIN_IN_MENU
    if (key == "rfgain_sel")
    {
       return std::to_string(static_cast<unsigned int>(chParams->tunerParams.gain.LNAstate));
    }
    else
#endif
    if (key == "iqcorr_ctrl")
    {
       if (chParams->ctrlParams.dcOffset.IQenable == 0) return "false";
       else                                             return "true";
    }
    else if (key == "agc_setpoint")
    {
       return std::to_string(chParams->ctrlParams.agc.setPoint_dBfs);
    }
    else if (key == "extref_ctrl")
    {
       unsigned char extRef = 0;
       if (device.hwVer == SDRPLAY_RSP2_ID) extRef = deviceParams->devParams->rsp2Params.extRefOutputEn;
      if (device.hwVer == SDRPLAY_RSPduo_ID) {
         // can't get extRefOutputEn for RSPduo slaves
         if (!deviceParams->devParams) {
            return "unknown";
         }
         extRef = deviceParams->devParams->rspDuoParams.extRefOutputEn;
       }
       if (extRef == 0) return "false";
       else             return "true";
    }
    else if (key == "biasT_ctrl")
    {
       unsigned char biasTen = 0;
       if (device.hwVer == SDRPLAY_RSP2_ID) biasTen = chParams->rsp2TunerParams.biasTEnable;
       else if (device.hwVer == SDRPLAY_RSPduo_ID) biasTen = chParams->rspDuoTunerParams.biasTEnable;
       else if (device.hwVer == SDRPLAY_RSP1A_ID || device.hwVer == SDRPLAY_RSP1B_ID) biasTen = chParams->rsp1aTunerParams.biasTEnable;
       else if (device.hwVer == SDRPLAY_RSPdx_ID) biasTen = deviceParams->devParams->rspDxParams.biasTEnable;
       else if (device.hwVer == SDRPLAY_RSPdxR2_ID) biasTen = deviceParams->devParams->rspDxParams.biasTEnable;
       if (biasTen == 0) return "false";
       else              return "true";
    }
    else if (key == "rfnotch_ctrl")
    {
       unsigned char notchEn = 0;
       if (device.hwVer == SDRPLAY_RSP2_ID) notchEn = chParams->rsp2TunerParams.rfNotchEnable;
       else if (device.hwVer == SDRPLAY_RSPduo_ID)
       {
          if (device.tuner == sdrplay_api_Tuner_A && chParams->rspDuoTunerParams.tuner1AmPortSel == sdrplay_api_RspDuo_AMPORT_1)
          {
             notchEn = chParams->rspDuoTunerParams.tuner1AmNotchEnable;
          }
          if (chParams->rspDuoTunerParams.tuner1AmPortSel == sdrplay_api_RspDuo_AMPORT_2)
          {
             notchEn = chParams->rspDuoTunerParams.rfNotchEnable;
          }
       }
       else if (device.hwVer == SDRPLAY_RSP1A_ID || device.hwVer == SDRPLAY_RSP1B_ID) notchEn = deviceParams->devParams->rsp1aParams.rfNotchEnable;
       else if (device.hwVer == SDRPLAY_RSPdx_ID) notchEn = deviceParams->devParams->rspDxParams.rfNotchEnable;
       else if (device.hwVer == SDRPLAY_RSPdxR2_ID) notchEn = deviceParams->devParams->rspDxParams.rfNotchEnable;
       if (notchEn == 0) return "false";
       else              return "true";
    }
    else if (key == "dabnotch_ctrl")
    {
       unsigned char dabNotchEn = 0;
       if (device.hwVer == SDRPLAY_RSPduo_ID) dabNotchEn = chParams->rspDuoTunerParams.rfDabNotchEnable;
       else if (device.hwVer == SDRPLAY_RSP1A_ID || device.hwVer == SDRPLAY_RSP1B_ID) dabNotchEn = deviceParams->devParams->rsp1aParams.rfDabNotchEnable;
       else if (device.hwVer == SDRPLAY_RSPdx_ID) dabNotchEn = deviceParams->devParams->rspDxParams.rfDabNotchEnable;
       else if (device.hwVer == SDRPLAY_RSPdxR2_ID) dabNotchEn = deviceParams->devParams->rspDxParams.rfDabNotchEnable;
       if (dabNotchEn == 0) return "false";
       else                 return "true";
    }
    else if (key == "hdr_ctrl")
    {
       unsigned char hdrEn = 0;
       if (device.hwVer == SDRPLAY_RSPdx_ID) hdrEn = deviceParams->devParams->rspDxParams.hdrEnable;
       if (device.hwVer == SDRPLAY_RSPdxR2_ID) hdrEn = deviceParams->devParams->rspDxParams.hdrEnable;
       if (hdrEn == 0) return "false";
       else            return "true";
    }

    // SoapySDR_logf(SOAPY_SDR_WARNING, "Unknown setting '%s'", key.c_str());
    return "";
}

void SoapySDRPlay::selectDevice(const std::string &serial,
                                const std::string &mode,
                                const std::string &antenna)
{
    serNo = serial;
    rspDeviceId = serial;
    if (mode == "SL") {
        rspDeviceId += "/S";
    }

    sdrplay_api_TunerSelectT tuner;
    sdrplay_api_RspDuoModeT rspDuoMode;
    double rspDuoSampleFreq = 0.0;
    if (mode.empty())
    {
        tuner = sdrplay_api_Tuner_Neither;
        rspDuoMode = sdrplay_api_RspDuoMode_Unknown;
        rspDuoSampleFreq = 0.0;
    }
    else if (mode == "ST")
    {
        tuner = sdrplay_api_Tuner_A;
        rspDuoMode = sdrplay_api_RspDuoMode_Single_Tuner;
        rspDuoSampleFreq = 0.0;
    }
    else if (mode == "DT")
    {
        tuner = sdrplay_api_Tuner_Both;
        rspDuoMode = sdrplay_api_RspDuoMode_Dual_Tuner;
        rspDuoSampleFreq = 6000000;
    }
    else if (mode == "MA")
    {
        tuner = sdrplay_api_Tuner_A;
        rspDuoMode = sdrplay_api_RspDuoMode_Master;
        rspDuoSampleFreq = 6000000;
    }
    else if (mode == "MA8")
    {
        tuner = sdrplay_api_Tuner_A;
        rspDuoMode = sdrplay_api_RspDuoMode_Master;
        rspDuoSampleFreq = 8000000;
    }
    else if (mode == "SL")
    {
        tuner = sdrplay_api_Tuner_Neither;
        rspDuoMode = sdrplay_api_RspDuoMode_Slave;
    }
    else
    {
        throw std::runtime_error("sdrplay RSPduo mode is invalid");
    }

    // if an antenna is specified, select the RSPduo tuner based on it
    if (!(rspDuoMode == sdrplay_api_RspDuoMode_Unknown ||
          rspDuoMode == sdrplay_api_RspDuoMode_Dual_Tuner))
    {
        if (!antenna.empty())
        {
            if (antenna == "Tuner 1 50 ohm") {
                tuner = sdrplay_api_Tuner_A;
            } else if (antenna == "Tuner 1 Hi-Z") {
                tuner = sdrplay_api_Tuner_A;
            } else if (antenna == "Tuner 2 50 ohm") {
                tuner = sdrplay_api_Tuner_B;
            } else {
                throw std::runtime_error("invalid RSPduo antenna selected");
            }
        }
    }

    selectDevice(tuner, rspDuoMode, rspDuoSampleFreq, nullptr);

    return;
}

void SoapySDRPlay::selectDevice()
{
    if (selectedRSPDevices.count(rspDeviceId) > 0 &&
        selectedRSPDevices.at(rspDeviceId) != &device) {
        selectDevice(device.tuner, device.rspDuoMode, device.rspDuoSampleFreq,
                     deviceParams);
    }
    return;
}

void SoapySDRPlay::selectDevice(sdrplay_api_TunerSelectT tuner,
                                sdrplay_api_RspDuoModeT rspDuoMode,
                                double rspDuoSampleFreq,
                                sdrplay_api_DeviceParamsT *thisDeviceParams)
{
    sdrplay_api_ErrT err;
    if (selectedRSPDevices.count(rspDeviceId)) {
        sdrplay_api_DeviceT *currDevice = selectedRSPDevices.at(rspDeviceId);
        selectedRSPDevices.erase(rspDeviceId);
        err = sdrplay_api_ReleaseDevice(currDevice);
        if (err != sdrplay_api_Success)
        {
            SoapySDR_logf(SOAPY_SDR_ERROR, "ReleaseDevice Error: %s", sdrplay_api_GetErrorString(err));
            throw std::runtime_error("ReleaseDevice() failed");
        }
    }

    // save all the device configuration so we can put it back later on
    bool hasDevParams = false;
    bool hasRxChannelA = false;
    bool hasRxChannelB = false;
    sdrplay_api_DevParamsT devParams;
    sdrplay_api_RxChannelParamsT rxChannelA;
    sdrplay_api_RxChannelParamsT rxChannelB;
    if (thisDeviceParams)
    {
        hasDevParams = thisDeviceParams->devParams;
        hasRxChannelA = thisDeviceParams->rxChannelA;
        hasRxChannelB = thisDeviceParams->rxChannelB;
        if (hasDevParams) devParams = *thisDeviceParams->devParams;
        if (hasRxChannelA) rxChannelA = *thisDeviceParams->rxChannelA;
        if (hasRxChannelB) rxChannelB = *thisDeviceParams->rxChannelB;
    }

    // retrieve hwVer and serNo by API
    unsigned int nDevs = 0;

    sdrplay_api_LockDeviceApi();
    sdrplay_api_DeviceT rspDevs[SDRPLAY_MAX_DEVICES];
    sdrplay_api_GetDevices(&rspDevs[0], &nDevs, SDRPLAY_MAX_DEVICES);

    unsigned devIdx = SDRPLAY_MAX_DEVICES;
    for (unsigned int i = 0; i < nDevs; i++)
    {
        if (not rspDevs[i].valid) continue;
        if (rspDevs[i].SerNo == serNo) devIdx = i;
    }
    if (devIdx == SDRPLAY_MAX_DEVICES) {
        SoapySDR_log(SOAPY_SDR_ERROR, "no sdrplay device matches");
        throw std::runtime_error("no sdrplay device matches");
    }

    device = rspDevs[devIdx];
    hwVer = device.hwVer;

    SoapySDR_logf(SOAPY_SDR_INFO, "devIdx: %d", devIdx);
    SoapySDR_logf(SOAPY_SDR_INFO, "SerNo: %s", device.SerNo);
    SoapySDR_logf(SOAPY_SDR_INFO, "hwVer: %d", device.hwVer);

    if (hwVer == SDRPLAY_RSPduo_ID && rspDuoMode != sdrplay_api_RspDuoMode_Slave)
    {
        if ((rspDuoMode & device.rspDuoMode) != rspDuoMode)
        {
            throw std::runtime_error("sdrplay RSPduo mode not available");
        }
        else
        {
            device.rspDuoMode = rspDuoMode;
        }
        if ((tuner & device.tuner) != tuner)
        {
            throw std::runtime_error("sdrplay RSPduo tuner not available");
        }
        else
        {
            device.tuner = tuner;
        }
        if (rspDuoSampleFreq != 0)
        {
            device.rspDuoSampleFreq = rspDuoSampleFreq;
        }
    }
    else if (hwVer == SDRPLAY_RSPduo_ID && rspDuoMode == sdrplay_api_RspDuoMode_Slave)
    {
        if (rspDuoMode != device.rspDuoMode)
        {
            throw std::runtime_error("sdrplay RSPduo slave mode not available");
        }
        if (tuner != sdrplay_api_Tuner_Neither && tuner != device.tuner)
        {
            throw std::runtime_error("sdrplay RSPduo tuner not available in slave mode");
        }
        if (rspDuoSampleFreq != 0 && rspDuoSampleFreq != device.rspDuoSampleFreq)
        {
            throw std::runtime_error("sdrplay RSPduo sample rate not available in slace mode");
        }
    }
    else
    {
        if (rspDuoMode != sdrplay_api_RspDuoMode_Unknown || tuner != sdrplay_api_Tuner_Neither)
        {
            throw std::runtime_error("sdrplay RSP does not support RSPduo mode or tuner");
        }
    }

    SoapySDR_logf(SOAPY_SDR_INFO, "rspDuoMode: %d", device.rspDuoMode);
    SoapySDR_logf(SOAPY_SDR_INFO, "tuner: %d", device.tuner);
    SoapySDR_logf(SOAPY_SDR_INFO, "rspDuoSampleFreq: %lf", device.rspDuoSampleFreq);

    err = sdrplay_api_SelectDevice(&device);
    if (err != sdrplay_api_Success)
    {
        sdrplay_api_UnlockDeviceApi();
        SoapySDR_logf(SOAPY_SDR_ERROR, "SelectDevice Error: %s", sdrplay_api_GetErrorString(err));
        throw std::runtime_error("SelectDevice() failed");
    }
    selectedRSPDevices[rspDeviceId] = &device;

    sdrplay_api_UnlockDeviceApi();

    // Enable (= sdrplay_api_DbgLvl_Verbose) API calls tracing,
    // but only for debug purposes due to its performance impact.
    sdrplay_api_DebugEnable(device.dev, sdrplay_api_DbgLvl_Disable);
    //sdrplay_api_DebugEnable(device.dev, sdrplay_api_DbgLvl_Verbose);

    err = sdrplay_api_GetDeviceParams(device.dev, &deviceParams);
    if (err != sdrplay_api_Success)
    {
        SoapySDR_logf(SOAPY_SDR_ERROR, "GetDeviceParams Error: %s", sdrplay_api_GetErrorString(err));
        throw std::runtime_error("GetDeviceParams() failed");
    }

    if (thisDeviceParams)
    {
        if (hasDevParams) *deviceParams->devParams = devParams;
        if (hasRxChannelA) *deviceParams->rxChannelA = rxChannelA;
        if (hasRxChannelB) *deviceParams->rxChannelB = rxChannelB;
    }

    chParams = device.tuner == sdrplay_api_Tuner_B ? deviceParams->rxChannelB : deviceParams->rxChannelA;

    return;
}

void SoapySDRPlay::releaseDevice()
{
    sdrplay_api_ErrT err;
    if (selectedRSPDevices.count(rspDeviceId)) {
        sdrplay_api_DeviceT *currDevice = selectedRSPDevices.at(rspDeviceId);
        if (currDevice != &device) {
            // nothing to do - we are good
            return;
        }
        selectedRSPDevices.erase(rspDeviceId);
        err = sdrplay_api_ReleaseDevice(currDevice);
        if (err != sdrplay_api_Success)
        {
            SoapySDR_logf(SOAPY_SDR_ERROR, "ReleaseDevice Error: %s", sdrplay_api_GetErrorString(err));
            throw std::runtime_error("ReleaseDevice() failed");
        }
    }

    return;
}

#ifdef SHOW_SERIAL_NUMBER_IN_MESSAGES
void SoapySDRPlay::SoapySDR_log(const SoapySDRLogLevel logLevel,
                                const char *message) const
{
    std::string message_with_info_string = "[S/N=" + serNo + "] - " + message;
    const char *message_with_info = message_with_info_string.c_str();
    ::SoapySDR_log(logLevel, message_with_info);
}

void SoapySDRPlay::SoapySDR_logf(const SoapySDRLogLevel logLevel,
                                const char *format, ...) const
{
    va_list argList;
    va_start(argList, format);
    std::string format_with_info_string = "[S/N=" + serNo + "] - " + format;
    const char *format_with_info = format_with_info_string.c_str();
    ::SoapySDR_vlogf(logLevel, format_with_info, argList);
    va_end(argList);
}
#endif
