ms: add sigproclib demod

This is basically a fixed version of ttsous ancient branch that can be
used instead of the VA. Required config option part of a future
patchset.

Change-Id: I6558992bd69f18526be5ebe7d424ca00ceb67772
diff --git a/GSM/GSMCommon.cpp b/GSM/GSMCommon.cpp
index 5e9e4ae..a9e2bb1 100644
--- a/GSM/GSMCommon.cpp
+++ b/GSM/GSMCommon.cpp
@@ -55,12 +55,15 @@
 };
 
 const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000");
+const BitVector GSM::gDummyBurstTSC("01110001011100010111000101");
 
 /* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
 const BitVector GSM::gRACHSynchSequenceTS0("01001011011111111001100110101010001111000");  /* GSM, GMSK (default) */
 const BitVector GSM::gRACHSynchSequenceTS1("01010100111110001000011000101111001001101");  /* EGPRS, 8-PSK */
 const BitVector GSM::gRACHSynchSequenceTS2("11101111001001110101011000001101101110111");  /* EGPRS, GMSK */
 
+const BitVector GSM::gSCHSynchSequence("1011100101100010000001000000111100101101010001010111011000011011");
+
 //                               |-head-||---------midamble----------------------||--------------data----------------||t|
 const BitVector GSM::gRACHBurst("0011101001001011011111111001100110101010001111000110111101111110000111001001010110011000");
 
diff --git a/GSM/GSMCommon.h b/GSM/GSMCommon.h
index 48723b4..aa059c2 100644
--- a/GSM/GSMCommon.h
+++ b/GSM/GSMCommon.h
@@ -52,11 +52,16 @@
 
 /** C0T0 filler burst, GSM 05.02, 5.2.6 */
 extern const BitVector gDummyBurst;
+extern const BitVector gDummyBurstTSC;
 
 /** Random access burst synch. sequence */
 extern const BitVector gRACHSynchSequenceTS0;
 extern const BitVector gRACHSynchSequenceTS1;
 extern const BitVector gRACHSynchSequenceTS2;
+
+/** Synchronization burst sync sequence */
+extern const BitVector gSCHSynchSequence;
+
 /** Random access burst synch. sequence, GSM 05.02 5.2.7 */
 extern const BitVector gRACHBurst;
 
diff --git a/Transceiver52M/Resampler.cpp b/Transceiver52M/Resampler.cpp
index 910c7ff..841c3a9 100644
--- a/Transceiver52M/Resampler.cpp
+++ b/Transceiver52M/Resampler.cpp
@@ -32,7 +32,7 @@
 #define M_PI			3.14159265358979323846264338327f
 #endif
 
-#define MAX_OUTPUT_LEN		4096
+#define MAX_OUTPUT_LEN		4096*4
 
 using namespace std;
 
diff --git a/Transceiver52M/ms/ms_rx_lower.cpp b/Transceiver52M/ms/ms_rx_lower.cpp
index c2adda3..d894e96 100644
--- a/Transceiver52M/ms/ms_rx_lower.cpp
+++ b/Transceiver52M/ms/ms_rx_lower.cpp
@@ -19,6 +19,8 @@
  *
  */
 
+#include "sigProcLib.h"
+#include "signalVector.h"
 #include <atomic>
 #include <cassert>
 #include <complex>
@@ -155,12 +157,12 @@
 	auto current_gsm_time = timekeeper.gsmtime();
 	const auto buf_len = is_first_sch_acq ? SCH_LEN_SPS : ONE_TS_BURST_LEN;
 	const auto which_in_buffer = is_first_sch_acq ? first_sch_buf : burst_copy_buffer;
+	memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer));
+#if 1
 	const auto which_out_buffer = is_first_sch_acq ? sch_acq_buffer : &sch_acq_buffer[40 * 2];
 	const auto ss = reinterpret_cast<std::complex<float> *>(which_out_buffer);
 	std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
-
 	int start;
-	memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer));
 	convert_and_scale(which_out_buffer, which_in_buffer, buf_len * 2, 1.f / float(rxFullScale));
 	if (is_first_sch_acq) {
 		float max_corr = 0;
@@ -173,9 +175,22 @@
 	detect_burst_nb(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits);
 
 	auto sch_decode_success = decode_sch(sch_demod_bits, is_first_sch_acq);
+#if 0
+	auto burst = new signalVector(buf_len, 50);
+	const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL;
+	struct estim_burst_params ebp;
 
+	// scale like uhd, +-2k -> +-32k
+	convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR);
+
+	auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp);
+
+	int howmuchdelay = ebp.toa * 4;
+	std::cerr << "ooffs: " << howmuchdelay << " " << std::endl;
+	std::cerr << "voffs: " << start << " " << sch_decode_success << std::endl;
+#endif
 	if (sch_decode_success) {
-		const auto ts_offset_symb = 0;
+		const auto ts_offset_symb = 4;
 		if (is_first_sch_acq) {
 			// update ts to first sample in sch buffer, to allow delay calc for current ts
 			first_sch_ts_start = first_sch_buf_rcv_ts + start - (ts_offset_symb * 4) - 1;
@@ -190,6 +205,97 @@
 		DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << start << " " << current_gsm_time.FN()
 			 << ":" << current_gsm_time.TN() << std::endl;
 	}
+#else
+	const auto ts_offset_symb = 4;
+	auto burst = new signalVector(buf_len, 50);
+	const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL;
+	struct estim_burst_params ebp;
+
+	// scale like uhd, +-2k -> +-32k
+	convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR);
+
+	auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp);
+
+	int howmuchdelay = ebp.toa * 4;
+
+	if (!rv) {
+		delete burst;
+		DBGLG() << "SCH : \x1B[31m detect fail \033[0m NOOOOOOOOOOOOOOOOOO toa:" << ebp.toa << " "
+			<< current_gsm_time.FN() << ":" << current_gsm_time.TN() << std::endl;
+		return false;
+	}
+
+	SoftVector *bits;
+	if (is_first_sch_acq) {
+		// can't be legit with a buf size spanning _at least_ one SCH but delay that implies partial sch burst
+		if (howmuchdelay < 0 || (buf_len - howmuchdelay) < ONE_TS_BURST_LEN) {
+			delete burst;
+			return false;
+		}
+
+		struct estim_burst_params ebp2;
+		// auto sch_chunk = new signalVector(ONE_TS_BURST_LEN, 50);
+		// auto sch_chunk_start = sch_chunk->begin();
+		// memcpy(sch_chunk_start, sch_buf_f.data() + howmuchdelay, sizeof(std::complex<float>) * ONE_TS_BURST_LEN);
+
+		auto delay = delayVector(burst, NULL, -howmuchdelay);
+
+		scaleVector(*delay, (complex)1.0 / ebp.amp);
+
+		auto rv2 = detectSCHBurst(*delay, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp2);
+		DBGLG() << "FIRST SCH : " << (rv2 ? "yes " : "   ") << "Timing offset     " << ebp2.toa << " symbols"
+			<< std::endl;
+
+		bits = demodAnyBurst(*delay, SCH, 4, &ebp2);
+		delete delay;
+	} else {
+		bits = demodAnyBurst(*burst, SCH, 4, &ebp);
+	}
+
+	delete burst;
+
+	// clamp to +-1.5 because +-127 softbits scaled by 64 after -0.5 can be at most +-1.5
+	clamp_array(bits->begin(), 148, 1.5f);
+
+	float_to_sbit(&bits->begin()[0], (signed char *)&sch_demod_bits[0], 62, 148);
+	// float_to_sbit(&bits->begin()[106], &data[39], 62, 39);
+
+	if (decode_sch((char *)sch_demod_bits, is_first_sch_acq)) {
+		auto current_gsm_time_updated = timekeeper.gsmtime();
+		if (is_first_sch_acq) {
+			// update ts to first sample in sch buffer, to allow delay calc for current ts
+			first_sch_ts_start = first_sch_buf_rcv_ts + howmuchdelay - (ts_offset_symb * 4);
+		} else {
+			// continuous sch tracking, only update if off too much
+			auto diff = [](float x, float y) { return x > y ? x - y : y - x; };
+
+			auto d = diff(ebp.toa, ts_offset_symb);
+			if (abs(d) > 0.3) {
+				if (ebp.toa < ts_offset_symb)
+					ebp.toa = d;
+				else
+					ebp.toa = -d;
+				temp_ts_corr_offset += ebp.toa * 4;
+
+				DBGLG() << "offs: " << ebp.toa << " " << temp_ts_corr_offset << std::endl;
+			}
+		}
+
+		auto a = gsm_sch_check_fn(current_gsm_time_updated.FN() - 1);
+		auto b = gsm_sch_check_fn(current_gsm_time_updated.FN());
+		auto c = gsm_sch_check_fn(current_gsm_time_updated.FN() + 1);
+		DBGLG() << "L SCH : Timing offset     " << rv << " " << ebp.toa << " " << a << b << c << "fn "
+			<< current_gsm_time_updated.FN() << ":" << current_gsm_time_updated.TN() << std::endl;
+
+		delete bits;
+		return true;
+	} else {
+		DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << ebp.toa << " " << current_gsm_time.FN()
+			 << ":" << current_gsm_time.TN() << std::endl;
+	}
+
+	delete bits;
+#endif
 	return false;
 }
 
diff --git a/Transceiver52M/ms/ms_upper.cpp b/Transceiver52M/ms/ms_upper.cpp
index c5664cd..2e8bc11 100644
--- a/Transceiver52M/ms/ms_upper.cpp
+++ b/Transceiver52M/ms/ms_upper.cpp
@@ -199,6 +199,7 @@
 		return true;
 	}
 
+#if 1
 	convert_and_scale(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale));
 
 	pow = energyDetect(sv, 20 * 4 /*sps*/);
@@ -232,6 +233,42 @@
 		// 	detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
 #endif
 	}
+#else
+
+	// lower layer sch detection offset, easy to verify by just printing the detected value using both the va+sigproc code.
+	convert_and_scale(ss + 16, e.burst, ONE_TS_BURST_LEN * 2, 15);
+
+	pow = energyDetect(sv, 20 * 4 /*sps*/);
+	if (pow < -1) {
+		LOG(ALERT) << "Received empty burst";
+		return false;
+	}
+
+	avg = sqrt(pow);
+
+	/* Detect normal or RACH bursts */
+	CorrType type = CorrType::TSC;
+	struct estim_burst_params ebp;
+	auto rc = detectAnyBurst(sv, mTSC, 3, 4, type, 48, &ebp);
+	if (rc > 0) {
+		type = (CorrType)rc;
+	}
+
+	if (rc < 0) {
+		std::cerr << "UR : \x1B[31m rx fail \033[0m @ toa:" << ebp.toa << " " << e.gsmts.FN() << ":"
+			  << e.gsmts.TN() << std::endl;
+		return false;
+	}
+	SoftVector *bits = demodAnyBurst(sv, type, 4, &ebp);
+
+	SoftVector::const_iterator burstItr = bits->begin();
+	// invert and fix to +-127 sbits
+	for (int ii = 0; ii < 148; ii++) {
+		demodded_softbits[ii] = *burstItr++ > 0.0f ? -127 : 127;
+	}
+	delete bits;
+
+#endif
 	RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
 	// FIXME: properly handle offset, sch/nb alignment diff? handled by lower anyway...
 	timingOffset = (int)round(0);
diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp
index df87f94..5fac365 100644
--- a/Transceiver52M/sigProcLib.cpp
+++ b/Transceiver52M/sigProcLib.cpp
@@ -129,6 +129,8 @@
 static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
 static CorrelationSequence *gEdgeMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
 static CorrelationSequence *gRACHSequences[] = {NULL,NULL,NULL};
+static CorrelationSequence *gSCHSequence = NULL;
+static CorrelationSequence *gDummySequence = NULL;
 static PulseSequence *GSMPulse1 = NULL;
 static PulseSequence *GSMPulse4 = NULL;
 
@@ -151,6 +153,12 @@
     gRACHSequences[i] = NULL;
   }
 
+  delete gSCHSequence;
+  gSCHSequence = NULL;
+
+  delete gDummySequence;
+  gDummySequence = NULL;
+
   delete GMSKRotation1;
   delete GMSKReverseRotation1;
   delete GMSKRotation4;
@@ -315,6 +323,7 @@
     append = true;
     break;
   case CUSTOM:
+  // FIXME: x->getstart?
     if (start < h->size() - 1) {
       head = h->size() - start;
       append = true;
@@ -1289,6 +1298,77 @@
   return status;
 }
 
+static bool generateDummyMidamble(int sps)
+{
+  bool status = true;
+  float toa;
+  complex *data = NULL;
+  signalVector *autocorr = NULL, *midamble = NULL;
+  signalVector *midMidamble = NULL, *_midMidamble = NULL;
+
+  delete gDummySequence;
+
+  /* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */
+  midMidamble = modulateBurst(gDummyBurstTSC.segment(5,16), 0, sps, true);
+  if (!midMidamble)
+    return false;
+
+  /* Simulated receive sequence is pulse shaped */
+  midamble = modulateBurst(gDummyBurstTSC, 0, sps, false);
+  if (!midamble) {
+    status = false;
+    goto release;
+  }
+
+  // NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst,
+  //       the ideal TSC has an + 180 degree phase shift,
+  //       due to the pi/2 frequency shift, that
+  //       needs to be accounted for.
+  //       26-midamble is 61 symbols into burst, has +90 degree phase shift.
+  scaleVector(*midMidamble, complex(-1.0, 0.0));
+  scaleVector(*midamble, complex(0.0, 1.0));
+
+  conjugateVector(*midMidamble);
+
+  /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
+  data = (complex *) convolve_h_alloc(midMidamble->size());
+  _midMidamble = new signalVector(data, 0, midMidamble->size(), convolve_h_alloc, free);
+  _midMidamble->setAligned(true);
+  midMidamble->copyTo(*_midMidamble);
+
+  autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY);
+  if (!autocorr) {
+    status = false;
+    goto release;
+  }
+
+  gDummySequence = new CorrelationSequence;
+  gDummySequence->sequence = _midMidamble;
+  gDummySequence->gain = peakDetect(*autocorr, &toa, NULL);
+
+  /* For 1 sps only
+   *     (Half of correlation length - 1) + midpoint of pulse shape + remainder
+   *     13.5 = (16 / 2 - 1) + 1.5 + (26 - 10) / 2
+   */
+  if (sps == 1)
+    gDummySequence->toa = toa - 13.5;
+  else
+    gDummySequence->toa = 0;
+
+release:
+  delete autocorr;
+  delete midamble;
+  delete midMidamble;
+
+  if (!status) {
+    delete _midMidamble;
+    free(data);
+    gDummySequence = NULL;
+  }
+
+  return status;
+}
+
 static CorrelationSequence *generateEdgeMidamble(int tsc)
 {
   complex *data = NULL;
@@ -1384,6 +1464,69 @@
   return status;
 }
 
+bool generateSCHSequence(int sps)
+{
+  bool status = true;
+  float toa;
+  complex *data = NULL;
+  signalVector *autocorr = NULL;
+  signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL;
+
+  delete gSCHSequence;
+
+  seq0 = modulateBurst(gSCHSynchSequence, 0, sps, false);
+  if (!seq0)
+    return false;
+
+  seq1 = modulateBurst(gSCHSynchSequence, 0, sps, true);
+  if (!seq1) {
+    status = false;
+    goto release;
+  }
+
+  conjugateVector(*seq1);
+
+  /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
+  data = (complex *) convolve_h_alloc(seq1->size());
+  _seq1 = new signalVector(data, 0, seq1->size(), convolve_h_alloc, free);
+  _seq1->setAligned(true);
+  seq1->copyTo(*_seq1);
+
+  autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY);
+  if (!autocorr) {
+    status = false;
+    goto release;
+  }
+
+  gSCHSequence = new CorrelationSequence;
+  gSCHSequence->sequence = _seq1;
+  gSCHSequence->buffer = data;
+  gSCHSequence->gain = peakDetect(*autocorr, &toa, NULL);
+
+  /* For 1 sps only
+   *     (Half of correlation length - 1) + midpoint of pulse shaping filer
+   *     20.5 = (64 / 2 - 1) + 1.5
+   */
+  if (sps == 1)
+    gSCHSequence->toa = toa - 32.5;
+  else
+    gSCHSequence->toa = 0.0;
+
+release:
+  delete autocorr;
+  delete seq0;
+  delete seq1;
+
+  if (!status) {
+    delete _seq1;
+    free(data);
+    gSCHSequence = NULL;
+  }
+
+  return status;
+}
+
+
 /*
  * Peak-to-average computation +/- range from peak in symbols
  */
@@ -1441,14 +1584,15 @@
   return energy/windowLength;
 }
 
-static signalVector *downsampleBurst(const signalVector &burst)
+static signalVector *downsampleBurst(const signalVector &burst, int in_len = DOWNSAMPLE_IN_LEN,
+				     int out_len = DOWNSAMPLE_OUT_LEN)
 {
-  signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len());
-  signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN);
-  burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN);
+  signalVector in(in_len, dnsampler->len());
+  // gSCHSequence->sequence->size(), ensure next conv has no realloc
+  signalVector *out = new signalVector(out_len, 64);
+  burst.copyToSegment(in, 0, in_len);
 
-  if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN,
-                        (float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) {
+  if (dnsampler->rotate((float *)in.begin(), in_len, (float *)out->begin(), out_len) < 0) {
     delete out;
     out = NULL;
   }
@@ -1469,6 +1613,12 @@
   /* Integer position where the sequence starts */
   const int ps = start + 1 - N + (int)roundf(toa);
 
+  if(ps < 0) // might be -22 for toa 40 with N=64, if off by a lot during sch ms sync
+    return 0;
+
+  if (ps + N > (int)burst->size())
+	  return 0;
+
   /* Estimate Signal power */
   S = 0.0f;
   for (int i=0, j=ps; i<(int)N; i++,j++)
@@ -1652,6 +1802,80 @@
   return rc;
 }
 
+int detectSCHBurst(signalVector &burst,
+		    float thresh,
+		    int sps,
+        sch_detect_type state, struct estim_burst_params *ebp)
+{
+  int rc, start, target, head, tail, len;
+  complex _amp;
+  CorrelationSequence *sync;
+
+  if ((sps != 1) && (sps != 4))
+    return -1;
+
+  target = 3 + 39 + 64;
+
+  switch (state) {
+  case sch_detect_type::SCH_DETECT_NARROW:
+    head = 4;
+    tail = 4;
+    break;
+  case sch_detect_type::SCH_DETECT_BUFFER:
+	  target = 1;
+	  head = 0;
+	  tail = (12 * 8 * 625) / 4; // 12 frames, downsampled /4 to 1 sps
+	  break;
+  case sch_detect_type::SCH_DETECT_FULL:
+  default:
+    head = target - 1;
+    tail = 39 + 3 + 9;
+    break;
+  }
+
+  start = (target - head) * 1 - 1;
+  len = (head + tail) * 1;
+  sync = gSCHSequence;
+  signalVector corr(len);
+
+  signalVector *dec = downsampleBurst(burst, len * 4, len);
+  rc = detectBurst(*dec, corr, sync, thresh, 1, start, len, ebp);
+  delete dec;
+
+  if (rc < 0) {
+    return -1;
+  } else if (!rc) {
+      ebp->amp = 0.0f;
+      ebp->toa = 0.0f;
+    return 0;
+  }
+
+  if (state == sch_detect_type::SCH_DETECT_BUFFER)
+	  ebp->toa = ebp->toa - (3 + 39 + 64);
+  else {
+	  /* Subtract forward search bits from delay */
+	  ebp->toa = ebp->toa - head;
+  }
+
+  return rc;
+}
+
+static int detectDummyBurst(const signalVector &burst, float threshold,
+                               int sps, unsigned max_toa, struct estim_burst_params *ebp)
+{
+  int rc, target, head, tail;
+  CorrelationSequence *sync;
+
+  target = 3 + 58 + 16 + 5;
+  head = 10;
+  tail = 6 + max_toa;
+  sync = gDummySequence;
+
+  ebp->tsc = 0;
+  rc = detectGeneralBurst(burst, threshold, sps, target, head, tail, sync, ebp);
+  return rc;
+}
+
 /*
  * Normal burst detection
  *
@@ -1670,7 +1894,7 @@
     return -SIGERR_UNSUPPORTED;
 
   target = 3 + 58 + 16 + 5;
-  head = 6;
+  head = 10;
   tail = 6 + max_toa;
   sync = gMidambles[tsc];
 
@@ -1719,6 +1943,9 @@
   case RACH:
     rc = detectRACHBurst(burst, threshold, sps, max_toa, type == EXT_RACH, ebp);
     break;
+  case IDLE:
+    rc = detectDummyBurst(burst, threshold, sps, max_toa, ebp);
+    break;
   default:
     LOG(ERR) << "Invalid correlation type";
   }
@@ -1921,6 +2148,9 @@
   generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1);
   generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1);
 
+  generateSCHSequence(1);
+  generateDummyMidamble(1);
+
   for (int tsc = 0; tsc < 8; tsc++) {
     generateMidamble(1, tsc);
     gEdgeMidambles[tsc] = generateEdgeMidamble(tsc);
diff --git a/Transceiver52M/sigProcLib.h b/Transceiver52M/sigProcLib.h
index 0c3c7c6..39c8ddd 100644
--- a/Transceiver52M/sigProcLib.h
+++ b/Transceiver52M/sigProcLib.h
@@ -31,6 +31,7 @@
   TSC,         ///< timeslot should contain a normal burst
   EXT_RACH,    ///< timeslot should contain an extended access burst
   RACH,        ///< timeslot should contain an access burst
+  SCH,
   EDGE,        ///< timeslot should contain an EDGE burst
   IDLE         ///< timeslot is an idle (or dummy) burst
 };
@@ -93,6 +94,8 @@
 void scaleVector(signalVector &x,
                  complex scale);
 
+signalVector *delayVector(const signalVector *in, signalVector *out, float delay);
+
 /**
         Rough energy estimator.
         @param rxBurst A GSM burst.
@@ -133,6 +136,17 @@
                    unsigned max_toa,
                    struct estim_burst_params *ebp);
 
+enum class sch_detect_type {
+	SCH_DETECT_FULL,
+	SCH_DETECT_NARROW,
+	SCH_DETECT_BUFFER,
+};
+
+int detectSCHBurst(signalVector &rxBurst,
+                    float detectThreshold,
+                    int sps,
+                    sch_detect_type state, struct estim_burst_params *ebp);
+
 /** Demodulate burst basde on type and output soft bits */
 SoftVector *demodAnyBurst(const signalVector &burst, CorrType type,
                           int sps, struct estim_burst_params *ebp);