diff --git a/Calorimeter/CaloDetDescr/CaloIdDictFiles/data/IdDictCalorimeter.xml b/Calorimeter/CaloDetDescr/CaloIdDictFiles/data/IdDictCalorimeter.xml
index 006fabfa2116e1390b3c2f612e3b378e45f5562d..37bed2379f814331055b953646980bbd9e3b444e 100644
--- a/Calorimeter/CaloDetDescr/CaloIdDictFiles/data/IdDictCalorimeter.xml
+++ b/Calorimeter/CaloDetDescr/CaloIdDictFiles/data/IdDictCalorimeter.xml
@@ -21,6 +21,6 @@
     <range field="part"       value="Ecal" />
     <range field="row"        values="Bottom Top" wraparound="FALSE" />
     <range field="module"     values="Starboard Port" wraparound="FALSE" />
-    <range field="pmt"        minvalue="0" maxvalue="0" />
+    <range field="pmt"        minvalue="0" maxvalue="1" />
   </region>
-</IdDictionary>
\ No newline at end of file
+</IdDictionary>
diff --git a/Calorimeter/CaloRecAlgs/python/CaloRecAlgsConfig.py b/Calorimeter/CaloRecAlgs/python/CaloRecAlgsConfig.py
index c49770ad2b7e9468eca196af582d2156b9ab977c..bfa25550254ef72e1499e420d69a0ccde3a9f547 100644
--- a/Calorimeter/CaloRecAlgs/python/CaloRecAlgsConfig.py
+++ b/Calorimeter/CaloRecAlgs/python/CaloRecAlgsConfig.py
@@ -15,8 +15,11 @@ def CalorimeterReconstructionCfg(flags, **kwargs):
     acc = ComponentAccumulator()
 
     kwargs.setdefault("CaloWaveHitContainerKey", "CaloWaveformHits")
+    kwargs.setdefault("Calo2WaveHitContainerKey", "Calo2WaveformHits")
     kwargs.setdefault("PreshowerWaveHitContainerKey", "PreshowerWaveformHits")
+    
     kwargs.setdefault("CaloHitContainerKey", "CaloHits")
+    kwargs.setdefault("Calo2HitContainerKey", "Calo2Hits")
     kwargs.setdefault("PreshowerHitContainerKey", "PreshowerHits")
 
     acc.merge(CaloRecToolCfg(flags, **kwargs))
diff --git a/Calorimeter/CaloRecAlgs/src/CaloRecAlg.cxx b/Calorimeter/CaloRecAlgs/src/CaloRecAlg.cxx
index b6df5fed6053f7499a6f45a95e4b51d2e6633ddd..c8844946d2f8b1242c22ec0540a07d23ff2c7ada 100644
--- a/Calorimeter/CaloRecAlgs/src/CaloRecAlg.cxx
+++ b/Calorimeter/CaloRecAlgs/src/CaloRecAlg.cxx
@@ -12,23 +12,19 @@ StatusCode CaloRecAlg::initialize() {
 
   // Set key to read calo hits from
   ATH_CHECK( m_caloWaveHitContainerKey.initialize() );
+  ATH_CHECK( m_calo2WaveHitContainerKey.initialize() );
 
   // Set key to read preshower hits from
   ATH_CHECK( m_preshowerWaveHitContainerKey.initialize() );
 
   // Set key to write container
   ATH_CHECK( m_caloHitContainerKey.initialize() );
+  ATH_CHECK( m_calo2HitContainerKey.initialize() );
   ATH_CHECK( m_preshowerHitContainerKey.initialize() );
 
   // Initalize tools
   ATH_CHECK( m_recoCalibTool.retrieve() );
 
-  // Store calibrattiion factos in a vector for ease of access
-  m_EM_mu_Map[0] = m_calo_ch0_EM_mu;
-  m_EM_mu_Map[1] = m_calo_ch1_EM_mu;
-  m_EM_mu_Map[2] = m_calo_ch2_EM_mu;
-  m_EM_mu_Map[3] = m_calo_ch3_EM_mu;
-
   return StatusCode::SUCCESS;
 }
 //----------------------------------------------------------------------
@@ -54,6 +50,15 @@ StatusCode CaloRecAlg::execute(const EventContext& ctx) const {
     ATH_MSG_DEBUG("Calorimeter Waveform Hit container found with zero length!");
   }
 
+  SG::ReadHandle<xAOD::WaveformHitContainer> calo2WaveHitHandle(m_calo2WaveHitContainerKey, ctx);
+
+  ATH_CHECK( calo2WaveHitHandle.isValid() );
+  ATH_MSG_DEBUG("Found ReadHandle for WaveformHitContainer " << m_calo2WaveHitContainerKey);
+
+  if (calo2WaveHitHandle->size() == 0) {
+    ATH_MSG_DEBUG("Calorimeter 2 Waveform Hit container found with zero length!");
+  }
+
   SG::ReadHandle<xAOD::WaveformHitContainer> preshowerWaveHitHandle(m_preshowerWaveHitContainerKey, ctx);
 
   ATH_CHECK( preshowerWaveHitHandle.isValid() );
@@ -63,116 +68,55 @@ StatusCode CaloRecAlg::execute(const EventContext& ctx) const {
     ATH_MSG_DEBUG("Preshower Waveform Hit container found with zero length!");
   }
 
-  // Find the output waveform container
+  // Only correct gain in real data
+  bool correct_gain = !m_isMC;
+  
+  // Reconstruct calorimeter hits
   SG::WriteHandle<xAOD::CalorimeterHitContainer> caloHitContainerHandle(m_caloHitContainerKey, ctx);
   ATH_CHECK( caloHitContainerHandle.record( std::make_unique<xAOD::CalorimeterHitContainer>(),
 					std::make_unique<xAOD::CalorimeterHitAuxContainer>() ) );
-
-  SG::WriteHandle<xAOD::CalorimeterHitContainer> preshowerHitContainerHandle(m_preshowerHitContainerKey, ctx);
-  ATH_CHECK( preshowerHitContainerHandle.record( std::make_unique<xAOD::CalorimeterHitContainer>(),
-                    std::make_unique<xAOD::CalorimeterHitAuxContainer>() ) );
-
   ATH_MSG_DEBUG("WaveformsHitContainer '" << caloHitContainerHandle.name() << "' initialized");
-  ATH_MSG_DEBUG("WaveformsHitContainer '" << preshowerHitContainerHandle.name() << "' initialized");
 
   // Loop over calo hits and calibrate each primary hit
   for( const auto& hit : *caloWaveHitHandle ) {
     if (hit->status_bit(xAOD::WaveformStatus::SECONDARY)) continue;
-
-    // Create a new calo hit
     xAOD::CalorimeterHit* calo_hit = new xAOD::CalorimeterHit();
     caloHitContainerHandle->push_back(calo_hit);
+    calo_hit->addHit(caloWaveHitHandle.get(), hit);
+    m_recoCalibTool->reconstruct(ctx, calo_hit, correct_gain);
+  }
+  ATH_MSG_DEBUG("CaloHitContainer '" << caloHitContainerHandle.name() << "' filled with "<< caloHitContainerHandle->size() <<" items");
 
-    ATH_MSG_DEBUG("calo_hit in channel " << hit->channel() );
-
-    float MIPcharge_ref = m_recoCalibTool->getMIPcharge_ref(hit->channel()); // get reference MIP charge from database
-
-    float charge = hit->integral()/50.0; // divide by 50 ohms to get charge
-    ATH_MSG_DEBUG("calo_hit filled has charge of " << charge << " pC");
-
-    float gainRatio = 1.0;
-    if (!m_isMC) { // MC already has correct MIP charge stored in MIPcharge_ref, so only need to to HV extrapolation with reral data
-      gainRatio = extrapolateHVgain(hit->channel());
-    }
-    ATH_MSG_DEBUG("HV gain ratio = " << gainRatio );
-
-
-    float Nmip = (charge * gainRatio) / MIPcharge_ref;
-    ATH_MSG_DEBUG("Nmip = " << Nmip );
-    calo_hit->set_Nmip(Nmip); // set Nmip value
-
-    float E_dep = Nmip * m_MIP_sim_Edep_calo;
-    ATH_MSG_DEBUG("E_dep in MeV = " << E_dep );
-    calo_hit->set_E_dep(E_dep);  // set calibrated E_dep value
-
-    float E_EM = Nmip * m_EM_mu_Map[hit->channel()];
-    ATH_MSG_DEBUG("Em E in MeV = " << E_EM );
-    calo_hit->set_E_EM(E_EM);  // set calibrated E_EM value
-
-    float fit_to_raw_ratio = 1.0;
-    if (hit->integral() != 0.0) { // avoid possibility of division by zero error
-      fit_to_raw_ratio = hit->raw_integral() / hit->integral();
-    }
-    calo_hit->set_fit_to_raw_ratio(fit_to_raw_ratio); // set fit-to-raw-ratio that can be used to take any of the calibrated values to what they would be if we used the raw integral instead of the fit integral
-
-    calo_hit->set_channel(hit->channel());  // set channel number
+  // Reconstruct high-gain calorimeter hits
+  SG::WriteHandle<xAOD::CalorimeterHitContainer> calo2HitContainerHandle(m_calo2HitContainerKey, ctx);
+  ATH_CHECK( calo2HitContainerHandle.record( std::make_unique<xAOD::CalorimeterHitContainer>(),
+					std::make_unique<xAOD::CalorimeterHitAuxContainer>() ) );
+  ATH_MSG_DEBUG("WaveformsHitContainer '" << calo2HitContainerHandle.name() << "' initialized");
 
-    calo_hit->clearWaveformLinks();
-    calo_hit->addHit(caloWaveHitHandle.get(), hit);  // create link to calo waveform hit
+  for( const auto& hit : *calo2WaveHitHandle ) {
+    if (hit->status_bit(xAOD::WaveformStatus::SECONDARY)) continue;
+    xAOD::CalorimeterHit* calo_hit = new xAOD::CalorimeterHit();
+    calo2HitContainerHandle->push_back(calo_hit);    
+    calo_hit->addHit(calo2WaveHitHandle.get(), hit);
+    m_recoCalibTool->reconstruct(ctx, calo_hit, correct_gain=false);
   }
+  ATH_MSG_DEBUG("Calo2HitContainer '" << calo2HitContainerHandle.name() << "' filled with "<< calo2HitContainerHandle->size() <<" items");
 
-  ATH_MSG_DEBUG("CaloHitContainer '" << caloHitContainerHandle.name() << "' filled with "<< caloHitContainerHandle->size() <<" items");
+  // Reconstruct preshower hits
+  SG::WriteHandle<xAOD::CalorimeterHitContainer> preshowerHitContainerHandle(m_preshowerHitContainerKey, ctx);
+  ATH_CHECK( preshowerHitContainerHandle.record( std::make_unique<xAOD::CalorimeterHitContainer>(),
+                    std::make_unique<xAOD::CalorimeterHitAuxContainer>() ) );
+  ATH_MSG_DEBUG("WaveformsHitContainer '" << preshowerHitContainerHandle.name() << "' initialized");
 
   for( const auto& hit : *preshowerWaveHitHandle ) {
     if (hit->status_bit(xAOD::WaveformStatus::SECONDARY)) continue;
-
-    // Create a new preshower hit
-    xAOD::CalorimeterHit* preshower_hit = new xAOD::CalorimeterHit();
-    preshowerHitContainerHandle->push_back(preshower_hit);
-
-    ATH_MSG_DEBUG("preshower_hit in channel " << hit->channel() );
-
-    float MIPcharge_ref = m_recoCalibTool->getMIPcharge_ref(hit->channel()); // get reference MIP charge from database
-
-    float charge = hit->integral()/50.0; // divide by 50 ohms to get charge
-    ATH_MSG_DEBUG("preshower_hit filled has charge of " << charge << " pC");
-
-    float Nmip = charge / MIPcharge_ref;
-    ATH_MSG_DEBUG("Nmip = " << Nmip );
-    preshower_hit->set_Nmip(Nmip); // set Nmip value
-
-    float E_dep = Nmip * m_MIP_sim_Edep_preshower;
-    ATH_MSG_DEBUG("E_dep in GeV = " << E_dep );
-    preshower_hit->set_E_dep(E_dep);  // set calibrated E_dep value
-
-    float fit_to_raw_ratio = 1.0;
-    if (hit->integral() != 0.0) { // avoid possibility of division by zero error
-      fit_to_raw_ratio = hit->raw_integral() / hit->integral();
-    }
-    preshower_hit->set_fit_to_raw_ratio(fit_to_raw_ratio); // set fit-to-raw-ratio that can be used to take any of the calibrated values to what they would be if we used the raw integral instead of the fit integral
-    
-    preshower_hit->set_channel(hit->channel());  // set channel number
-
-    preshower_hit->clearWaveformLinks();
-    preshower_hit->addHit(preshowerWaveHitHandle.get(), hit);  // create link to preshower waveform hit
-  }
-
+    xAOD::CalorimeterHit* calo_hit = new xAOD::CalorimeterHit();
+    preshowerHitContainerHandle->push_back(calo_hit);
+    calo_hit->addHit(preshowerWaveHitHandle.get(), hit);
+    m_recoCalibTool->reconstruct(ctx, calo_hit, correct_gain=false);
+  }  
   ATH_MSG_DEBUG("PreshowerHitContainer '" << preshowerHitContainerHandle.name() << "' filled with "<< preshowerHitContainerHandle->size() <<" items");
-
-
+  
   return StatusCode::SUCCESS;
 }
 
-//----------------------------------------------------------------------
-float CaloRecAlg::extrapolateHVgain(int channel) const {
-  float PMT_hv = m_recoCalibTool->getHV(channel);
-  float PMT_hv_ref = m_recoCalibTool->getHV_ref(channel);
-  TF1 gaincurve = m_recoCalibTool->get_PMT_HV_curve(channel);
-
-  float gaincurve_atHV = gaincurve.Eval(PMT_hv);
-  float gaincurve_atHVref = gaincurve.Eval(PMT_hv_ref);
-
-  return ( gaincurve_atHVref / gaincurve_atHV ) * pow( PMT_hv_ref / PMT_hv , 6.6);
-}
-//----------------------------------------------------------------------
-
diff --git a/Calorimeter/CaloRecAlgs/src/CaloRecAlg.h b/Calorimeter/CaloRecAlgs/src/CaloRecAlg.h
index 721f521621ae519778a8ddadd8b5711f1beeac9b..b11fc05ae5cf91b8e5b4aed67074d07674a511a7 100644
--- a/Calorimeter/CaloRecAlgs/src/CaloRecAlg.h
+++ b/Calorimeter/CaloRecAlgs/src/CaloRecAlg.h
@@ -71,6 +71,10 @@ class CaloRecAlg : public AthReentrantAlgorithm {
   SG::ReadHandleKey<xAOD::WaveformHitContainer> m_caloWaveHitContainerKey {this, "CaloWaveHitContainerKey", "CaloWaveformHits"};
   //@}
 
+  //@{
+  SG::ReadHandleKey<xAOD::WaveformHitContainer> m_calo2WaveHitContainerKey {this, "Calo2WaveHitContainerKey", "Calo2WaveformHits"};
+  //@}
+
   //@{
   SG::ReadHandleKey<xAOD::WaveformHitContainer> m_preshowerWaveHitContainerKey {this, "PreshowerWaveHitContainerKey", "PreshowerWaveformHits"};
   //@}
@@ -80,21 +84,10 @@ class CaloRecAlg : public AthReentrantAlgorithm {
    */
   //@{
   SG::WriteHandleKey<xAOD::CalorimeterHitContainer> m_caloHitContainerKey {this, "CaloHitContainerKey", "CaloHits"};
+  SG::WriteHandleKey<xAOD::CalorimeterHitContainer> m_calo2HitContainerKey {this, "Calo2HitContainerKey", "Calo2Hits"};
   SG::WriteHandleKey<xAOD::CalorimeterHitContainer> m_preshowerHitContainerKey {this, "PreshowerHitContainerKey", "PreshowerHits"};
   //@}
 
-  float extrapolateHVgain(int channel) const;
-
-  FloatProperty m_MIP_sim_Edep_calo {this, "MIP_sim_Edep_calo", 58.5}; // MIP deposits 5.85 MeV of energy in calo
-  FloatProperty m_MIP_sim_Edep_preshower {this, "MIP_sim_Edep_preshower", 4.894}; // MIP deposits 4.894 MeV of energy in a preshower layer
-
-  FloatProperty m_calo_ch0_EM_mu {this, "m_calo_ch0_EM_mu", 330.0}; // factor used to do rough calibration of calo ch0 to EM energy: 0.33 GeV or 330 MeV
-  FloatProperty m_calo_ch1_EM_mu {this, "m_calo_ch1_EM_mu", 330.0}; // factor used to do rough calibration of calo ch1 to EM energy: 0.33 GeV or 330 MeV
-  FloatProperty m_calo_ch2_EM_mu {this, "m_calo_ch2_EM_mu", 330.0}; // factor used to do rough calibration of calo ch2 to EM energy: 0.33 GeV or 330 MeV
-  FloatProperty m_calo_ch3_EM_mu {this, "m_calo_ch3_EM_mu", 330.0}; // factor used to do rough calibration of calo ch3 to EM energy: 0.33 GeV or 330 MeV
-
-  float m_EM_mu_Map[4]; // vector that holds EM_mu calibration factors for calo channels
-
   Gaudi::Property<bool> m_isMC {this, "isMC", false};
 
 };
diff --git a/Calorimeter/CaloRecTools/CMakeLists.txt b/Calorimeter/CaloRecTools/CMakeLists.txt
index f0e9f07b53f5426b360c2639d4f7953a44d57792..abfdf9927c6467cbd3f8e847c09a1b799b0b327b 100644
--- a/Calorimeter/CaloRecTools/CMakeLists.txt
+++ b/Calorimeter/CaloRecTools/CMakeLists.txt
@@ -13,7 +13,8 @@ atlas_add_library( CaloRecToolsLib
                    CaloRecTools/*.h src/*.cxx src/*.h
                    PUBLIC_HEADERS CaloRecTools
                    PRIVATE_INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
-                   LINK_LIBRARIES AthenaBaseComps AthenaPoolUtilities AthenaKernel GaudiKernel
+                   LINK_LIBRARIES xAODFaserWaveform xAODFaserCalorimeter
+		   AthenaBaseComps AthenaPoolUtilities AthenaKernel GaudiKernel
                    PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES}
        		   )
 
diff --git a/Calorimeter/CaloRecTools/CaloRecTools/ICaloRecTool.h b/Calorimeter/CaloRecTools/CaloRecTools/ICaloRecTool.h
index feb141d915d6214a681f437d94b8118e14c33820..e52efd93dd560bc38a83b9a23c10c16e6df533d9 100644
--- a/Calorimeter/CaloRecTools/CaloRecTools/ICaloRecTool.h
+++ b/Calorimeter/CaloRecTools/CaloRecTools/ICaloRecTool.h
@@ -17,6 +17,10 @@
 #include "GaudiKernel/ToolHandle.h"
 #include "GaudiKernel/EventContext.h"
 
+// Data Classes
+// #include "xAODFaserWaveform/WaveformHit.h"
+#include "xAODFaserCalorimeter/CalorimeterHit.h"
+
 #include "TF1.h"
 
 ///Interface for Calo reco algorithms
@@ -40,6 +44,11 @@ class ICaloRecTool : virtual public IAlgTool
   virtual float getMIPcharge_ref(int channel) const = 0;
 
   virtual TF1 get_PMT_HV_curve(int channel) const = 0;
+
+  // Reconstruct one waveform hit and put the resulting Calorimeter hit in the container
+  virtual void reconstruct(const EventContext& ctx,
+			   xAOD::CalorimeterHit* hit,
+			   bool correct_gain) const = 0;
 };
 
 #endif // CALORECTOOL_ICALORECTOOL_H
diff --git a/Calorimeter/CaloRecTools/src/CaloRecTool.cxx b/Calorimeter/CaloRecTools/src/CaloRecTool.cxx
index 916011019805ab9087c57427859086dec456eb80..01a81e287ca312ecde8616cf22a1c23a29186767 100644
--- a/Calorimeter/CaloRecTools/src/CaloRecTool.cxx
+++ b/Calorimeter/CaloRecTools/src/CaloRecTool.cxx
@@ -40,6 +40,29 @@ CaloRecTool::initialize() {
 
   HVgaincurves_rootFile->Close(); // close the root file
 
+  // These should be in DB, but just hardcode this for now
+  m_MIP_sim_Edep[0] = m_MIP_sim_Edep_calo.value();
+  m_MIP_sim_Edep[1] = m_MIP_sim_Edep_calo.value();
+  m_MIP_sim_Edep[2] = m_MIP_sim_Edep_calo.value();
+  m_MIP_sim_Edep[3] = m_MIP_sim_Edep_calo.value();
+
+  m_MIP_sim_Edep[12] = m_MIP_sim_Edep_preshower.value();
+  m_MIP_sim_Edep[13] = m_MIP_sim_Edep_preshower.value();
+  
+  m_MIP_sim_Edep[16] = m_MIP_sim_Edep_calo2.value();
+  m_MIP_sim_Edep[17] = m_MIP_sim_Edep_calo2.value();
+  m_MIP_sim_Edep[18] = m_MIP_sim_Edep_calo2.value();
+  m_MIP_sim_Edep[19] = m_MIP_sim_Edep_calo2.value();
+
+  m_EM_mu_Map[0] = m_calo_EM_mu.value();
+  m_EM_mu_Map[1] = m_calo_EM_mu.value();
+  m_EM_mu_Map[2] = m_calo_EM_mu.value();
+  m_EM_mu_Map[3] = m_calo_EM_mu.value();
+
+  m_EM_mu_Map[16] = m_calo_EM_mu.value();
+  m_EM_mu_Map[17] = m_calo_EM_mu.value();
+  m_EM_mu_Map[18] = m_calo_EM_mu.value();
+  m_EM_mu_Map[19] = m_calo_EM_mu.value();
 
   return StatusCode::SUCCESS;
 }
@@ -150,7 +173,7 @@ float CaloRecTool::getMIPcharge_ref(const EventContext& ctx, int channel) const
 
   ATH_MSG_DEBUG("in getMIPcharge_ref("<<channel<<")");
 
-  float MIP_charge_ref =0.;
+  float MIP_charge_ref =1.;  // Default for no calibration parameters
 
   // Read Cond Handle
   SG::ReadCondHandle<CondAttrListCollection> readHandle{m_MIP_ref_ReadKey, ctx};
@@ -187,8 +210,63 @@ float CaloRecTool::getMIPcharge_ref(int channel) const {
   return CaloRecTool::getMIPcharge_ref(ctx, channel);
 }
 
+//----------------------------------------------------------------------
+float CaloRecTool::extrapolateHVgain(const EventContext& ctx, int channel) const {
+  float PMT_hv = getHV(ctx, channel);
+  float PMT_hv_ref = getHV_ref(ctx, channel);
+  TF1 gaincurve = get_PMT_HV_curve(channel);
+
+  float gaincurve_atHV = gaincurve.Eval(PMT_hv);
+  float gaincurve_atHVref = gaincurve.Eval(PMT_hv_ref);
+
+  return ( gaincurve_atHVref / gaincurve_atHV ) * pow( PMT_hv_ref / PMT_hv , 6.6);
+}
+
 //--------------------------------------------------------------
 
+//----------------------------------------------------------------------
+// Reconstruct one waveform hit 
+//xAOD::CalorimeterHit*
+void
+CaloRecTool::reconstruct(const EventContext& ctx,
+			 xAOD::CalorimeterHit* calo_hit,
+			 bool correct_gain=false) const {
+
+  // Get the waveform hit attached to this calo hit
+  const xAOD::WaveformHit* wave_hit = calo_hit->Hit(0);
+  ATH_MSG_DEBUG("calo_hit in channel " << wave_hit->channel() );
+  
+  float MIPcharge_ref = getMIPcharge_ref(ctx, wave_hit->channel()); // get reference MIP charge from database
+
+  float charge = wave_hit->integral()/50.0; // divide by 50 ohms to get charge
+  ATH_MSG_DEBUG("calo_hit filled has charge of " << charge << " pC");
+
+  float gainRatio = 1.0;
+  if (correct_gain) { // MC already has correct MIP charge stored in MIPcharge_ref, so only need to to HV extrapolation with reral data
+    gainRatio = extrapolateHVgain(ctx, wave_hit->channel());
+  }
+  ATH_MSG_DEBUG("HV gain ratio = " << gainRatio );
+
+  float Nmip = (charge * gainRatio) / MIPcharge_ref;
+  ATH_MSG_DEBUG("Nmip = " << Nmip );
+  calo_hit->set_Nmip(Nmip); // set Nmip value
+
+  float E_dep = Nmip * m_MIP_sim_Edep[wave_hit->channel()];
+  ATH_MSG_DEBUG("E_dep in MeV = " << E_dep );
+  calo_hit->set_E_dep(E_dep);  // set calibrated E_dep value
+
+  float E_EM = Nmip * m_EM_mu_Map[wave_hit->channel()];
+  ATH_MSG_DEBUG("Em E in MeV = " << E_EM );
+  calo_hit->set_E_EM(E_EM);  // set calibrated E_EM value
+
+  float fit_to_raw_ratio = 1.0;
+  if (wave_hit->integral() != 0.0) { // avoid possibility of division by zero error
+    fit_to_raw_ratio = wave_hit->raw_integral() / wave_hit->integral();
+  }
+  calo_hit->set_fit_to_raw_ratio(fit_to_raw_ratio); // set fit-to-raw-ratio that can be used to take any of the calibrated values to what they would be if we used the raw integral instead of the fit integral
+
+}
+
 
 
 
diff --git a/Calorimeter/CaloRecTools/src/CaloRecTool.h b/Calorimeter/CaloRecTools/src/CaloRecTool.h
index 6ab891ad42784e3388d933f42de9267545fab3d9..de736af74393bf424b2fd471795aea7bc2808f52 100644
--- a/Calorimeter/CaloRecTools/src/CaloRecTool.h
+++ b/Calorimeter/CaloRecTools/src/CaloRecTool.h
@@ -25,6 +25,10 @@
 #include "Gaudi/Property.h"
 #include "GaudiKernel/EventContext.h"
 
+// Data Classes
+#include "xAODFaserWaveform/WaveformHit.h"
+#include "xAODFaserCalorimeter/CalorimeterHit.h"
+
 // Include ROOT classes
 #include "TF1.h"
 #include "TFile.h"
@@ -57,6 +61,11 @@ class CaloRecTool: public extends<AthAlgTool, ICaloRecTool> {
   // method for returning PMT HV calibration curves from root file
   virtual TF1 get_PMT_HV_curve(int channel) const override;
 
+  // Reconstruct one waveform hit and put the resulting Calorimeter hit in the container
+  virtual void reconstruct(const EventContext& ctx,
+			   xAOD::CalorimeterHit* hit,
+			   bool correct_gain) const override;
+
   TFile* HVgaincurves_rootFile;
 
   TF1* chan0_HVgaincurve_pntr;
@@ -80,6 +89,18 @@ class CaloRecTool: public extends<AthAlgTool, ICaloRecTool> {
   SG::ReadCondHandleKey<CondAttrListCollection> m_PMT_HV_ReadKey{this, "PMT_HV_ReadKey", "/WAVE/Calibration/HV", "Key of folder for PMT HV reading"};
   SG::ReadCondHandleKey<CondAttrListCollection> m_MIP_ref_ReadKey{this, "MIP_ref_ReadKey", "/WAVE/Calibration/MIP_ref", "Key of folder for MIP charge calibration measurment, also stores PMT HV used to measure the reference MIP charge"};
 
+  // Could also put these in DB, but just hardcode them for now
+  FloatProperty m_MIP_sim_Edep_calo {this, "MIP_sim_Edep_calo", 58.5}; // MIP deposits 5.85 MeV of energy in calo
+  FloatProperty m_MIP_sim_Edep_calo2 {this, "MIP_sim_Edep_calo", 58.5}; // MIP deposits 5.85 MeV of energy in calo
+  FloatProperty m_MIP_sim_Edep_preshower {this, "MIP_sim_Edep_preshower", 4.894}; // MIP deposits 4.894 MeV of energy in a preshower layer
+
+  FloatProperty m_calo_EM_mu {this, "m_calo_EM_mu", 330.0}; // factor used to do rough calibration of calo to EM energy: 0.33 GeV or 330 MeV
+
+  float m_MIP_sim_Edep[32]; // vector that holds Edep factors for calo and preshower
+  float m_EM_mu_Map[32]; // vector that holds EM_mu calibration factors for calo channels
+
+  float extrapolateHVgain(const EventContext& ctx, int channel) const;
+
 };
 
 #endif // CALORECTOOLS_CALORECTOOL_H
diff --git a/PhysicsAnalysis/NtupleDumper/scripts/faser_ntuple_maker.py b/PhysicsAnalysis/NtupleDumper/scripts/faser_ntuple_maker.py
index ac3c57ce5fe602f9f817f8092ef0112ae1217a6e..de2d757a56a56b0c1f782a9c8add40fd8a28e062 100755
--- a/PhysicsAnalysis/NtupleDumper/scripts/faser_ntuple_maker.py
+++ b/PhysicsAnalysis/NtupleDumper/scripts/faser_ntuple_maker.py
@@ -27,7 +27,8 @@ parser.add_argument("--merge", type=int, default=1,
                     help="Specify merged files per reco file (MC only)")
 parser.add_argument("--last", type=int, default=0,
                     help="Specify last file in slice (normally --files)")
-
+parser.add_argument("-g", "--geom", default="",
+                    help="Specify geometry (if it can't be parsed from run number\n Values: Ti12Data03 (2022 TiT12)")
 parser.add_argument("--outfile", default="",
                     help="Override output file name")
                     
@@ -244,9 +245,9 @@ if len(args.geom) > 0:
     runtype = args.geom
 else:
     if args.isMC:
-        runtype = TI12MC04
+        runtype = "TI12MC04"
     else:
-        runtype = TI12Data04
+        runtype = "TI12Data04"
 
 if runtype in ["TI12Data04", "TI12MC04"]:
     ConfigFlags.GeoModel.FaserVersion = "FASERNU-04" # FASER geometry
diff --git a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
index def572ed1dbd7ad5e6af132eaeb17fbfc5a57f11..1692580349dde4d73684761e4a9dd33f81530274 100644
--- a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
+++ b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
@@ -179,13 +179,15 @@ StatusCode NtupleDumperAlg::initialize()
   m_tree->Branch("inputBitsNext", &m_inputBitsNext, "inputBitsNext/I");
 
   //WAVEFORMS
+  // Need to put an option in here for pre/post 2024 data
   addWaveBranches("VetoNu",2,4);
-  addWaveBranches("VetoSt1",1,14);
+  addWaveBranches("VetoSt1",2,14);
   addWaveBranches("VetoSt2",2,6);
   addWaveBranches("Timing",4,8);
   addWaveBranches("Preshower",2,12);
   addWaveBranches("Calo",4,0);
-
+  addWaveBranches("CaloHi", 4,16);
+  
   m_tree->Branch("ScintHit", &m_scintHit);
 
   addCalibratedBranches("Calo",4,0);
@@ -417,6 +419,12 @@ StatusCode NtupleDumperAlg::initialize()
   m_HistRandomCharge[12] = new TH1F("hRandomCharge12", "Preshower ch12 Charge from Random Events;charge (pC);Events/bin", 100, -1.0, 1.0);
   m_HistRandomCharge[13] = new TH1F("hRandomCharge13", "Preshower ch13 Charge from Random Events;charge (pC);Events/bin", 100, -1.0, 1.0);
   m_HistRandomCharge[14] = new TH1F("hRandomCharge14", "Veto ch14 Charge from Random Events;charge (pC);Events/bin", 100, -1.0, 1.0);
+  m_HistRandomCharge[15] = new TH1F("hRandomCharge15", "Veto ch15 Charge from Random Events;charge (pC);Events/bin", 100, -1.0, 1.0);
+
+  m_HistRandomCharge[16] = new TH1F("hRandomCharge16", "CaloHi ch0 Charge from Random Events;charge (pC);Events/bin", 100, -1.0, 1.0);
+  m_HistRandomCharge[17] = new TH1F("hRandomCharge17", "CaloHi ch1 Charge from Random Events;charge (pC);Events/bin", 100, -1.0, 1.0);
+  m_HistRandomCharge[18] = new TH1F("hRandomCharge18", "CaloHi ch2 Charge from Random Events;charge (pC);Events/bin", 100, -1.0, 1.0);
+  m_HistRandomCharge[19] = new TH1F("hRandomCharge19", "CaloHi ch3 Charge from Random Events;charge (pC);Events/bin", 100, -1.0, 1.0);
 
   ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge0", m_HistRandomCharge[0]));
   ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge1", m_HistRandomCharge[1]));
@@ -433,6 +441,11 @@ StatusCode NtupleDumperAlg::initialize()
   ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge12", m_HistRandomCharge[12]));
   ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge13", m_HistRandomCharge[13]));
   ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge14", m_HistRandomCharge[14]));
+  ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge15", m_HistRandomCharge[15]));
+  ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge16", m_HistRandomCharge[16]));
+  ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge17", m_HistRandomCharge[17]));
+  ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge18", m_HistRandomCharge[18]));
+  ATH_CHECK(histSvc()->regHist("/HIST2/RandomCharge19", m_HistRandomCharge[19]));
 
   if (m_onlyBlinded){
     ATH_MSG_INFO("Only events that would be blinded are saved in ntuple");
@@ -577,7 +590,7 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
     // for random (only) triggers, store charge of scintillators in histograms
     if (m_tap == 16) { 
       // Fill histograms
-      for (int chan = 0; chan<15; chan++) {
+      for (unsigned int chan = 0; chan<nchan; chan++) {
         m_HistRandomCharge[chan]->Fill(m_wave_raw_charge[chan]);
       }
     }
@@ -1381,7 +1394,7 @@ NtupleDumperAlg::clearTree() const
   m_inputBits=0;
   m_inputBitsNext=0;
 
-  for(int ii=0;ii<15;ii++) {
+  for(unsigned int ii=0;ii<nchan;ii++) {
       m_wave_localtime[ii]=0;
       m_wave_peak[ii]=0;
       m_wave_width[ii]=0;
diff --git a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
index 5f7dedda2c7801f58ecdd5365dc152dbdab763e7..d636d8ef70dcdd7fbcb90fd44eddccd8c2ac36ac 100644
--- a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
+++ b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
@@ -84,8 +84,10 @@ private:
   SG::ReadHandleKey<xAOD::WaveformHitContainer> m_triggerContainer { this, "TriggerContainer", "TriggerWaveformHits", "Trigger hit container name" };
   SG::ReadHandleKey<xAOD::WaveformHitContainer> m_preshowerContainer { this, "PreshowerContainer", "PreshowerWaveformHits", "Preshower hit container name" };
   SG::ReadHandleKey<xAOD::WaveformHitContainer> m_ecalContainer { this, "EcalContainer", "CaloWaveformHits", "Ecal hit container name" };
+  SG::ReadHandleKey<xAOD::WaveformHitContainer> m_ecal2Container { this, "Ecal2Container", "Calo2WaveformHits", "Ecal hit container name" };
 
   SG::ReadHandleKey<xAOD::CalorimeterHitContainer> m_ecalCalibratedContainer { this, "EcalCalibratedContainer", "CaloHits", "Ecal Calibrated hit container name" };
+  SG::ReadHandleKey<xAOD::CalorimeterHitContainer> m_ecal2CalibratedContainer { this, "Ecal2CalibratedContainer", "Calo2Hits", "Ecal Calibrated hit container name" };
   SG::ReadHandleKey<xAOD::CalorimeterHitContainer> m_preshowerCalibratedContainer { this, "preshowerCalibratedContainer", "PreshowerHits", "Preshower Calibrated hit container name" };
 
   SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer> m_clusterContainer { this, "ClusterContainer", "SCT_ClusterContainer", "Tracker cluster container name" };
@@ -146,7 +148,9 @@ private:
 
   mutable TTree* m_tree;
 
-  mutable TH1* m_HistRandomCharge[15];
+  const static unsigned int nchan=20;
+
+  mutable TH1* m_HistRandomCharge[nchan];
 
   mutable unsigned int m_run_number;
   mutable unsigned int m_event_number;
@@ -170,22 +174,22 @@ private:
   mutable unsigned int m_inputBits;
   mutable unsigned int m_inputBitsNext;
 
-  mutable float m_wave_localtime[15];
-  mutable float m_wave_peak[15];
-  mutable float m_wave_width[15];
-  mutable float m_wave_charge[15];
+  mutable float m_wave_localtime[nchan];
+  mutable float m_wave_peak[nchan];
+  mutable float m_wave_width[nchan];
+  mutable float m_wave_charge[nchan];
 
-  mutable float m_wave_raw_peak[15];
-  mutable float m_wave_raw_charge[15];
-  mutable float m_wave_baseline_mean[15];
-  mutable float m_wave_baseline_rms[15];
-  mutable unsigned int m_wave_status[15];
+  mutable float m_wave_raw_peak[nchan];
+  mutable float m_wave_raw_charge[nchan];
+  mutable float m_wave_baseline_mean[nchan];
+  mutable float m_wave_baseline_rms[nchan];
+  mutable unsigned int m_wave_status[nchan];
  
   mutable unsigned int m_scintHit;
  
-  mutable float m_calibrated_nMIP[15];
-  mutable float m_calibrated_E_dep[15];
-  mutable float m_calibrated_E_EM[15];
+  mutable float m_calibrated_nMIP[nchan];
+  mutable float m_calibrated_E_dep[nchan];
+  mutable float m_calibrated_E_EM[nchan];
 
   mutable float m_calo_total_nMIP;
   mutable float m_calo_total_E_dep;
@@ -204,6 +208,7 @@ private:
   mutable float m_Calo1_Edep;
   mutable float m_Calo2_Edep;
   mutable float m_Calo3_Edep;
+
   mutable float m_Calo_Total_Edep;
   mutable float m_Preshower12_Edep;
   mutable float m_Preshower13_Edep;
diff --git a/Waveform/WaveRecTools/src/WaveformReconstructionTool.cxx b/Waveform/WaveRecTools/src/WaveformReconstructionTool.cxx
index f7ef9339ae32919ef6600cf099da2d42c5eb4a69..1caf49d4a3be081da8c8b8339324125ee6df788b 100644
--- a/Waveform/WaveRecTools/src/WaveformReconstructionTool.cxx
+++ b/Waveform/WaveRecTools/src/WaveformReconstructionTool.cxx
@@ -96,6 +96,14 @@ WaveformReconstructionTool::reconstructPrimary(
   int lo_edge = int((trigger_time+offset)/2.) + m_windowStart;
   int hi_edge = int((trigger_time+offset)/2.) + m_windowStart + m_windowWidth;
 
+  if (hi_edge >= int(wave.size())) {
+    // This likely means we have the wrong digitizer range
+    ATH_MSG_WARNING("Found channel " << wave.channel() << " with low edge: " << lo_edge << " hi edge: " << hi_edge << " > wave.size() " << wave.size());
+    ATH_MSG_WARNING("  trigger_time + offset: " << (trigger_time+offset) << " => " << int((trigger_time+offset)/2.));
+    newhit->set_status_bit(xAOD::WaveformStatus::WAVEFORM_INVALID);
+    return StatusCode::SUCCESS;
+  }
+  
   // Fill raw hit values
   fillRawHitValues(wave, lo_edge, hi_edge, newhit);
 
@@ -367,7 +375,11 @@ WaveformReconstructionTool::fillRawHitValues(const RawWaveform& wave,
   // First, make sure we don't overflow the waveform range
   if (lo_edge < 0) lo_edge = 0;
   if (hi_edge >= int(wave.size())) hi_edge = wave.size() - 1;
-
+  if (hi_edge <= lo_edge) {
+    ATH_MSG_WARNING("Channel " << wave.channel() << " waveform with lo: " << lo_edge << " hi: " << hi_edge << "!");
+    return;
+  }
+  
   ATH_MSG_DEBUG("Fill channel " << wave.channel() 
 		<< " waveform from sample " << lo_edge << " to " << hi_edge);
 
diff --git a/Waveform/WaveformConditions/WaveCondUtils/scripts/makeTimingDB.py b/Waveform/WaveformConditions/WaveCondUtils/scripts/makeTimingDB.py
index 57c3390c087b7f41850716d6ec24aa7d7e7044f6..0074c2b69906eda40667defdebd2596ff96d8652 100755
--- a/Waveform/WaveformConditions/WaveCondUtils/scripts/makeTimingDB.py
+++ b/Waveform/WaveformConditions/WaveCondUtils/scripts/makeTimingDB.py
@@ -15,7 +15,7 @@ nominal_data = {
     6525: 820.
 }
 
-offset_channels = 16
+# offset_channels = 20
 
 # Run 
 # 0 - initial data
@@ -53,7 +53,10 @@ offset_data = {
   5042: ehn1_offsets,
   5050: ti12_offsets,
 # IFT and VetoNu installed
-  6525: [ -10., -10., -10., -10., -25., -25., 0., 0., 0., 0., 0., 0., 18., 18., 0., 0. ]
+  6525: [ -10., -10., -10., -10., -25., -25., 0., 0., 0., 0., 0., 0., 18., 18., 0., 0. ],
+# 2024, add 2nd digitizer
+  13847: [ -10., -10., -10., -10., -25., -25., 0., 0., 0., 0., 0., 0., 18., 18., 0., 0., -10., -10., -10., -10. ]
+    
 }
 
 attr_list_desc = '<timeStamp>run-lumi</timeStamp><addrHeader><address_header service_type="71" clid="40774348" /></addrHeader><typeName>AthenaAttributeList</typeName>'
@@ -82,13 +85,12 @@ for run, data in offset_data.items():
     assert run > lastRun, 'Run numbers out of order'
     assert run <= maxInt32, 'Run number out of range'
     lastRun = run
-    assert len(data) == offset_channels, 'Offset data does not have '+str(offset_channels)+' entries'
-    for i in range(offset_channels):
+    #assert len(data) == offset_channels, 'Offset data does not have '+str(offset_channels)+' entries'
+    for i in range(len(data)):
         assert isinstance(data[i], float), 'Offset time is not float'
 
 # Data looks OK
 
-
 from PyCool import cool
 from CoolConvUtilities.AtlCoolLib import indirectOpen
 
@@ -135,7 +137,7 @@ offsetFolder = db.createFolder('/WAVE/DAQ/TimingOffset', offsetFolderSpec, cond_
 lastValid = cool.ValidityKeyMax
 for firstValidRun, offset_list in reversed(offset_data.items()):
     firstValid = (firstValidRun << 32)
-    for channel in range(offset_channels):
+    for channel in range(len(offset_list)):
         offsetRecord = cool.Record(offsetSpec)
         offsetRecord[ 'TriggerOffset' ] = float(offset_list[channel])
         offsetFolder.storeObject( firstValid, lastValid, offsetRecord, cool.ChannelId(channel) )
@@ -154,8 +156,9 @@ nominal_data = {
     0: 820.
 }
 # No offsets by default
+# Expand this to 20 channels for 2024 data
 offset_data = {
-  0: [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]
+  0: [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]
 }
 
 # Validate again just in case
@@ -176,8 +179,8 @@ for run, data in offset_data.items():
     assert run > lastRun, 'Run numbers out of order'
     assert run <= maxInt32, 'Run number out of range'
     lastRun = run
-    assert len(data) == offset_channels, 'Offset data does not have '+str(offset_channels)+' entries'
-    for i in range(offset_channels):
+    #assert len(data) == offset_channels, 'Offset data does not have '+str(offset_channels)+' entries'
+    for i in range(len(data)):
         assert isinstance(data[i], float), 'Offset time is not float'
 
 # Data looks OK
@@ -224,7 +227,7 @@ offsetFolder = db.createFolder('/WAVE/DAQ/TimingOffset', offsetFolderSpec, cond_
 lastValid = cool.ValidityKeyMax
 for firstValidRun, offset_list in reversed(offset_data.items()):
     firstValid = (firstValidRun << 32)
-    for channel in range(offset_channels):
+    for channel in range(len(offset_list)):
         offsetRecord = cool.Record(offsetSpec)
         offsetRecord[ 'TriggerOffset' ] = float(offset_list[channel])
         offsetFolder.storeObject( firstValid, lastValid, offsetRecord, cool.ChannelId(channel) )
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.cxx b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.cxx
index a7de79076eddf9fa132849deb24089dacbf996dd..2828113ed35bf6d24c19ccc31ca3470fd0a39d98 100644
--- a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.cxx
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.cxx
@@ -94,10 +94,17 @@ WaveformCableMappingTool::getCableMapping(const EventContext& ctx) const {
       // Ugh, cant use switch statement with strings
       // Must do this using an if ladder
       if (det_type == "calo") {
-	identifier = m_ecalID->pmt_id(rowVal, moduleVal, pmtVal);
+	// Do checks of PMT identifier
+	identifier = m_ecalID->pmt_id(rowVal, moduleVal, pmtVal, true);
+	// Test a few things
+	ATH_MSG_DEBUG("Calo  ID:" << identifier);
+	ATH_MSG_DEBUG("PMT:" << m_ecalID->pmt(identifier) << " PMT Max:" << m_ecalID->pmt_max(identifier));
       }
       else if (det_type == "calo2") {
-	identifier = m_ecalID->pmt_id(rowVal, moduleVal, pmtVal);
+	// Do checks of PMT identifier
+	identifier = m_ecalID->pmt_id(rowVal, moduleVal, pmtVal, true);
+	ATH_MSG_DEBUG("Calo2 ID:" << identifier);
+	ATH_MSG_DEBUG("PMT:" << m_ecalID->pmt(identifier) << " PMT Max:" << m_ecalID->pmt_max(identifier));
       }
       else if (det_type == "veto") {
 	identifier = m_vetoID->pmt_id(stationVal, plateVal, pmtVal);