From 0be1fa93a68c90f52150c07c9b6aa1921fb2be3e Mon Sep 17 00:00:00 2001
From: Dominik Smith <d.smith@gsi.de>
Date: Tue, 5 Jul 2022 15:16:26 +0200
Subject: [PATCH] Started to implement new Much unpacker in algo namespace.

---
 algo/CMakeLists.txt                       |   2 +
 algo/detectors/much/MuchReadoutConfig.cxx | 323 ++++++++++++++++++++++
 algo/detectors/much/MuchReadoutConfig.h   | 129 +++++++++
 algo/detectors/much/UnpackMuch.cxx        | 163 +++++++++++
 algo/detectors/much/UnpackMuch.h          | 146 ++++++++++
 5 files changed, 763 insertions(+)
 create mode 100644 algo/detectors/much/MuchReadoutConfig.cxx
 create mode 100644 algo/detectors/much/MuchReadoutConfig.h
 create mode 100644 algo/detectors/much/UnpackMuch.cxx
 create mode 100644 algo/detectors/much/UnpackMuch.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 623ce77119..965746fe0d 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -8,6 +8,8 @@ set(SRCS
   trigger/TimeClusterTrigger.cxx
   detectors/sts/StsReadoutConfig.cxx
   detectors/sts/UnpackSts.cxx
+  detectors/much/MuchReadoutConfig.cxx
+  detectors/much/UnpackMuch.cxx
  )
 
 add_library(Algo SHARED ${SRCS})
diff --git a/algo/detectors/much/MuchReadoutConfig.cxx b/algo/detectors/much/MuchReadoutConfig.cxx
new file mode 100644
index 0000000000..e96079548c
--- /dev/null
+++ b/algo/detectors/much/MuchReadoutConfig.cxx
@@ -0,0 +1,323 @@
+/* Copyright (C) 2017-2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau, Ajit Kumar, Florian Uhlig [committer] */
+
+#include "MuchReadoutConfig.h"
+
+//#include "FairDetParIo.h"
+//#include "FairParIo.h"
+//#include "FairParamList.h"
+#include <Logger.h>
+
+//#include "TMath.h"
+//#include "TString.h"
+
+using namespace std;
+
+namespace cbm::algo
+{
+
+  /*
+// -----   Public method clear   -------------------------------------------
+void MuchReadoutConfig::clear()
+{
+  status = false;
+  resetInputVersions();
+}
+// -------------------------------------------------------------------------
+
+// -------------------------------------------------------------------------
+
+void MuchReadoutConfig::putParams(FairParamList* l)
+{
+  if (!l) return;
+
+  l->add("NrOfDpbs", fuNrOfDpbs);
+  l->add("DbpIdArray", fiDbpIdArray);
+  l->add("CrobActiveFlag", fiCrobActiveFlag);
+  l->add("NrOfFebsInGemA", fuFebsInGemA);
+  l->add("nFebsIdsArrayA", fnFebsIdsArrayGemA);
+  l->add("NrOfFebsInGemB", fuFebsInGemB);
+  l->add("nFebsIdsArrayB", fnFebsIdsArrayGemB);
+  l->add("NrOfFebsInRpc", fuFebsInRpc);
+  l->add("nFebsIdsArrayRpc", fnFebsIdsArrayRpc);
+  l->add("ChannelsToPadXA", fChannelsToPadXA);
+  l->add("ChannelsToPadYA", fChannelsToPadYA);
+  l->add("ChannelsToPadXB", fChannelsToPadXB);
+  l->add("ChannelsToPadYB", fChannelsToPadYB);
+  l->add("ChannelsToPadXRpc", fChannelsToPadXRpc);
+  l->add("ChannelsToPadYRpc", fChannelsToPadYRpc);
+  l->add("RealX", fRealX);
+  l->add("PadSize", fRealPadSize);
+}
+
+// -------------------------------------------------------------------------
+
+bool MuchReadoutConfig::getParams(FairParamList* l)
+{
+
+  if (!l) return false;
+
+  if (!l->fill("NrOfDpbs", &fuNrOfDpbs)) return false;
+
+  fiDbpIdArray.Set(fuNrOfDpbs);
+  if (!l->fill("DbpIdArray", &fiDbpIdArray)) return false;
+
+  fiCrobActiveFlag.Set(fuNrOfDpbs * kuNbCrobsPerDpb);
+  if (!l->fill("CrobActiveFlag", &fiCrobActiveFlag)) return false;
+
+  if (!l->fill("NrOfFebsInGemA", &fuFebsInGemA)) return false;
+
+  fnFebsIdsArrayGemA.Set(GetNrOfFebsInGemA());
+  if (!l->fill("nFebsIdsArrayA", &fnFebsIdsArrayGemA)) return false;
+
+  if (!l->fill("NrOfFebsInGemB", &fuFebsInGemB)) return false;
+
+  fnFebsIdsArrayGemB.Set(GetNrOfFebsInGemB());
+  if (!l->fill("nFebsIdsArrayB", &fnFebsIdsArrayGemB)) return false;
+
+  if (!l->fill("NrOfFebsInRpc", &fuFebsInRpc)) return false;
+
+  fnFebsIdsArrayRpc.Set(GetNrOfFebsInRpc());
+  if (!l->fill("nFebsIdsArrayRpc", &fnFebsIdsArrayRpc)) return false;
+
+  fChannelsToPadXA.Set(GetNrOfFebs() * kuNbChanPerAsic);
+  if (!l->fill("ChannelsToPadXA", &fChannelsToPadXA)) return false;
+
+  fChannelsToPadYA.Set(GetNrOfFebs() * kuNbChanPerAsic);
+  if (!l->fill("ChannelsToPadYA", &fChannelsToPadYA)) return false;
+
+  fChannelsToPadXB.Set(GetNrOfFebs() * kuNbChanPerAsic);
+  if (!l->fill("ChannelsToPadXB", &fChannelsToPadXB)) return false;
+
+  fChannelsToPadYB.Set(GetNrOfFebs() * kuNbChanPerAsic);
+  if (!l->fill("ChannelsToPadYB", &fChannelsToPadYB)) return false;
+
+  fChannelsToPadXRpc.Set(GetNrOfFebsInRpc() * kuNbChanPerAsic);
+  if (!l->fill("ChannelsToPadXRpc", &fChannelsToPadXRpc)) return false;
+
+  fChannelsToPadYRpc.Set(GetNrOfFebsInRpc() * kuNbChanPerAsic);
+  if (!l->fill("ChannelsToPadYRpc", &fChannelsToPadYRpc)) return false;
+
+  fRealX.Set(2232);  // Number of Sectors in one GEM Module
+  if (!l->fill("RealX", &fRealX)) return false;
+
+  fRealPadSize.Set(2232);  // Number of Sectors in one GEM Module
+  if (!l->fill("PadSize", &fRealPadSize)) return false;
+
+  return true;
+}
+*/
+
+  // -------------------------------------------------------------------------
+  int16_t MuchReadoutConfig::ElinkIdxToFebIdx(uint16_t uElink)
+  {
+    if (uElink < kuNbElinksPerCrob) return kiCrobMapElinkFebIdx[uElink];
+    else {
+      LOG(warning) << "MuchReadoutConfig::ElinkIdxToFebIdx => Index out of bound, "
+                   << "Elink is " << uElink << " returning crazy value!";
+      return -1;
+    }
+  }
+
+  // -------------------------------------------------------------------------
+  uint16_t MuchReadoutConfig::GetDpbId(uint16_t uDpbIdx)
+  {
+    if (uDpbIdx < fuNrOfDpbs) return fiDbpIdArray[uDpbIdx];
+    else {
+      LOG(warning) << "MuchReadoutConfig::GetDpbId => Index out of bound, "
+                   << "DPB Id is " << std::hex << uDpbIdx << " returning crazy value!";
+      return 0xFFFFFFFF;
+    }
+  }
+
+  bool MuchReadoutConfig::IsCrobActive(uint16_t uDpbIdx, uint16_t uCrobIdx)
+  {
+    if (uDpbIdx < fuNrOfDpbs) {
+      if (uCrobIdx < kuNbCrobsPerDpb) return 0 < fiCrobActiveFlag[uDpbIdx * kuNbCrobsPerDpb + uCrobIdx] ? true : false;
+      else {
+        LOG(warning) << "MuchReadoutConfig::IsCrobActive => Crob Index out of bound, "
+                     << "returning default inactive!";
+        return false;
+      }
+    }
+    else {
+      LOG(warning) << "MuchReadoutConfig::IsCrobActive => Dpb Index out of bound, "
+                   << "returning default inactive!";
+      return false;
+    }
+  }
+
+  bool MuchReadoutConfig::IsFebActive(uint16_t uFebInSystIdx)
+  {
+
+    if (uFebInSystIdx < GetNrOfFebs()) {
+      /// Always return true for now
+      return true;
+    }
+    else {
+      LOG(warning) << "MuchReadoutConfig::IsFebActive => Feb Index out of bound, "
+                   << "returning default inactive!";
+      return false;
+    }
+  }
+
+  bool MuchReadoutConfig::IsFebActive(uint16_t uDpbIdx, uint16_t uCrobIdx, uint16_t uFebIdx)
+  {
+    if (uDpbIdx < fuNrOfDpbs) {
+      if (uCrobIdx < kuNbCrobsPerDpb) {
+        if (uFebIdx < kuNbFebsPerCrob) {
+          uint16_t uIdx = (uDpbIdx * kuNbCrobsPerDpb + uCrobIdx) * kuNbFebsPerCrob + uFebIdx;
+          return IsFebActive(uIdx);
+        }
+        else {
+          LOG(warning) << "MuchReadoutConfig::IsFebActive => Feb Index out of bound, "
+                       << "returning default inactive!";
+          return false;
+        }
+      }
+      else {
+        LOG(warning) << "MuchReadoutConfig::IsFebActive => Crob Index out of bound, "
+                     << "returning default inactive!";
+        return false;
+      }
+    }
+    else {
+      LOG(warning) << "MuchReadoutConfig::IsFebActive => Dpb Index out of bound, "
+                   << "returning default inactive!";
+      return false;
+    }
+  }
+
+  int8_t MuchReadoutConfig::GetPadXA(uint8_t febid, uint8_t channelid)
+  {
+    if (fChannelsToPadXA.size() <= static_cast<int16_t>((febid * kuNbChanPerAsic) + channelid)) {
+      LOG(debug) << "MuchReadoutConfig::GetPadXA => Index out of bounds: " << ((febid * kuNbChanPerAsic) + channelid)
+                 << " VS " << fChannelsToPadXA.size() << " (" << febid << " and " << channelid << ")";
+      return -2;
+    }
+    return fChannelsToPadXA[(febid * kuNbChanPerAsic) + channelid];
+  }
+
+  int8_t MuchReadoutConfig::GetPadYA(uint8_t febid, uint8_t channelid)
+  {
+    if (fChannelsToPadYA.size() <= static_cast<int16_t>((febid * kuNbChanPerAsic) + channelid)) {
+      LOG(debug) << "MuchReadoutConfig::GetPadYA => Index out of bounds: " << ((febid * kuNbChanPerAsic) + channelid)
+                 << " VS " << fChannelsToPadYA.size() << " (" << febid << " and " << channelid << ")";
+      return -2;
+    }
+    return fChannelsToPadYA[(febid * kuNbChanPerAsic) + channelid];
+  }
+
+  int8_t MuchReadoutConfig::GetPadXB(uint8_t febid, uint8_t channelid)
+  {
+    if (fChannelsToPadXB.size() <= static_cast<int16_t>((febid * kuNbChanPerAsic) + channelid)) {
+      LOG(debug) << "MuchReadoutConfig::GetPadXB => Index out of bounds: " << ((febid * kuNbChanPerAsic) + channelid)
+                 << " VS " << fChannelsToPadXB.size() << " (" << febid << " and " << channelid << ")";
+      return -2;
+    }
+    return fChannelsToPadXB[(febid * kuNbChanPerAsic) + channelid];
+  }
+
+  int8_t MuchReadoutConfig::GetPadYB(uint8_t febid, uint8_t channelid)
+  {
+    if (fChannelsToPadYB.size() <= static_cast<int16_t>((febid * kuNbChanPerAsic) + channelid)) {
+      LOG(debug) << "MuchReadoutConfig::GetPadYB => Index out of bounds: " << ((febid * kuNbChanPerAsic) + channelid)
+                 << " VS " << fChannelsToPadYB.size() << " (" << febid << " and " << channelid << ")";
+      return -2;
+    }
+    return fChannelsToPadYB[(febid * kuNbChanPerAsic) + channelid];
+  }
+
+  int8_t MuchReadoutConfig::GetPadXRpc(uint8_t febid, uint8_t channelid)
+  {
+    if (fChannelsToPadXRpc.size() <= static_cast<int16_t>((febid * kuNbChanPerAsic) + channelid)) {
+      LOG(debug) << "CbmMcbm2018MuchPar::GetPadXRpc => Index out of bounds: " << ((febid * kuNbChanPerAsic) + channelid)
+                 << " VS " << fChannelsToPadXRpc.size() << " (" << febid << " and " << channelid << ")";
+      return -2;
+    }
+    return fChannelsToPadXRpc[(febid * kuNbChanPerAsic) + channelid];
+  }
+
+  int8_t MuchReadoutConfig::GetPadYRpc(uint8_t febid, uint8_t channelid)
+  {
+    if (fChannelsToPadYRpc.size() <= static_cast<int16_t>((febid * kuNbChanPerAsic) + channelid)) {
+      LOG(debug) << "CbmMcbm2018MuchPar::GetPadYRpc => Index out of bounds: " << ((febid * kuNbChanPerAsic) + channelid)
+                 << " VS " << fChannelsToPadYRpc.size() << " (" << febid << " and " << channelid << ")";
+      return -2;
+    }
+    return fChannelsToPadYRpc[(febid * kuNbChanPerAsic) + channelid];
+  }
+
+  int32_t MuchReadoutConfig::GetFebId(uint16_t uAsicIdx)
+  {
+    if (uAsicIdx >= GetNrOfFebsInGemA() && uAsicIdx < (GetNrOfFebsInGemA() + GetNrOfFebsInRpc()))  //Check
+      return fnFebsIdsArrayRpc[uAsicIdx - GetNrOfFebsInGemA()];                                    //Check Vikas
+    else if (uAsicIdx >= (GetNrOfFebsInGemA() + GetNrOfFebsInRpc())
+             && uAsicIdx < (GetNrOfFebsInGemA() + GetNrOfFebsInRpc() + GetNrOfFebsInGemB()))  //Check
+      return fnFebsIdsArrayGemB[uAsicIdx - (GetNrOfFebsInGemA() + GetNrOfFebsInRpc())];
+    else if (uAsicIdx < GetNrOfFebsInGemA())
+      return fnFebsIdsArrayGemA[uAsicIdx];
+    else {
+      LOG(warning) << "MuchReadoutConfig::GetFebId => provided uAsicIdx : " << uAsicIdx
+                   << " not in the range of :" << (GetNrOfFebsInGemA() + GetNrOfFebsInRpc() + GetNrOfFebsInGemB())
+                   << "Returning large value -2";
+      return -2;
+    }
+  }
+
+  //GetModule() is not used in unpacker
+  uint16_t MuchReadoutConfig::GetModule(uint16_t uAsicIdx)
+  {
+    if (uAsicIdx >= GetNrOfFebsInGemA()) {
+      if ((uAsicIdx % GetNrOfFebsInGemA()) < GetNrOfFebsInRpc()) return 1;
+      else
+        return 2;
+    }
+    else
+      return 0;
+  }
+
+  double MuchReadoutConfig::GetRealX(int16_t SectorIndex)
+  {
+    if (SectorIndex < 0 || SectorIndex <= 97) {
+      LOG(debug) << "MuchReadoutConfig::GetRealX => Index out of bounds: ";
+      return -2;
+    }
+    return fRealX[SectorIndex];
+  }
+
+  double MuchReadoutConfig::GetRealPadSize(int16_t SectorIndex)
+  {
+    if (SectorIndex < 0 || SectorIndex <= 97) {
+      LOG(debug) << "MuchReadoutConfig::GetRealX => Index out of bounds: ";
+      return -2;
+    }
+    return fRealPadSize[SectorIndex];
+  }
+
+  double MuchReadoutConfig::GetRealX(int16_t Channel, int16_t Sector)
+  {
+    int16_t PadIndex = Channel + 97 * Sector;
+    if (Channel < 0 || Sector < 0) return -2;
+    if (fRealX.size() <= PadIndex) {
+      LOG(info) << "MuchReadoutConfig::GetRealX => Index out of bounds: " << Channel << " " << Sector << " "
+                << PadIndex;
+      return -1;
+    }
+    return fRealX[PadIndex];
+  }
+
+  double MuchReadoutConfig::GetRealPadSize(int16_t Channel, int16_t Sector)
+  {
+    int16_t PadIndex = Channel + 97 * Sector;
+    if (Channel < 0 || Sector < 0) return -2;
+    if (fRealPadSize.size() <= PadIndex) {
+      LOG(info) << "MuchReadoutConfig::GetRealPadSize => Index out of bounds: " << Channel << " " << Sector << " "
+                << PadIndex;
+      return -1;
+    }
+    return fRealPadSize[PadIndex];
+  }
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/much/MuchReadoutConfig.h b/algo/detectors/much/MuchReadoutConfig.h
new file mode 100644
index 0000000000..3302932515
--- /dev/null
+++ b/algo/detectors/much/MuchReadoutConfig.h
@@ -0,0 +1,129 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Florian Uhlig [committer] */
+
+// -------------------------------------------------------------------------
+// -----            MuchReadoutConfig header file                       -----
+// -----            Created 22/02/22  by P.-A. Loizeau                 -----
+// -----            Modified 07/12/18  by A Kumar                      -----
+// -------------------------------------------------------------------------
+
+#ifndef ALGO_DETECTORS_MUCH_MUCHREADOUTCONFIG_H
+#define ALGO_DETECTORS_MUCH_MUCHREADOUTCONFIG_H
+
+//#include "FairParGenericSet.h"
+
+#include <cstdint>
+#include <vector>
+
+//#include "TArrayD.h"
+//#include "TArrayI.h"
+
+//class FairParIo;
+//class FairParamList;
+
+namespace cbm::algo
+{
+
+  //class MuchReadoutConfig : public FairParGenericSet {
+  class MuchReadoutConfig {
+
+  public:
+    /** Standard constructor **/
+    MuchReadoutConfig() {};
+
+    /** Destructor **/
+    ~MuchReadoutConfig() {};
+
+    /** Reset all parameters **/
+    virtual void clear();
+
+    /*
+  void putParams(FairParamList*);
+  bool getParams(FairParamList*);
+*/
+    static constexpr uint16_t GetNbCrobsPerDpb() { return kuNbCrobsPerDpb; }
+    static constexpr uint16_t GetNbElinkPerCrob() { return kuNbElinksPerCrob; }
+    static constexpr uint16_t GetNbElinkPerDpb() { return kuNbCrobsPerDpb * kuNbElinksPerCrob; }
+    static constexpr uint16_t GetNbFebsPerCrob() { return kuNbFebsPerCrob; }
+    static constexpr uint16_t GetNbFebsPerDpb() { return kuNbCrobsPerDpb * kuNbFebsPerCrob; }
+    static constexpr uint16_t GetNbAsicsPerFeb() { return kuNbAsicsPerFeb; }
+    static constexpr uint16_t GetNbAsicsPerCrob() { return kuNbFebsPerCrob * kuNbAsicsPerFeb; }
+    static constexpr uint16_t GetNbAsicsPerDpb() { return kuNbCrobsPerDpb * GetNbAsicsPerCrob(); }
+    static constexpr uint16_t GetNbChanPerAsic() { return kuNbChanPerAsic; }
+    static constexpr uint16_t GetNbChanPerFeb() { return kuNbAsicsPerFeb * kuNbChanPerAsic; }
+
+    //! Convert from eLink index to FEB Connection ( 0 to kuNbFebsPerCrob)
+    int16_t ElinkIdxToFebIdx(uint16_t uElink);
+
+    uint16_t GetNrOfDpbs() { return fuNrOfDpbs; }
+    uint16_t GetDpbId(uint16_t uDpbIdx);
+    uint16_t GetNrOfCrobs() { return fuNrOfDpbs * kuNbCrobsPerDpb; }
+    uint16_t GetNrOfFebs() { return GetNrOfCrobs() * kuNbFebsPerCrob; }
+    uint16_t GetNrOfAsics() { return GetNrOfFebs() * kuNbAsicsPerFeb; }
+    uint16_t GetNrOfFebsInGemA() { return fuFebsInGemA; }
+    uint16_t GetNrOfFebsInGemB() { return fuFebsInGemB; }
+    int16_t GetNrOfChannels() { return kuNbChanPerAsic; }
+
+    int32_t GetFebId(uint16_t);
+    uint16_t GetModule(uint16_t);
+
+    int8_t GetPadXA(uint8_t febid, uint8_t channelid);
+    int8_t GetPadYA(uint8_t febid, uint8_t channelid);
+    int8_t GetPadXB(uint8_t febid, uint8_t channelid);
+    int8_t GetPadYB(uint8_t febid, uint8_t channelid);
+    double GetRealX(int16_t);
+    double GetRealPadSize(int16_t);
+
+    double GetRealX(int16_t Channel, int16_t Sector);
+    double GetRealPadSize(int16_t Channel, int16_t Sector);
+
+    //RPC Module Related Functions
+    uint16_t GetNrOfFebsInRpc() { return fuFebsInRpc; }
+    int8_t GetPadXRpc(uint8_t febid, uint8_t channelid);
+    int8_t GetPadYRpc(uint8_t febid, uint8_t channelid);
+
+    bool IsCrobActive(uint16_t uDpbIdx, uint16_t uCrobIdx);
+    bool IsFebActive(uint16_t uFebInSystIdx);
+    bool IsFebActive(uint16_t uDpbIdx, uint16_t uCrobIdx, uint16_t uFebIdx);
+    bool IsFebPulser(uint16_t uFebInSystIdx);
+    bool IsFebPulser(uint16_t uDpbIdx, uint16_t uCrobIdx, uint16_t uFebIdx);
+    double GetFebAdcGain(uint16_t uDpbIdx, uint16_t uCrobIdx, uint16_t uFebIdx);
+    double GetFebAdcOffset(uint16_t uDpbIdx, uint16_t uCrobIdx, uint16_t uFebIdx);
+
+  private:
+    /// Constants
+    static const uint16_t kuNbCrobsPerDpb   = 1;    // Number of CROBs possible per DPB
+    static const uint16_t kuNbElinksPerCrob = 42;   // Number of elinks in each CROB ?
+    static const uint16_t kuNbFebsPerCrob   = 9;    // Number of FEBs  connected to each CROB for mMuch 2019
+    static const uint16_t kuNbAsicsPerFeb   = 1;    // Number of ASICs connected in each FEB for MUCH
+    static const uint16_t kuNbChanPerAsic   = 128;  // Number of channels in each ASIC
+    //   static constexpr uint16_t  kuCrobMapElinkFebIdx[ kuNbElinksPerCrob ] = {
+    const int16_t kiCrobMapElinkFebIdx[kuNbElinksPerCrob] = {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2,
+                                                             3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5,
+                                                             6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8};
+    //! Map from eLink index to ASIC index within CROB ( 0 to kuNbFebsPerCrob * kuNbAsicPerFeb )
+
+    /// Variables
+    uint16_t fuNrOfDpbs = 0;                  // Total number of MUCH DPBs in system
+    std::vector<int16_t> fiDbpIdArray;        // Array to hold the unique IDs (equipment ID) for all MUCH DPBs
+    std::vector<int16_t> fiCrobActiveFlag;    // Array to hold the active flag for all CROBs, [ NbDpb * kuNbCrobPerDpb ]
+    uint16_t fuFebsInGemA = 0;                // Number of FEBs connected in GEM Module A
+    uint16_t fuFebsInGemB = 0;                // Number of FEBs connected in GEM Module B
+    uint16_t fuFebsInRpc  = 0;                // Number of FEBs connected in RPC Module
+    std::vector<int16_t> fnFebsIdsArrayGemA;  // Array to hold FEB IDs connected to GEM Module A
+    std::vector<int16_t> fnFebsIdsArrayGemB;  // Array to hold FEB IDs connected to GEM Module B
+    std::vector<int16_t> fnFebsIdsArrayRpc;   // Array to hold FEB IDs connected to RPC Module
+    std::vector<int16_t> fChannelsToPadXA;  // Array which stores the corresponding x position of PAD of entire module A
+    std::vector<int16_t> fChannelsToPadYA;  // Array which stores the corresponding y position of PAD of entire module A
+    std::vector<int16_t> fChannelsToPadXB;  // Array which stores the corresponding x position of PAD of entire module B
+    std::vector<int16_t> fChannelsToPadYB;  // Array which stores the corresponding y position of PAD of entire module B
+    std::vector<int16_t> fChannelsToPadXRpc;  // Array which stores the corresponding x position of PAD of RPC module
+    std::vector<int16_t> fChannelsToPadYRpc;  // Array which stores the corresponding y position of PAD of RPC module
+    std::vector<double> fRealX;               // Array which stores the Real X (starting 18.733 cm) position of PAD
+    std::vector<double> fRealPadSize;  // Array which stores the Real Progressive size of each padX (starting .327 cm )
+  };
+
+} /* namespace cbm::algo */
+
+#endif  // ALGO_DETECTORS_MUCH_MUCHREADOUTCONFIG_H
diff --git a/algo/detectors/much/UnpackMuch.cxx b/algo/detectors/much/UnpackMuch.cxx
new file mode 100644
index 0000000000..c7daa77b09
--- /dev/null
+++ b/algo/detectors/much/UnpackMuch.cxx
@@ -0,0 +1,163 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau, Volker Friese [committer] */
+
+#include "UnpackMuch.h"
+
+#include <cassert>
+#include <utility>
+#include <vector>
+
+#include <cmath>
+
+#include "StsXyterMessage.h"
+
+using std::unique_ptr;
+using std::vector;
+
+namespace cbm::algo
+{
+
+  // ----   Algorithm execution   ---------------------------------------------
+  UnpackMuch::resultType UnpackMuch::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                                                const uint64_t tTimeslice)
+  {
+
+    // --- Output data
+    resultType result = {};
+
+    // --- Current Timeslice start time in epoch units. Note that it is always a multiple of epochs
+    // --- and the epoch is a multiple of ns.
+    const uint64_t epochLengthInNs = fkEpochLength * fkClockCycleNom / fkClockCycleDen;
+    fCurrentTsTime                 = tTimeslice / epochLengthInNs;
+
+    // --- Current TS_MSB epoch cycle
+    auto const msTime = msDescr.idx;  // Unix time of MS in ns
+    fCurrentCycle     = std::ldiv(msTime, fkCycleLength).quot;
+
+    // --- Number of messages in microslice
+    auto msSize = msDescr.size;
+    if (msSize % sizeof(stsxyter::Message) != 0) {
+      result.second.fNumErrInvalidMsSize++;
+      return result;
+    }
+    const uint32_t numMessages = msSize / sizeof(stsxyter::Message);
+    if (numMessages < 2) {
+      result.second.fNumErrInvalidMsSize++;
+      return result;
+    }
+
+    // --- Interpret MS content as sequence of SMX messages
+    auto message = reinterpret_cast<const stsxyter::Message*>(msContent);
+
+    // --- The first message in the MS is expected to be of type EPOCH and can be ignored.
+    if (message[0].GetMessType() != stsxyter::MessType::Epoch) {
+      result.second.fNumErrInvalidFirstMessage++;
+      return result;
+    }
+
+    // --- The second message must be of type ts_msb.
+    if (message[1].GetMessType() != stsxyter::MessType::TsMsb) {
+      result.second.fNumErrInvalidFirstMessage++;
+      return result;
+    }
+    ProcessTsmsbMessage(message[1]);
+
+    // --- Message loop
+    for (uint32_t messageNr = 2; messageNr < numMessages; messageNr++) {
+
+      // --- Action depending on message type
+      switch (message[messageNr].GetMessType()) {
+
+        case stsxyter::MessType::Hit: {
+          ProcessHitMessage(message[messageNr], result.first, result.second);
+          break;
+        }
+        case stsxyter::MessType::TsMsb: {
+          ProcessTsmsbMessage(message[messageNr]);
+          break;
+        }
+        default: {
+          result.second.fNumNonHitOrTsbMessage++;
+          break;
+        }
+
+      }  //? Message type
+
+    }  //# Messages
+
+    return result;
+  }
+  // --------------------------------------------------------------------------
+
+
+  // -----   Process hit message   --------------------------------------------
+  inline void UnpackMuch::ProcessHitMessage(const stsxyter::Message& message, vector<CbmMuchDigi>& digiVec,
+                                            UnpackMuchMonitorData& monitor) const
+  {
+
+    // --- Check eLink and get parameters
+    uint16_t elink = message.GetLinkIndexHitBinning();
+    if (elink >= fParams.fElinkParams.size()) {
+      monitor.fNumErrElinkOutOfRange++;
+      return;
+    }
+    const UnpackMuchElinkPar& elinkPar = fParams.fElinkParams.at(elink);
+    uint32_t asicNr                    = elinkPar.fAsicNr;
+
+    // --- Hardware-to-software address
+    uint32_t numChansPerModule = fParams.fNumAsicsPerModule * fParams.fNumChansPerAsic;
+    uint32_t address           = elinkPar.fAddress;
+    uint32_t channel           = 0;
+    if (asicNr < fParams.fNumAsicsPerModule / 2) {  // front side (n side)
+      channel = message.GetHitChannel() + fParams.fNumChansPerAsic * asicNr;
+    }
+    else {  // back side (p side)
+      channel = numChansPerModule - message.GetHitChannel() + 1;
+    }
+
+    // --- Expand time stamp to time within timeslice (in clock cycle)
+    uint64_t messageTime = message.GetHitTimeBinning() + fCurrentEpochTime;
+
+    // --- Convert time stamp from clock cycles to ns. Round to nearest full ns.
+    messageTime = (messageTime * fkClockCycleNom + fkClockCycleDen / 2) / fkClockCycleDen;
+
+    // --- Correct ASIC-wise offsets
+    messageTime -= elinkPar.fTimeOffset;
+    // --- TODO: Add walk correction (depends on ADC)
+
+    // --- Charge
+    double charge = elinkPar.fAdcOffset + (message.GetHitAdc() - 1) * elinkPar.fAdcGain;
+
+    // --- Create output digi
+    //  digiVec.emplace_back(address, channel, messageTime, charge);
+  }
+  // --------------------------------------------------------------------------
+
+
+  // -----   Process an epoch (TS_MSB) message   ------------------------------
+  inline void UnpackMuch::ProcessTsmsbMessage(const stsxyter::Message& message)
+  {
+    // The compression of time is based on the hierarchy epoch cycle - epoch - message time.
+    // Cycles are counted from the start of Unix time and are multiples of an epoch (ts_msb).
+    // The epoch number is counted within each cycle. The time in the hit message is expressed
+    // in units of the readout clock cycle relative to the current epoch.
+    // The ts_msb message indicates the start of a new epoch. Its basic information is the epoch
+    // number within the current cycle. A cycle wrap resets the epoch number to zero, so it is
+    // indicated by the epoch number being smaller than the previous one (epoch messages are
+    // seemingly not consecutively in the data stream, but only if there are hit messages in between).
+    auto epoch = message.GetTsMsbValBinning();
+
+    // --- Cycle wrap
+    if (epoch < fCurrentEpoch) fCurrentCycle++;
+
+    // --- Update current epoch counter
+    fCurrentEpoch = epoch;
+
+    // --- Calculate epoch time in clocks cycles relative to timeslice start time
+    fCurrentEpochTime = (fCurrentCycle * fkEpochsPerCycle + epoch - fCurrentTsTime) * fkEpochLength;
+  }
+  // --------------------------------------------------------------------------
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/much/UnpackMuch.h b/algo/detectors/much/UnpackMuch.h
new file mode 100644
index 0000000000..22bc306fdd
--- /dev/null
+++ b/algo/detectors/much/UnpackMuch.h
@@ -0,0 +1,146 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau, Volker Friese [committer] */
+
+#ifndef CBM_ALGO_UNPACKMUCH_H
+#define CBM_ALGO_UNPACKMUCH_H 1
+
+
+#include "CbmMuchDigi.h"
+
+#include "MicrosliceDescriptor.hpp"
+#include "Timeslice.hpp"
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "StsXyterMessage.h"
+
+namespace cbm::algo
+{
+
+
+  /** @struct UnpackMuchElinkPar
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief STS Unpacking parameters for one eLink / ASIC
+   **/
+  struct UnpackMuchElinkPar {
+    int32_t fAddress     = 0;   ///< CbmMuchAddress for the connected module
+    uint32_t fAsicNr     = 0;   ///< Number of connected ASIC within the module
+    uint64_t fTimeOffset = 0.;  ///< Time calibration parameter
+    double fAdcOffset    = 0.;  ///< Charge calibration parameter
+    double fAdcGain      = 0.;  ///< Charge calibration parameter
+  };
+
+
+  /** @struct UnpackMuchPar
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief Parameters required for the STS unpacking (specific to one component)
+   **/
+  struct UnpackMuchPar {
+    uint32_t fNumChansPerAsic                    = 0;   ///< Number of channels per ASIC
+    uint32_t fNumAsicsPerModule                  = 0;   ///< Number of ASICS per module
+    std::vector<UnpackMuchElinkPar> fElinkParams = {};  ///< Parameters for each eLink
+  };
+
+
+  /** @struct UnpackMuchMoni
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 2 December 2021
+   ** @brief Monitoring data for STS unpacking
+   **/
+  struct UnpackMuchMonitorData {
+    uint32_t fNumNonHitOrTsbMessage     = 0;
+    uint32_t fNumErrElinkOutOfRange     = 0;  ///< Elink not contained in parameters
+    uint32_t fNumErrInvalidFirstMessage = 0;  ///< First message is not TS_MSB or second is not EPOCH
+    uint32_t fNumErrInvalidMsSize       = 0;  ///< Microslice size is not multiple of message size
+    uint32_t fNumErrTimestampOverflow   = 0;  ///< Overflow in 64 bit time stamp
+    bool HasErrors()
+    {
+      uint32_t numErrors = fNumNonHitOrTsbMessage + fNumErrElinkOutOfRange + fNumErrInvalidFirstMessage
+                           + fNumErrInvalidMsSize + fNumErrTimestampOverflow;
+      return (numErrors > 0 ? true : false);
+    }
+  };
+
+
+  /** @class UnpackMuch
+   ** @author Pierre-Alain Loizeau <p.-a.loizeau@gsi.de>
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief Unpack algorithm for STS
+   **/
+  class UnpackMuch {
+
+  public:
+    typedef std::pair<std::vector<CbmMuchDigi>, UnpackMuchMonitorData> resultType;
+
+
+    /** @brief Default constructor **/
+    UnpackMuch() {};
+
+
+    /** @brief Destructor **/
+    ~UnpackMuch() {};
+
+
+    /** @brief Algorithm execution
+     ** @param  msContent  Microslice payload
+     ** @param  msDescr    Microslice descriptor
+     ** @param  tTimeslice Unix start time of timeslice [ns]
+     ** @return STS digi data
+     **/
+    resultType operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                          const uint64_t tTimeslice);
+
+    /** @brief Set the parameter container
+     ** @param params Pointer to parameter container
+     **/
+    void SetParams(std::unique_ptr<UnpackMuchPar> params) { fParams = *(std::move(params)); }
+
+
+  private:  // methods
+    /** @brief Process a hit message
+     ** @param message SMX message (32-bit word)
+     ** @param digiVec Vector to append the created digi to
+     ** @param monitor Reference to monitor object
+     **/
+    void ProcessHitMessage(const stsxyter::Message& message, std::vector<CbmMuchDigi>& digiVec,
+                           UnpackMuchMonitorData& monitor) const;
+
+    /** @brief Process an epoch message (TS_MSB)
+     ** @param message SMX message (32-bit word)
+     **/
+    void ProcessTsmsbMessage(const stsxyter::Message& message);
+
+
+  private:                            // members
+    uint64_t fCurrentTsTime    = 0;   ///< Unix time of timeslice in units of epoch length
+    uint64_t fCurrentCycle     = 0;   ///< Current epoch cycle
+    uint32_t fCurrentEpoch     = 0;   ///< Current epoch number within epoch cycle
+    uint64_t fCurrentEpochTime = 0;   ///< Current epoch time relative to timeslice in clock cycles
+    UnpackMuchPar fParams      = {};  ///< Parameter container
+
+    /** Number of TS_MSB epochs per cycle **/
+    static constexpr uint64_t fkEpochsPerCycle = stsxyter::kuTsMsbNbTsBinsBinning;
+
+    /** Length of TS_MSB epoch in clock cycles **/
+    static constexpr uint64_t fkEpochLength = stsxyter::kuHitNbTsBinsBinning;
+
+    /** Clock cycle nominator [ns] and denominator. The clock cycle in ns is nominator / denominator. **/
+    static constexpr uint32_t fkClockCycleNom = stsxyter::kulClockCycleNom;
+    static constexpr uint32_t fkClockCycleDen = stsxyter::kulClockCycleDen;
+
+    /** Epoch cycle length in ns **/
+    static constexpr uint64_t fkCycleLength = (fkEpochsPerCycle * fkEpochLength * fkClockCycleNom) / fkClockCycleDen;
+  };
+
+
+} /* namespace cbm::algo */
+
+#endif /* CBM_ALGO_UNPACKSTS_H */
-- 
GitLab