mcbts: Add multi-ARFCN radio support

Add new radio interface "radioInterfaceMulti" for multi-carrier
support.

Only USRP B200/B210 devices are supported because of sample
rate requirements (3.2 Msps).

Only 4 SPS operation Tx/RX is supported.

8-PSK is supported.

Other options may be added at a later time

Signed-off-by: Tom Tsou <tom.tsou@ettus.com>
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index 52ec995..667217f 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -67,6 +67,7 @@
 	$(COMMON_SOURCES) \
 	Resampler.cpp \
 	radioInterfaceResamp.cpp \
+	radioInterfaceMulti.cpp \
 	radioInterfaceDiversity.cpp
 
 bin_PROGRAMS = osmo-trx
diff --git a/Transceiver52M/UHDDevice.cpp b/Transceiver52M/UHDDevice.cpp
index 5ffaa62..af9fc98 100644
--- a/Transceiver52M/UHDDevice.cpp
+++ b/Transceiver52M/UHDDevice.cpp
@@ -35,6 +35,7 @@
 #endif
 
 #define B2XX_CLK_RT      26e6
+#define B2XX_MCBTS_CLK_RT   3.2e6
 #define E1XX_CLK_RT      52e6
 #define B100_BASE_RT     400000
 #define USRP2_BASE_RT    390625
@@ -61,6 +62,7 @@
 	B100,
 	B200,
 	B210,
+	B2XX_MCBTS,
 	E1XX,
 	E3XX,
 	X3XX,
@@ -110,6 +112,7 @@
 	{ B200,  4, 1, B2XX_TIMING_4SPS, "B200 4/1 Tx/Rx SPS" },
 	{ B210,  1, 1, B2XX_TIMING_1SPS, "B210 1 SPS" },
 	{ B210,  4, 1, B2XX_TIMING_4SPS, "B210 4/1 Tx/Rx SPS" },
+	{ B2XX_MCBTS, 4, 4, 1.07188e-4, "B200/B210 4 SPS Multi-ARFCN" },
 	{ E1XX,  1, 1, 9.5192e-5, "E1XX 1 SPS" },
 	{ E1XX,  4, 1, 6.5571e-5, "E1XX 4/1 Tx/Rx SPS" },
 	{ E3XX,  1, 1, 1.84616e-4, "E3XX 1 SPS" },
@@ -150,9 +153,20 @@
 		return -9999.99;
 	}
 
+
 	if ((sps != 4) && (sps != 1))
 		return -9999.99;
 
+	if (iface == RadioDevice::MULTI_ARFCN) {
+		switch (type) {
+		case B2XX_MCBTS:
+			return  4 * MCBTS_SPACING;
+		default:
+			LOG(ALERT) << "Invalid device combination";
+			return -9999.99;
+		}
+	}
+
 	switch (type) {
 	case USRP2:
 	case X3XX:
@@ -542,12 +556,15 @@
 	if ((dev_type == B200) || (dev_type == B210) || (dev_type == E3XX)) {
 		if (set_master_clk(B2XX_CLK_RT) < 0)
 			return -1;
-	}
-	else if (dev_type == E1XX) {
+	} else if (dev_type == E1XX) {
 		if (set_master_clk(E1XX_CLK_RT) < 0)
 			return -1;
+	} else if (dev_type == B2XX_MCBTS) {
+		if (set_master_clk(B2XX_MCBTS_CLK_RT) < 0)
+			return -1;
 	}
 
+
 	// Set sample rates
 	try {
 		usrp_dev->set_tx_rate(tx_rate);
@@ -574,6 +591,9 @@
 
 double uhd_device::setTxGain(double db, size_t chan)
 {
+	if (iface == MULTI_ARFCN)
+		chan = 0;
+
 	if (chan >= tx_gains.size()) {
 		LOG(ALERT) << "Requested non-existent channel" << chan;
 		return 0.0f;
@@ -620,6 +640,9 @@
 
 double uhd_device::getRxGain(size_t chan)
 {
+	if (iface == MULTI_ARFCN)
+		chan = 0;
+
 	if (chan >= rx_gains.size()) {
 		LOG(ALERT) << "Requested non-existent channel " << chan;
 		return 0.0f;
@@ -762,11 +785,24 @@
 	}
 
 	// Verify and set channels
-	if ((dev_type == B210) && (chans == 2)) {
-	} else if ((dev_type == UMTRX) && (chans == 2)) {
-		uhd::usrp::subdev_spec_t subdev_spec(swap_channels?"B:0 A:0":"A:0 B:0");
-		usrp_dev->set_tx_subdev_spec(subdev_spec);
-		usrp_dev->set_rx_subdev_spec(subdev_spec);
+	if (iface == MULTI_ARFCN) {
+		if ((dev_type != B200) && (dev_type != B210)) {
+			LOG(ALERT) << "Unsupported device configuration";
+			return -1;
+		}
+
+		dev_type = B2XX_MCBTS;
+		chans = 1;
+	} else if (chans == 2) {
+		if (dev_type == B210) {
+		} else if (dev_type == UMTRX) {
+			uhd::usrp::subdev_spec_t subdev_spec(swap_channels?"B:0 A:0":"A:0 B:0");
+			usrp_dev->set_tx_subdev_spec(subdev_spec);
+			usrp_dev->set_rx_subdev_spec(subdev_spec);
+		} else {
+			LOG(ALERT) << "Invalid device configuration";
+			return -1;
+		}
 	} else if (chans != 1) {
 		LOG(ALERT) << "Invalid channel combination for device";
 		return -1;
@@ -839,6 +875,8 @@
 
 	if (iface == DIVERSITY)
 		return DIVERSITY;
+	if (iface == MULTI_ARFCN)
+		return MULTI_ARFCN;
 
 	switch (dev_type) {
 	case B100:
@@ -1295,7 +1333,7 @@
  */
 TIMESTAMP uhd_device::initialWriteTimestamp()
 {
-	if (rx_sps == tx_sps)
+	if ((iface == MULTI_ARFCN) || (rx_sps == tx_sps))
 		return ts_initial;
 	else
 		return ts_initial * tx_sps;
diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp
index 9dcda5f..ac9cc48 100644
--- a/Transceiver52M/osmo-trx.cpp
+++ b/Transceiver52M/osmo-trx.cpp
@@ -74,6 +74,7 @@
 	bool extref;
 	Transceiver::FillerType filler;
 	bool diversity;
+	bool mcbts;
 	double offset;
 	double rssi_offset;
 	bool swap_channels;
@@ -127,7 +128,7 @@
  */
 bool trx_setup_config(struct trx_config *config)
 {
-	std::string refstr, fillstr, divstr, edgestr;
+	std::string refstr, fillstr, divstr, mcstr, edgestr;
 
 	if (!testConfig())
 		return false;
@@ -163,13 +164,29 @@
 			config->diversity = DEFAULT_DIVERSITY;
 	}
 
-	/* Diversity only supported on 2 channels */
-	if (config->diversity)
+	if (!config->chans)
+		config->chans = DEFAULT_CHANS;
+
+	if (config->mcbts && ((config->chans < 0) || (config->chans > 5))) {
+		std::cout << "Unsupported number of channels" << std::endl;
+		return false;
+	}
+
+	/* Diversity only supported on 2 channels without multi-carrier */
+	if (config->diversity && config->mcbts) {
+		std::cout << "Multi-carrier diversity unsupported" << std::endl;
+		return false;
+	}
+	if (config->diversity && (config->chans != 2)) {
+		std::cout << "Setting channels to 2 for diversity" << std::endl;
 		config->chans = 2;
+	}
 
 	edgestr = config->edge ? "Enabled" : "Disabled";
 	refstr = config->extref ? "Enabled" : "Disabled";
 	divstr = config->diversity ? "Enabled" : "Disabled";
+	mcstr = config->mcbts ? "Enabled" : "Disabled";
+
 	switch (config->filler) {
 	case Transceiver::FILLER_DUMMY:
 		fillstr = "Dummy bursts";
@@ -200,6 +217,7 @@
 	ost << "   EDGE support............ " << edgestr << std::endl;
 	ost << "   External Reference...... " << refstr << std::endl;
 	ost << "   C0 Filler Table......... " << fillstr << std::endl;
+	ost << "   Multi-Carrier........... " << mcstr << std::endl;
 	ost << "   Diversity............... " << divstr << std::endl;
 	ost << "   Tuning offset........... " << config->offset << std::endl;
 	ost << "   RSSI to dBm offset...... " << config->rssi_offset << std::endl;
@@ -235,6 +253,10 @@
 		radio = new RadioInterfaceDiversity(usrp, config->tx_sps,
 						    config->chans);
 		break;
+	case RadioDevice::MULTI_ARFCN:
+		radio = new RadioInterfaceMulti(usrp, config->tx_sps,
+						config->rx_sps, config->chans);
+		break;
 	default:
 		LOG(ALERT) << "Unsupported radio interface configuration";
 		return NULL;
@@ -309,6 +331,7 @@
 		"  -p    Base port number\n"
 		"  -e    Enable EDGE receiver\n"
 		"  -d    Enable dual channel diversity receiver\n"
+		"  -m    Enable multi-ARFCN transceiver (default=disabled)\n"
 		"  -x    Enable external 10 MHz reference\n"
 		"  -s    Tx samples-per-symbol (1 or 4)\n"
 		"  -b    Rx samples-per-symbol (1 or 4)\n"
@@ -334,13 +357,14 @@
 	config->rach_delay = 0;
 	config->extref = false;
 	config->filler = Transceiver::FILLER_ZERO;
+	config->mcbts = false;
 	config->diversity = false;
 	config->offset = 0.0;
 	config->rssi_offset = 0.0;
 	config->swap_channels = false;
 	config->edge = false;
 
-	while ((option = getopt(argc, argv, "ha:l:i:p:c:dxfo:s:b:r:A:R:Se")) != -1) {
+	while ((option = getopt(argc, argv, "ha:l:i:p:c:dmxfo:s:b:r:A:R:Se")) != -1) {
 		switch (option) {
 		case 'h':
 			print_help();
@@ -361,6 +385,9 @@
 		case 'c':
 			config->chans = atoi(optarg);
 			break;
+		case 'm':
+			config->mcbts = true;
+			break;
 		case 'd':
 			config->diversity = true;
 			break;
@@ -403,8 +430,8 @@
 		}
 	}
 
-	/* Force 4 SPS for EDGE configurations */
-	if (config->edge) {
+	/* Force 4 SPS for EDGE or multi-ARFCN configurations */
+	if ((config->edge) || (config->mcbts)) {
 		config->tx_sps = 4;
 		config->rx_sps = 4;
 	}
@@ -412,7 +439,8 @@
 	if (config->edge && (config->filler == Transceiver::FILLER_NORM_RAND))
 		config->filler = Transceiver::FILLER_EDGE_RAND;
 
-	if ((config->tx_sps != 1) && (config->tx_sps != 4)) {
+	if ((config->tx_sps != 1) && (config->tx_sps != 4) &&
+	    (config->rx_sps != 1) && (config->rx_sps != 4)) {
 		printf("Unsupported samples-per-symbol %i\n\n", config->tx_sps);
 		print_help();
 		exit(0);
@@ -455,6 +483,9 @@
 	srandom(time(NULL));
 
 	/* Create the low level device object */
+	if (config.mcbts)
+		iface = RadioDevice::MULTI_ARFCN;
+
 	usrp = RadioDevice::make(config.tx_sps, config.rx_sps, iface,
 				 config.chans, config.offset);
 	type = usrp->open(config.dev_args, config.extref, config.swap_channels);
diff --git a/Transceiver52M/radioDevice.h b/Transceiver52M/radioDevice.h
index a992483..142fcf6 100644
--- a/Transceiver52M/radioDevice.h
+++ b/Transceiver52M/radioDevice.h
@@ -23,6 +23,7 @@
 #endif
 
 #define GSMRATE       (1625e3/6)
+#define MCBTS_SPACING  800000.0
 
 /** a 64-bit virtual timestamp for radio data */
 typedef unsigned long long TIMESTAMP;
@@ -39,6 +40,7 @@
     NORMAL,
     RESAMP_64M,
     RESAMP_100M,
+    MULTI_ARFCN,
     DIVERSITY,
   };
 
diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h
index 1f225a2..b63cc4c 100644
--- a/Transceiver52M/radioInterface.h
+++ b/Transceiver52M/radioInterface.h
@@ -22,6 +22,8 @@
 #include "radioClock.h"
 #include "radioBuffer.h"
 #include "Resampler.h"
+#include "Channelizer.h"
+#include "Synthesis.h"
 
 static const unsigned gSlotLen = 148;      ///< number of symbols per slot, not counting guard periods
 
@@ -101,7 +103,7 @@
   RadioClock* getClock(void) { return &mClock;};
 
   /** set transmit frequency */
-  bool tuneTx(double freq, size_t chan = 0);
+  virtual bool tuneTx(double freq, size_t chan = 0);
 
   /** set receive frequency */
   virtual bool tuneRx(double freq, size_t chan = 0);
@@ -164,6 +166,34 @@
   void close();
 };
 
+class RadioInterfaceMulti : public RadioInterface {
+private:
+  bool pushBuffer();
+  void pullBuffer();
+
+  signalVector *outerSendBuffer;
+  signalVector *outerRecvBuffer;
+  std::vector<signalVector *> history;
+  std::vector<bool> active;
+
+  Resampler *dnsampler;
+  Resampler *upsampler;
+  Channelizer *channelizer;
+  Synthesis *synthesis;
+
+public:
+  RadioInterfaceMulti(RadioDevice* radio, size_t tx_sps,
+                      size_t rx_sps, size_t chans = 1);
+  ~RadioInterfaceMulti();
+
+  bool init(int type);
+  void close();
+
+  bool tuneTx(double freq, size_t chan);
+  bool tuneRx(double freq, size_t chan);
+  double setRxGain(double dB, size_t chan);
+};
+
 class RadioInterfaceDiversity : public RadioInterface {
 public:
   RadioInterfaceDiversity(RadioDevice* wRadio,
diff --git a/Transceiver52M/radioInterfaceMulti.cpp b/Transceiver52M/radioInterfaceMulti.cpp
new file mode 100644
index 0000000..ba81fe1
--- /dev/null
+++ b/Transceiver52M/radioInterfaceMulti.cpp
@@ -0,0 +1,387 @@
+/*
+ * Multi-carrier radio interface
+ *
+ * Copyright (C) 2016 Ettus Research LLC 
+ *
+ * Author: Tom Tsou <tom.tsou@ettus.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include <radioInterface.h>
+#include <Logger.h>
+
+#include "Resampler.h"
+
+extern "C" {
+#include "convert.h"
+}
+
+/* Resampling parameters for 64 MHz clocking */
+#define RESAMP_INRATE			65
+#define RESAMP_OUTRATE			(96 / 2)
+
+/* Universal resampling parameters */
+#define NUMCHUNKS				24
+
+#define MCHANS					4
+
+RadioInterfaceMulti::RadioInterfaceMulti(RadioDevice *radio, size_t tx_sps,
+					 size_t rx_sps, size_t chans)
+	: RadioInterface(radio, tx_sps, rx_sps, chans),
+	  outerSendBuffer(NULL), outerRecvBuffer(NULL),
+	  dnsampler(NULL), upsampler(NULL), channelizer(NULL), synthesis(NULL)
+{
+}
+
+RadioInterfaceMulti::~RadioInterfaceMulti()
+{
+	close();
+}
+
+void RadioInterfaceMulti::close()
+{
+	delete outerSendBuffer;
+	delete outerRecvBuffer;
+	delete dnsampler;
+	delete upsampler;
+	delete channelizer;
+	delete synthesis;
+
+	outerSendBuffer = NULL;
+	outerRecvBuffer = NULL;
+	dnsampler = NULL;
+	upsampler = NULL;
+	channelizer = NULL;
+	synthesis = NULL;
+
+	mReceiveFIFO.resize(0);
+	powerScaling.resize(0);
+	history.resize(0);
+	active.resize(0);
+
+	RadioInterface::close();
+}
+
+static int getLogicalChan(size_t pchan, size_t chans)
+{
+	switch (chans) {
+	case 1:
+		if (pchan == 0)
+			return 0;
+		else
+			return -1;
+		break;
+	case 2:
+		if (pchan == 0)
+			return 0;
+		if (pchan == 3)
+			return 1;
+		else
+			return -1;
+		break;
+	case 3:
+		if (pchan == 1)
+			return 0;
+		if (pchan == 0)
+			return 1;
+		if (pchan == 3)
+			return 2;
+		else
+			return -1;
+		break;
+	default:
+		break;
+	};
+
+	return -1;
+}
+
+static int getFreqShift(size_t chans)
+{
+	switch (chans) {
+	case 1:
+		return 0;
+	case 2:
+		return 0;
+	case 3:
+		return 1;
+	default:
+		break;
+	};
+
+	return -1;
+}
+
+/* Initialize I/O specific objects */
+bool RadioInterfaceMulti::init(int type)
+{
+	float cutoff = 1.0f;
+	size_t inchunk = 0, outchunk = 0;
+
+	if (mChans > MCHANS - 1) {
+		LOG(ALERT) << "Invalid channel configuration " << mChans;
+		return false;
+	}
+
+	close();
+
+	sendBuffer.resize(mChans);
+	recvBuffer.resize(mChans);
+	convertSendBuffer.resize(1);
+	convertRecvBuffer.resize(1);
+
+	mReceiveFIFO.resize(mChans);
+	powerScaling.resize(mChans);
+	history.resize(mChans);
+	active.resize(MCHANS, false);
+
+	inchunk = RESAMP_INRATE * 4;
+	outchunk = RESAMP_OUTRATE * 4;
+
+	if (inchunk  * NUMCHUNKS < 625 * 2) {
+		LOG(ALERT) << "Invalid inner chunk size " << inchunk;
+		return false;
+	}
+
+	dnsampler = new Resampler(RESAMP_INRATE, RESAMP_OUTRATE);
+	if (!dnsampler->init(1.0)) {
+		LOG(ALERT) << "Rx resampler failed to initialize";
+		return false;
+	}
+
+	upsampler = new Resampler(RESAMP_OUTRATE, RESAMP_INRATE);
+	if (!upsampler->init(cutoff)) {
+		LOG(ALERT) << "Tx resampler failed to initialize";
+		return false;
+	}
+
+	channelizer = new Channelizer(MCHANS, outchunk);
+	if (!channelizer->init()) {
+		LOG(ALERT) << "Rx channelizer failed to initialize";
+		return false;
+	}
+
+	synthesis = new Synthesis(MCHANS, outchunk);
+	if (!synthesis->init()) {
+		LOG(ALERT) << "Tx synthesis filter failed to initialize";
+		return false;
+	}
+
+	/*
+	 * Allocate high and low rate buffers. The high rate receive
+	 * buffer and low rate transmit vectors feed into the resampler
+	 * and requires headroom equivalent to the filter length. Low
+	 * rate buffers are allocated in the main radio interface code.
+	 */
+	for (size_t i = 0; i < mChans; i++) {
+		sendBuffer[i] = new RadioBuffer(NUMCHUNKS, inchunk,
+					        upsampler->len(), true);
+		recvBuffer[i] = new RadioBuffer(NUMCHUNKS, inchunk,
+		                                0, false);
+		history[i] = new signalVector(dnsampler->len());
+
+		synthesis->resetBuffer(i);
+	}
+
+	outerSendBuffer = new signalVector(synthesis->outputLen());
+	outerRecvBuffer = new signalVector(channelizer->inputLen());
+
+	convertSendBuffer[0] = new short[2 * synthesis->outputLen()];
+	convertRecvBuffer[0] = new short[2 * channelizer->inputLen()];
+
+	/* Configure channels */
+	switch (mChans) {
+	case 1:
+		active[0] = true;
+		break;
+	case 2:
+		active[0] = true;
+		active[3] = true;
+		break;
+	case 3:
+		active[0] = true;
+		active[1] = true;
+		active[3] = true;
+		break;
+	default:
+		LOG(ALERT) << "Unsupported channel combination";
+		return false;
+	}
+
+	return true;
+}
+
+/* Receive a timestamped chunk from the device */
+void RadioInterfaceMulti::pullBuffer()
+{
+	bool local_underrun;
+	size_t num;
+	float *buf;
+
+	if (recvBuffer[0]->getFreeSegments() <= 0)
+		return;
+
+	/* Outer buffer access size is fixed */
+	num = mRadio->readSamples(convertRecvBuffer,
+				  outerRecvBuffer->size(),
+				  &overrun,
+				  readTimestamp,
+				  &local_underrun);
+	if (num != channelizer->inputLen()) {
+		LOG(ALERT) << "Receive error " << num << ", " << channelizer->inputLen();
+		return;
+	}
+
+	convert_short_float((float *) outerRecvBuffer->begin(),
+			    convertRecvBuffer[0], 2 * outerRecvBuffer->size());
+
+	underrun |= local_underrun;
+	readTimestamp += num;
+
+	channelizer->rotate((float *) outerRecvBuffer->begin(),
+			    outerRecvBuffer->size());
+
+	for (size_t pchan = 0; pchan < MCHANS; pchan++) {
+		if (!active[pchan])
+			continue;
+
+		int lchan = getLogicalChan(pchan, mChans);
+		if (lchan < 0) {
+			LOG(ALERT) << "Invalid logical channel " << pchan;
+			continue;
+		}
+
+		/*
+		 * Update history by writing into the head portion of the
+		 * channelizer output buffer. For this to work, filter length of
+		 * the polyphase channelizer partition filter should be equal to
+		 * or larger than the resampling filter.
+		 */
+		buf = channelizer->outputBuffer(pchan);
+		size_t cLen = channelizer->outputLen();
+		size_t hLen = dnsampler->len();
+		size_t hSize = 2 * hLen * sizeof(float);
+
+		memcpy(&buf[2 * -hLen], history[lchan]->begin(), hSize);
+		memcpy(history[lchan]->begin(), &buf[2 * (cLen - hLen)], hSize);
+
+		float *wr_segment = recvBuffer[lchan]->getWriteSegment();
+
+		/* Write to the end of the inner receive buffer */
+		if (!dnsampler->rotate(channelizer->outputBuffer(pchan),
+				       channelizer->outputLen(),
+				       wr_segment,
+				       recvBuffer[lchan]->getSegmentLen())) {
+			LOG(ALERT) << "Sample rate upsampling error";
+		}
+	}
+}
+
+/* Send a timestamped chunk to the device */
+bool RadioInterfaceMulti::pushBuffer()
+{
+	if (sendBuffer[0]->getAvailSegments() <= 0)
+		return false;
+
+	for (size_t pchan = 0; pchan < MCHANS; pchan++) {
+		if (!active[pchan]) {
+			synthesis->resetBuffer(pchan);
+			continue;
+		}
+
+		int lchan = getLogicalChan(pchan, mChans);
+		if (lchan < 0) {
+			LOG(ALERT) << "Invalid logical channel " << pchan;
+			continue;
+		}
+
+		if (!upsampler->rotate(sendBuffer[lchan]->getReadSegment(),
+				       sendBuffer[lchan]->getSegmentLen(),
+				       synthesis->inputBuffer(pchan),
+				       synthesis->inputLen())) {
+			LOG(ALERT) << "Sample rate downsampling error";
+		}
+	}
+
+	synthesis->rotate((float *) outerSendBuffer->begin(),
+			  outerSendBuffer->size());
+
+	convert_float_short(convertSendBuffer[0],
+			    (float *) outerSendBuffer->begin(),
+			    1.0 / (float) mChans, 2 * outerSendBuffer->size());
+
+	size_t num = mRadio->writeSamples(convertSendBuffer,
+					  outerSendBuffer->size(),
+					  &underrun,
+					  writeTimestamp);
+	if (num != outerSendBuffer->size()) {
+		LOG(ALERT) << "Transmit error " << num;
+	}
+
+	writeTimestamp += num;
+
+	return true;
+}
+
+/* Frequency comparison limit */
+#define FREQ_DELTA_LIMIT		10.0
+
+static bool fltcmp(double a, double b)
+{
+	return fabs(a - b) < FREQ_DELTA_LIMIT ? true : false;
+}
+
+bool RadioInterfaceMulti::tuneTx(double freq, size_t chan)
+{
+  if (chan >= mChans)
+    return false;
+
+  double shift = (double) getFreqShift(mChans);
+
+  if (!chan)
+    return mRadio->setTxFreq(freq + shift * MCBTS_SPACING);
+
+  double center = mRadio->getTxFreq();
+  if (!fltcmp(freq, center + (double) (chan - shift) * MCBTS_SPACING))
+    return false;
+
+  return true;
+}
+
+bool RadioInterfaceMulti::tuneRx(double freq, size_t chan)
+{
+  if (chan >= mChans)
+    return false;
+
+  double shift = (double) getFreqShift(mChans);
+
+  if (!chan)
+    return mRadio->setRxFreq(freq + shift * MCBTS_SPACING);
+
+  double center = mRadio->getRxFreq();
+  if (!fltcmp(freq, center + (double) (chan - shift) * MCBTS_SPACING))
+    return false;
+
+  return true;
+}
+
+double RadioInterfaceMulti::setRxGain(double db, size_t chan)
+{
+  if (!chan)
+    return mRadio->setRxGain(db);
+  else
+    return mRadio->getRxGain();
+}
diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp
index 7a032b3..6495b1e 100644
--- a/Transceiver52M/sigProcLib.cpp
+++ b/Transceiver52M/sigProcLib.cpp
@@ -80,7 +80,6 @@
 #define DOWNSAMPLE_OUT_LEN	156
 
 static Resampler *dnsampler = NULL;
-static signalVector *dnsampler_in = NULL;
 
 /*
  * RACH and midamble correlation waveforms. Store the buffer separately
@@ -163,7 +162,6 @@
   delete GSMPulse1;
   delete GSMPulse4;
   delete dnsampler;
-  delete dnsampler_in;
 
   GMSKRotation1 = NULL;
   GMSKRotation4 = NULL;
@@ -1936,13 +1934,15 @@
 
 signalVector *downsampleBurst(signalVector &burst)
 {
-  size_t ilen = DOWNSAMPLE_IN_LEN, olen = DOWNSAMPLE_OUT_LEN;
+  signalVector *in, *out;
 
-  signalVector *out = new signalVector(olen);
-  memcpy(dnsampler_in->begin(), burst.begin(), ilen * 2 * sizeof(float));
+  in = new signalVector(DOWNSAMPLE_IN_LEN, dnsampler->len());
+  out = new signalVector(DOWNSAMPLE_OUT_LEN);
+  memcpy(in->begin(), burst.begin(), DOWNSAMPLE_IN_LEN * 2 * sizeof(float));
 
-  dnsampler->rotate((float *) dnsampler_in->begin(), ilen,
-                    (float *) out->begin(), olen);
+  dnsampler->rotate((float *) in->begin(), DOWNSAMPLE_IN_LEN,
+                    (float *) out->begin(), DOWNSAMPLE_OUT_LEN);
+  delete in;
   return out;
 };
 
@@ -2128,8 +2128,6 @@
     goto fail;
   }
 
-  dnsampler_in = new signalVector(DOWNSAMPLE_IN_LEN, dnsampler->len());
-
   return true;
 
 fail: