diff --git a/include/Fadc250Decoder.h b/include/Fadc250Decoder.h
index aa9479202467353fc1598736ad1a6ebfd20fea98..dae10b857ff1fc5e7f1c6f1759bdd48b92a438b6 100644
--- a/include/Fadc250Decoder.h
+++ b/include/Fadc250Decoder.h
@@ -36,7 +36,7 @@ struct Fadc250Chan
 {
     bool overflow;
     uint32_t win_sum;
-    std::vector<uint32_t> integral, amplitude, time, pulse_raw, raw;
+    std::vector<uint32_t> integral, peak, time, pedestal, pulse_raw, raw;
 
     Fadc250Chan() : overflow(false), win_sum(0) {}
 };
diff --git a/include/utils.h b/include/utils.h
index c5e3bf92306630b38151dc032931875bf650c27f..7f8fbfad76719d416a5065a7c24d4938320debe7 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -6,6 +6,7 @@
 #include <functional>
 #include <unordered_map>
 #include "ConfigParser.h"
+#include "ConfigObject.h"
 
 
 enum ModuleType
@@ -89,4 +90,69 @@ std::vector<PulseData> get_pulses(Module *mod, int ch, ModuleType t = kMaxModule
 }
 
 
+std::vector<Module> read_modules(const std::string &path, bool verbose=false)
+{
+    // read in file
+    std::string buffer = ConfigParser::file_to_string(path);
+
+    // remove comments
+    ConfigParser::comment_line(buffer, "#", "\n");
+
+    // get content blocks
+    auto text = ConfigParser::break_into_blocks(buffer, "{", "}");
+
+    ConfigObject conf;
+    std::vector<Module> res;
+    for(auto &b : text.blocks)
+    {
+        // module
+        if (!ConfigParser::case_ins_equal("Module", b.label))
+            continue;
+
+        auto btext = ConfigParser::break_into_blocks(b.content, "{", "}");
+        conf.ReadConfigString(btext.residual);
+
+        // module attributes
+        Module mod;
+        mod.crate = conf.GetConfigValue("crate").Int();
+        mod.slot = conf.GetConfigValue("slot").Int();
+        mod.type = str2ModuleType(conf.GetConfigValue("type").c_str());
+
+        // channels
+        for (auto &sub : btext.blocks) {
+            if (!ConfigParser::case_ins_equal("Channels", sub.label))
+                continue;
+            ConfigParser parser;
+            parser.ReadBuffer(sub.content.c_str());
+            while(parser.ParseLine()) {
+                mod.channels.emplace_back(Channel{
+                    parser.TakeFirst().Int(),
+                    parser.TakeFirst().String(),
+                    str2ChannelType(parser.TakeFirst().c_str())
+                });
+            }
+        }
+        res.emplace_back(mod);
+    }
+
+    if (verbose) {
+        // print out all channels
+        std::cout << "Read-in " << res.size() << " modules from \"" << path << "\"." << std::endl;
+        for (auto &mod : res) {
+            std::cout << "Module: crate = " << mod.crate
+                      << ", slot = " << mod.slot
+                      << ", type = " << ModuleType2str(mod.type)
+                      << std::endl;
+            for (auto &ch : mod.channels) {
+                std::cout << "\t Channel " << ch.id
+                          << ": name =  " << ch.name
+                          << ", type = " << ChannelType2str(ch.type)
+                          << std::endl;
+            }
+        }
+    }
+
+    return res;
+}
+
 #endif // MY_UTILS_H
diff --git a/scripts/analyze_waveform.py b/scripts/analyze_waveform.py
index e1a509361d439debd84db7c5160c4a31d2986dee..d2e7b2e346715342411dd9fed4d6dead83369a67 100644
--- a/scripts/analyze_waveform.py
+++ b/scripts/analyze_waveform.py
@@ -4,6 +4,7 @@ import json
 import numpy as np
 import pandas as pd
 import argparse
+from collections import OrderedDict
 from matplotlib import pyplot as plt
 from scipy import signal
 from collections.abc import Iterable
@@ -13,14 +14,25 @@ def branch_to_array1d(br, t):
     return np.ndarray((len(br), ), dtype=t, buffer=br)
 
 
-def get_channels(json_path):
-    dbf = open(json_path)
-    db = json.load(dbf)
-    channels = dict()
-    for mod, mod_prop in db.items():
-        for ch in mod_prop['channels']:
-            channels[ch['name']] = ch['type']
-    return channels
+def get_maximum_peaks(tree, chan_pos, ref_pos=0.):
+    # init
+    peaks = np.zeros(len(chan_pos), dtype=np.float32)
+    poses = np.zeros(len(chan_pos), dtype=np.float32)
+
+    for i, (c, twin) in enumerate(chan_pos.items()):
+        cpeaks = branch_to_array1d(tree.__getattr__(c + '_Ppeak'), np.float32)
+        cposes = branch_to_array1d(tree.__getattr__(c + '_Ptime'), np.float32) - ref_pos
+        # find maximum peak inside window
+        mask = (cposes <= twin[1]) & (cposes >= twin[0])
+        cpeaks = cpeaks[mask]
+        cposes = cposes[mask]
+        if not len(cpeaks):
+            continue
+        idx = np.argmax(cpeaks)
+        if cpeaks[idx] > 1e-5:
+            peaks[i] = cpeaks[idx]
+            poses[i] = cposes[idx]
+    return peaks, poses
 
 
 parser = argparse.ArgumentParser('Raw waveform analysis')
@@ -41,69 +53,59 @@ dbf = open(args.tconfig)
 ch_pos = json.load(dbf)
 
 # trigger channels
-# tr = ['S2_1', 'S2_2', 'S2_3', 'S2_4', 'S2_5', 'S2_6', 'S2_7', 'S2_8', 'S2_9', 'S2_10', 'S2_11']
-# tr_time = (17, 23)
-tr = ['C1', 'C2', 'C3', 'C4', 'C5_1', 'C5_2', 'C5_3', 'C5_4', 'C6_1', 'C6_2', 'C6_3', 'C6_4',
-      'C7_1', 'C7_2', 'C7_3', 'C7_4', 'C8_1', 'C8_2', 'C8_3', 'C8_4', 'C9_1', 'C9_2', 'C9_3', 'C9_4']
-tr_time = (28, 32)
+# scintillator
+sc_ch = ['S2_1', 'S2_2', 'S2_3', 'S2_4', 'S2_5', 'S2_6', 'S2_7', 'S2_8', 'S2_9', 'S2_10', 'S2_11']
+sc_pos = OrderedDict([(ch, (17, 23)) for ch in sc_ch])
+
+# calorimeter
+ec_ch = ['C1', 'C2', 'C3', 'C4', 'C5_1', 'C5_2', 'C5_3', 'C5_4', 'C6_1', 'C6_2', 'C6_3', 'C6_4',
+         'C7_1', 'C7_2', 'C7_3', 'C7_4', 'C8_1', 'C8_2', 'C8_3', 'C8_4', 'C9_1', 'C9_2', 'C9_3', 'C9_4']
+ec_pos = OrderedDict([(ch, (28, 32)) for ch in ec_ch])
 
 f = ROOT.TFile.Open(args.root_file, 'read')
 tree = f.EvTree
-
-trg_ch = np.ndarray(shape=(tree.GetEntries(), ), dtype=object)
-trg_val = np.ndarray(shape=(tree.GetEntries(), 3), dtype='float64')
-ch_val = np.ndarray(shape=(tree.GetEntries(), len(ch_pos)*2), dtype='float64')
-
-for iev in np.arange(0, tree.GetEntries()):
+nev = tree.GetEntries()
+if args.nev > 0 and args.nev <= nev:
+    nev = args.nev
+
+# init buffers
+trg_cols = ['peak', 'pos', 'nhits', 'sum']
+ec_names = np.ndarray(shape=(nev, ), dtype=object)
+ec_vals = np.zeros(shape=(nev, len(trg_cols)), dtype=np.float32)
+sc_names = np.ndarray(shape=(nev, ), dtype=object)
+sc_vals = np.zeros(shape=(nev, len(trg_cols)), dtype=np.float32)
+ch_vals = np.zeros(shape=(nev, len(ch_pos)*2), dtype=np.float32)
+
+for iev in np.arange(0, nev):
     tree.GetEntry(iev)
     if iev % 1000 == 0:
         print('processed {}'.format(iev), end='\r')
 
-    # triggers
-    ev_trg_peaks, ev_trg_poses = [], []
-    for c in tr:
-        peaks = branch_to_array1d(tree.__getattr__(c + '_Ppeak'), np.float32)
-        poses = branch_to_array1d(tree.__getattr__(c + '_Ptime'), np.float32)
-        tpeak, tpos = 0, 0
-        for peak, pos in zip(peaks, poses):
-            if pos <= tr_time[1] and pos >= tr_time[0] and peak > tpeak:
-                tpeak = peak
-                tpos = pos
-        ev_trg_peaks.append(tpeak)
-        ev_trg_poses.append(tpos)
-
-    # no triggers
-    if not len(ev_trg_peaks):
-        continue
-
-    # get the maximum peak from all trigger channels
-    ich = np.argmax(ev_trg_peaks)
-    trg_ch[iev] = tr[ich]
-    trg_val[iev] = (ev_trg_peaks[ich], ev_trg_poses[ich], len(ev_trg_peaks))
-
-    # channels
-    for i, (c, twin) in enumerate(ch_pos.items()):
-        peaks = branch_to_array1d(tree.__getattr__(c + '_Ppeak'), np.float32)
-        poses = branch_to_array1d(tree.__getattr__(c + '_Ptime'), np.float32)
-
-        # check timing, maximum peak inside the time window
-        cpeak, cpos = 0, 0
-        for peak, pos in zip(peaks, poses):
-            pos -= ev_trg_poses[ich]
-            if pos >= twin[0] and pos <= twin[1] and peak > cpeak:
-                cpeak = peak
-                cpos = pos
-        ch_val[iev][[i, len(ch_pos) + i]] = (cpeak, cpos)
-
-    if args.nev > 0 and iev >= args.nev:
-        break
+    sc_peaks, sc_poses = get_maximum_peaks(tree, sc_pos)
+    ec_peaks, ec_poses = get_maximum_peaks(tree, ec_pos)
+
+    # save result to buffer, EC
+    iec = np.argmax(ec_peaks)
+    ec_names[iev] = list(ec_pos)[iec]
+    ec_vals[iev] = (ec_peaks[iec], ec_poses[iec], np.sum(ec_peaks > 0), np.sum(ec_peaks))
+    ref = ec_poses[iec]
+
+    # SC
+    isc = np.argmax(sc_peaks)
+    sc_names[iev] = list(sc_pos)[isc]
+    sc_vals[iev] = (sc_peaks[isc], sc_poses[isc], np.sum(sc_peaks > 0), np.sum(sc_peaks))
+
+    # CHER
+    ch_peaks, ch_poses = get_maximum_peaks(tree, ch_pos, ref)
+    ch_vals[iev] = np.concatenate([ch_peaks, ch_poses])
 
 print('processed {}'.format(iev))
 
-cols = ['trg_peak', 'trg_pos', 'trg_nhits'] \
-     + [c + '_peak' for c, _ in ch_pos.items()] \
-     + [c + '_pos' for c, _ in ch_pos.items()]
-result = pd.DataFrame(index=np.arange(iev+1), columns=cols, data=np.concatenate((trg_val[0:iev+1], ch_val[0:iev+1]), axis=1))
-result.loc[:, 'trg_ch'] = trg_ch[0:iev+1]
+cols = ['ec_' + col for col in trg_cols] + ['sc_' + col for col in trg_cols] \
+     + [c + '_peak' for c, _ in ch_pos.items()] + [c + '_pos' for c, _ in ch_pos.items()]
+result = pd.DataFrame(index=np.arange(nev), columns=cols,
+                      data=np.concatenate((ec_vals, sc_vals, ch_vals), axis=1))
+result.loc[:, 'ec_ch'] = ec_names
+result.loc[:, 'sc_ch'] = sc_names
 result.to_csv(args.output)
 
diff --git a/src/analyze.cpp b/src/analyze.cpp
index db816174b29664fa5e20712ef85db2fc76f579af..2b6c0af9beaef8db2d440ece00b59307a6e577ed 100644
--- a/src/analyze.cpp
+++ b/src/analyze.cpp
@@ -1,3 +1,5 @@
+#include <iostream>
+#include <iomanip>
 #include <fstream>
 #include "utils.h"
 #include "ConfigParser.h"
@@ -6,21 +8,50 @@
 #include "THaCodaFile.h"
 #include "THaEvData.h"
 #include "CodaDecoder.h"
-#include "Fadc250Module.h"
 #include "evio.h"
+#include "TSpectrum.h"
 #include "TTree.h"
 #include "TFile.h"
 #include "TH1.h"
+#include "simpleLib.h"
+#include "Fadc250Module.h"
+#include "Fadc250Decoder.h"
 
 
+#define FADC_BANK 3
 #define PROGRESS_COUNT 1000
 
 
-std::vector<Module> read_modules(const std::string &path);
-std::unordered_map<std::string, std::vector<float>> read_timing(const std::string &path);
 void write_raw_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules);
-void process_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules,
-                  std::unordered_map<std::string, std::vector<float>> timing, float nsig, float thres);
+
+// get fadc250 data from the module
+Fadc250Data pack_fadc250_data(Decoder::Fadc250Module* fadc)
+{
+    Fadc250Data res(0, 0, 1);
+    res.mode = fadc->GetMode();
+
+    res.events.resize(1);
+    auto &event = res.events[0];
+
+    for (int i = 0; i < FADC_NCHAN; ++i) {
+        for (int j = 0; j < fadc->GetNumFadcEvents(i); ++j) {
+            auto integral = fadc->GetPulseIntegralData(i, j);
+            auto peak = fadc->GetPulsePeakData(i, j);
+            auto pedestal = fadc->GetPulsePedestalData(i, j);
+            auto time = fadc->GetPulseTimeData(i, j);
+
+            event.channels[i].integral.push_back(integral);
+            event.channels[i].peak.push_back(peak);
+            event.channels[i].pedestal.push_back(pedestal);
+            event.channels[i].time.push_back(time);
+        }
+
+        if (fadc->GetNumSamples(i) > 0) {
+            event.channels[i].raw = fadc->GetPulseSamplesVector(i);
+        }
+    }
+    return res;
+}
 
 
 int main(int argc, char* argv[])
@@ -29,18 +60,12 @@ int main(int argc, char* argv[])
     ConfigOption conf_opt;
     conf_opt.AddOpts(ConfigOption::help_message, 'h', "help");
     conf_opt.AddOpt(ConfigOption::arg_require, 'n');
-    conf_opt.AddOpt(ConfigOption::arg_require, 'c');
-    conf_opt.AddOpt(ConfigOption::arg_require, 'f');
     conf_opt.AddLongOpt(ConfigOption::arg_require, "config-module", 'm');
-    conf_opt.AddLongOpt(ConfigOption::arg_require, "config-timing", 't');
-    conf_opt.AddLongOpt(ConfigOption::arg_none, "raw-output", 'r');
+    conf_opt.AddLongOpt(ConfigOption::arg_require, "waveform-output", 'w');
 
     conf_opt.SetDesc("usage: %0 <data_file> <out_file>");
     conf_opt.SetDesc('n', "number of events to process (< 0 means all).");
-    // conf_opt.SetDesc('c', "timing cut factor, default 3 (3 sigmas).");
-    // conf_opt.SetDesc('f', "fired module threshold on ADC value (ped subtracted, default 50.0");
     conf_opt.SetDesc('m', "configuration file for modules to be read-in, default \"config/mapmt_module.conf\"");
-    // conf_opt.SetDesc('t', "configuration file for signal timing to be read-in, default \"config/mapmt_timing.conf\"");
 
     if (!conf_opt.ParseArgs(argc, argv) || conf_opt.NbofArgs() != 2) {
         std::cout << conf_opt.GetInstruction() << std::endl;
@@ -48,28 +73,16 @@ int main(int argc, char* argv[])
     }
 
     int nev = -1;
-    float nsig = 3.;
-    float thres = 50.;
     std::string mconf = "config/esb_module.conf";
-    std::string tconf = "config/mapmt_timing.conf";
 
     for (auto &opt : conf_opt.GetOptions()) {
         switch (opt.mark) {
         case 'n':
             nev = opt.var.Int();
             break;
-        case 'c':
-            nsig = opt.var.Float();
-            break;
-        case 'f':
-            thres = opt.var.Float();
-            break;
         case 'm':
             mconf = opt.var.String();
             break;
-        case 't':
-            tconf = opt.var.String();
-            break;
         default :
             std::cout << conf_opt.GetInstruction() << std::endl;
             return -1;
@@ -77,339 +90,224 @@ int main(int argc, char* argv[])
     }
 
     auto modules = read_modules(mconf);
-    // auto timing = read_timing(tconf);
 
     write_raw_data(conf_opt.GetArgument(0), conf_opt.GetArgument(1), nev, modules);
 }
 
 
-std::vector<Module> read_modules(const std::string &path)
+#define MAX_NPEAKS 20
+#define MAX_RAW 300
+struct BranchData
 {
-
-    // read in file
-    std::string buffer = ConfigParser::file_to_string(path);
-
-    // remove comments
-    ConfigParser::comment_line(buffer, "#", "\n");
-
-    // get content blocks
-    auto text = ConfigParser::break_into_blocks(buffer, "{", "}");
-
-    ConfigObject conf;
-    std::vector<Module> res;
-    for(auto &b : text.blocks)
-    {
-        // module
-        if (!ConfigParser::case_ins_equal("Module", b.label))
-            continue;
-
-        auto btext = ConfigParser::break_into_blocks(b.content, "{", "}");
-        conf.ReadConfigString(btext.residual);
-
-        // module attributes
-        Module mod;
-        mod.crate = conf.GetConfigValue("crate").Int();
-        mod.slot = conf.GetConfigValue("slot").Int();
-        mod.type = str2ModuleType(conf.GetConfigValue("type").c_str());
-
-        // channels
-        for (auto &sub : btext.blocks) {
-            if (!ConfigParser::case_ins_equal("Channels", sub.label))
-                continue;
-            ConfigParser parser;
-            parser.ReadBuffer(sub.content.c_str());
-            while(parser.ParseLine()) {
-                mod.channels.emplace_back(Channel{
-                    parser.TakeFirst().Int(),
-                    parser.TakeFirst().String(),
-                    str2ChannelType(parser.TakeFirst().c_str())
-                });
-            }
+    int npul, nraw;
+    float integral[MAX_NPEAKS], peak[MAX_NPEAKS], time[MAX_NPEAKS];
+    int raw[MAX_RAW];
+    float ped_mean, ped_err;
+};
+
+#define BUF_SIZE 1000
+static double buffer[BUF_SIZE], wfbuf[BUF_SIZE], bkbuf[BUF_SIZE];
+void refine_pedestal(const std::vector<uint32_t> &raw, float &ped_mean, float &ped_err)
+{
+    int count = 0;
+    float mean = 0.;
+    for (auto &val : raw) {
+        if (std::abs(val - ped_mean) < 2.0 * ped_err) {
+            buffer[count] = val;
+            mean += val;
+            count ++;
         }
-        res.emplace_back(mod);
+    }
+    if (count == 0) {
+        return;
     }
 
-    // print out all channels
-    std::cout << "Read-in " << res.size() << " modules from \"" << path << "\"." << std::endl;
-    for (auto &mod : res) {
-        std::cout << "Module: crate = " << mod.crate
-                  << ", slot = " << mod.slot
-                  << ", type = " << ModuleType2str(mod.type)
-                  << std::endl;
-        for (auto &ch : mod.channels) {
-            std::cout << "\t Channel " << ch.id
-                      << ": name =  " << ch.name
-                      << ", type = " << ChannelType2str(ch.type)
-                      << std::endl;
-        }
+    mean /= count;
+    float change = std::abs(ped_mean - mean);
+
+    // no outliers
+    if (change < 0.05 * ped_err) {
+        return;
     }
 
-    return res;
+    // recursively refine
+    float err = 0.;
+    for (int i = 0; i < count; ++i) {
+        err += (buffer[i] - mean)*(buffer[i] - mean);
+    }
+    ped_mean = mean;
+    ped_err = std::sqrt(err/count);
+    refine_pedestal(raw, ped_mean, ped_err);
 }
 
-
-std::unordered_map<std::string, std::vector<float>> read_timing(const std::string &path)
+// a function for a simple constant baseline fit
+void find_pedestal(const std::vector<uint32_t> &raw, float &ped_mean, float &ped_err)
 {
-    std::unordered_map<std::string, std::vector<float>> res;
-    ConfigParser parser;
-
-    std::cout << "Reading channel timing info from \"" << path << "\"." << std::endl;
-    parser.ReadFile(path);
-    std::string name;
-    float center, sigma;
-    while (parser.ParseLine()) {
-        parser >> name >> center >> sigma;
-        if (res.find(name) != res.end()) {
-            std::cout << "WARNING: Duplicated timing information for channel " << name << std::endl;
-        }
-        res[name] = {center, sigma};
-        std::cout << name << ": (" << center << ", " << sigma << ")" << std::endl;
+    ped_mean = 0;
+    ped_err = 0;
+
+    for (auto &val : raw) {
+        ped_mean += val;
     }
+    ped_mean /= raw.size();
 
-    return res;
-}
+    for (auto &val : raw) {
+        ped_err += (val - ped_mean)*(val - ped_mean);
+    }
+    ped_err = std::sqrt(ped_err/raw.size());
 
+    refine_pedestal(raw, ped_mean, ped_err);
+}
 
-void write_raw_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules)
+// analyze the waveform data and fill the result in a branch data
+void waveform_analysis(const std::vector<uint32_t> &raw, BranchData &res)
 {
-    Decoder::THaCodaFile datafile(dpath.c_str());
-    THaEvData *evdata = new Decoder::CodaDecoder();
-
-#define BUFFER_SIZE 100
-    struct BranchData
-    {
-        int num;
-        float integral[BUFFER_SIZE], peak[BUFFER_SIZE], time[BUFFER_SIZE];
-    };
+    // no need to analyze
+    if (raw.empty()) {
+        return;
+    }
 
-    auto *hfile = new TFile(opath.c_str(), "RECREATE", "MAPMT test results");
-    auto tree = new TTree("EvTree", "Cherenkov Test Events");
-    int evtype;
-    tree->Branch("Event Type", &evtype, "EventType/I");
-    // set up branches and a container to be combined with the branches
-    std::unordered_map<std::string, BranchData> brdata;
-    for (auto &mod : modules) {
-        for (auto &ch : mod.channels) {
-            auto n = ch.name;
-            if (brdata.find(n) != brdata.end())
-                std::cout << "WARNING: Duplicated channel names found! -- " << n << std::endl;
-            brdata[n] = BranchData();
-            tree->Branch((ch.name + "_Npulse").c_str(), &brdata[n].num, (ch.name + "_N/I").c_str());
-            tree->Branch((ch.name + "_Pint").c_str(), &brdata[n].integral[0], (ch.name + "_Pint[" + ch.name + "_N]/F").c_str());
-            tree->Branch((ch.name + "_Ppeak").c_str(), &brdata[n].peak[0], (ch.name + "_Ppeak[" + ch.name + "_N]/F").c_str());
-            tree->Branch((ch.name + "_Ptime").c_str(), &brdata[n].time[0], (ch.name + "_Ptime[" + ch.name + "_N]/F").c_str());
-        }
+    // fill in the raw data
+    res.nraw = raw.size();
+    for (size_t i = 0; i < raw.size(); ++i) {
+        res.raw[i] = raw[i];
     }
 
-    int count = 0;
-    while ((datafile.codaRead() == S_SUCCESS) && (nev-- != 0)) {
-        // read-in data
-        evdata->LoadEvent(datafile.getEvBuffer());
-        evtype = evdata->GetEvType();
+    // get pedestals
+    find_pedestal(raw, res.ped_mean, res.ped_err);
 
-        if((++count % PROGRESS_COUNT) == 0) {
-            std::cout << "Processed events - " << count << "\r" << std::flush;
-        }
+    // fill in spectrum buffer
+    for (size_t i = 0; i < raw.size(); ++i) {
+        wfbuf[i] = raw[i] - res.ped_mean;
+    }
 
-        // clear data buffer
-        for (auto &br : brdata) {
-            br.second.num = 0;
+    // find peaks
+    TSpectrum s;
+    s.SetResolution(0.5);
+    int npeaks = s.SearchHighRes(wfbuf, bkbuf, res.nraw, 3.0, 20, false, 5, true, 3);
+
+    // fill branch data
+    double *pos = s.GetPositionX();
+    res.npul = 0;
+    for (int i = 0; i < npeaks; ++i) {
+        int j = pos[i];
+        if (wfbuf[j] < 5.*res.ped_err) { continue; }
+        res.time[res.npul] = j;
+        res.peak[res.npul] = wfbuf[j];
+        /*
+        res.integral[res.npul] = wfbuf[j];
+        j = pos[i] - 1;
+        while ( (j > 0) && (wfbuf[j] - wfbuf[j - 1])*wfbuf[j] > 0. ) {
+            res.integral[res.npul] += wfbuf[j--];
+        }
+        j = pos[i] + 1;
+        while ( (j < res.nraw - 1) && (wfbuf[j] - wfbuf[j + 1])*wfbuf[j] > 0. ) {
+            res.integral[res.npul] += wfbuf[j++];
         }
+        */
+        res.npul ++;
+    }
+}
 
-        // read data into event buffer
-        for (auto &mod : modules) {
-            auto *fadc = dynamic_cast<Decoder::Fadc250Module*>(evdata->GetModule(mod.crate, mod.slot));
-            if ((evdata->GetNumRaw(mod.crate, mod.slot) == 0) || (fadc == nullptr))
-	      std::cout << " lll " << evdata->GetNumRaw(mod.crate, mod.slot) << std::endl;
-                continue;
-
-            for (auto &ch : mod.channels) {
-                auto pulses = get_pulses(fadc, ch.id, mod.type);
-                auto &data = brdata[ch.name];
-                data.num = pulses.size();
-                for (int i = 0; i < data.num; ++i) {
-                    data.integral[i] = pulses[i].integral;
-                    data.peak[i] = pulses[i].peak;
-		    std::cout << " i " << i << " int " << pulses[i].integral << std::endl;
-                    data.time[i] = pulses[i].time;
-                }
+
+// fill decoded FADC250 data into the container (branch data)
+void fill_branch(const Fadc250Data &slot_data, const Module &mod, std::unordered_map<std::string, BranchData> &brdata)
+{
+    for (auto &event : slot_data.events) {
+        for (auto &ch : mod.channels) {
+            auto &channel = event.channels[ch.id];
+            auto &bd = brdata[ch.name];
+            bd.npul = channel.integral.size();
+            for (uint32_t i = 0; i < channel.integral.size(); ++i) {
+                bd.integral[i] = channel.integral[i] - 23./4.*channel.pedestal[i];
+                bd.peak[i] = channel.peak[i] - 1./4.*channel.pedestal[i];
+                bd.time[i] = channel.time[i] * 0.0625;
             }
+            waveform_analysis(channel.raw, bd);
         }
+    }
+}
 
-        // take reference time difference
-        if (brdata["Ref"].num == 0)
-            continue;
-        auto ref_time = brdata["Ref"].time[0];
-        for (auto &br: brdata) {
-            for (int i = 0; i < br.second.num; ++i) {
-                br.second.time[i] -= ref_time;
+// fill branch data into the root tree
+void fill_tree(TTree *tree, std::unordered_map<std::string, BranchData> &brdata, bool &init, int mode)
+{
+    if ((mode != 1) && (mode != 3)) {
+        std::cout << "Warning: unsupported mode " << mode << ", data won't be recorded." << std::endl;
+        return;
+    }
+
+    // initialize
+    if (!init) {
+        for (auto &it : brdata) {
+            auto n = it.first;
+            tree->Branch((n + "_Npulse").c_str(), &brdata[n].npul, (n + "_N/I").c_str());
+            tree->Branch((n + "_Pint").c_str(), &brdata[n].integral[0], (n + "_Pint[" + n + "_N]/F").c_str());
+            tree->Branch((n + "_Ptime").c_str(), &brdata[n].time[0], (n + "_Ptime[" + n + "_N]/F").c_str());
+            // raw waveform provides more information
+            if (mode == 1) {
+                tree->Branch((n + "_Ppeak").c_str(), &brdata[n].peak[0], (n + "_Ppeak[" + n + "_N]/F").c_str());
+                tree->Branch((n + "_Nraw").c_str(), &brdata[n].nraw, (n + "_Nraw/I").c_str());
+                tree->Branch((n + "_raw").c_str(), &brdata[n].raw[0], (n + "_raw[" + n + "_Nraw]/I").c_str());
+                tree->Branch((n + "_ped_mean").c_str(), &brdata[n].ped_mean, (n + "_ped_mean/F").c_str());
+                tree->Branch((n + "_ped_err").c_str(), &brdata[n].ped_err, (n + "_ped_err/F").c_str());
             }
         }
-
-        tree->Fill();
+        init = true;
+        std::cout << "Initialized root file for mode " << mode << " data." << std::endl;
     }
-    std::cout << "Processed events - " << count << std::endl;
 
-    hfile->Write();
-    hfile->Close();
+    tree->Fill();
 }
 
 
-void process_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules,
-                  std::unordered_map<std::string, std::vector<float>> timing, float nsig, float thres)
+void write_raw_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules)
 {
     Decoder::THaCodaFile datafile(dpath.c_str());
     THaEvData *evdata = new Decoder::CodaDecoder();
-    auto *hfile = new TFile(opath.c_str(), "RECREATE", "MAPMT test results");
-
-    std::unordered_map<std::string, std::vector<PulseData>> event;
-    std::unordered_map<std::string, std::vector<TH1*>> hists;
-    std::vector<std::string> signals;
-    std::unordered_map<std::string, PulseData> pulses;
 
-    // initialize histograms and timing
+    auto *hfile = new TFile(opath.c_str(), "RECREATE", "MAPMT test results");
+    auto tree = new TTree("EvTree", "Cherenkov Test Events");
+    int evtype;
+    tree->Branch("Event Type", &evtype, "EventType/I");
+    // set up branches and a container to be combined with the branches
+    std::unordered_map<std::string, BranchData> brdata;
     for (auto &mod : modules) {
         for (auto &ch : mod.channels) {
-            if (ch.type != kSignal)
-                continue;
-            signals.push_back(ch.name);
-            pulses[ch.name] = PulseData();
-            if (hists.find(ch.name) != hists.end())
-                std::cout << "WARNING: Duplicated channel names found! -- " << ch.name << std::endl;
-
-            hists[ch.name] = {
-                new TH1F((ch.name + "_tdiff").c_str(), (ch.name + "_tdiff").c_str(), 400, 0, 0),
-                new TH1F((ch.name + "_tdiff_cut").c_str(), (ch.name + "_tdiff_cut").c_str(), 100, 0, 0),
-                new TH1F((ch.name + "_Ppk").c_str(), (ch.name + "_Ppk").c_str(), 100, 0, 0)
-            };
-
-            if (timing.find(ch.name) == timing.end()) {
-                std::cout << "WARNING: Cannot find timing information for channel " << ch.name
-                          << ", using default value (20, 1)" << std::endl;
-                timing[ch.name] = {20., 1.};
+            auto n = ch.name;
+            if (brdata.find(n) != brdata.end()) {
+                std::cout << "WARNING: Duplicated channel names found! -- " << n << std::endl;
             }
+            brdata[n] = BranchData();
         }
     }
-    auto trghist = new TH1F("Trigger Timing", "CaloSum Trigger", 100, -200, 200);
-    auto sumhist = new TH1F("Sum of Cerenkov Signals", "Signal Sum", 200, 0, 0);
-    std::map<int, TH1*> sumhists {
-        {2, new TH1F("Sum of Cerenkov Signals Nhit = 2", "Signal Sum Nhit = 2", 200, 0, 0)},
-        {3, new TH1F("Sum of Cerenkov Signals Nhit = 3", "Signal Sum Nhit = 3", 200, 0, 0)},
-        {4, new TH1F("Sum of Cerenkov Signals Nhit = 4", "Signal Sum Nhit = 4", 200, 0, 0)},
-        {5, new TH1F("Sum of Cerenkov Signals Nhit = 5", "Signal Sum Nhit = 5", 200, 0, 0)},
-        {6, new TH1F("Sum of Cerenkov Signals Nhit = 6", "Signal Sum Nhit = 6", 200, 0, 0)},
-        {7, new TH1F("Sum of Cerenkov Signals Nhit = 7", "Signal Sum Nhit = 7", 200, 0, 0)},
-        {8, new TH1F("Sum of Cerenkov Signals Nhit = 8", "Signal Sum Nhit = 8", 200, 0, 0)},
-    };
-    int sum_thres = 6;
-    auto sumhist2 = new TH1F(Form("Sum of Cerenkov Signals (Mhit > %i)", sum_thres),
-                             Form("Signal Sum (Nhit > %i)", sum_thres),
-                             200, 0, 0);
-    auto cnthist = new TH1I("Number of Fired Modules", "Fire Count", 16, 1, 16);
-
-    auto ocomp = ConfigParser::decompose_path(opath);
-    ocomp.ext = "dat";
-    std::ofstream output(ConfigParser::compose_path(ocomp));
-    char csv_sep = ',';
-
-    // csv file header
-    output << "Event Number" << csv_sep << "Event Type" << csv_sep << "Fired Counts" << csv_sep <<"Signal Sum" << csv_sep << "LED";
-    for (auto &sig : signals) {
-        output << csv_sep << sig;
-    }
-    output << std::endl;
 
     int count = 0;
+    bool init_tree = false;
     while ((datafile.codaRead() == S_SUCCESS) && (nev-- != 0)) {
         // read-in data
         evdata->LoadEvent(datafile.getEvBuffer());
-        if((++count % PROGRESS_COUNT) == 0) {
-            std::cout << "Processed events - " << count << "\r" << std::flush;
-        }
+        evtype = evdata->GetEvType();
 
-        for (auto &pul : pulses) {
-            pul.second = PulseData();
+        if((count % PROGRESS_COUNT) == 0) {
+            std::cout << "Processed events - " << count << "\r" << std::flush;
         }
 
-        // read data into event buffer
-        bool empty_slot = false;
+        int data_mode = -1;
         for (auto &mod : modules) {
             auto *fadc = dynamic_cast<Decoder::Fadc250Module*>(evdata->GetModule(mod.crate, mod.slot));
-            if ((evdata->GetNumRaw(mod.crate, mod.slot) == 0) || (fadc == nullptr))
-                empty_slot = true;
-
-            for (auto &ch : mod.channels) {
-                if (empty_slot) {
-                    event[ch.name] = std::vector<PulseData>();
-                } else {
-                    event[ch.name] = get_pulses(fadc, ch.id, mod.type);
-                }
+            // no data
+            if ((evdata->GetNumRaw(mod.crate, mod.slot) == 0) || (fadc == nullptr)) { continue; }
+            auto slot_data = pack_fadc250_data(fadc);
+            // check data mode
+            if (slot_data.GetMode() > 0 && data_mode < 0) {
+                data_mode = slot_data.GetMode();
             }
+            // fill branch data
+            fill_branch(slot_data, mod, brdata);
         }
-
-        // further process event
-        // no reference time or triggered by LED
-        if ((event["Ref"].size() == 0) || (event["LED"].size() > 0))
-            continue;
-        auto ref_time = event["Ref"].front().time;
-        // Calosum timing
-        float trg_pulse = 0.;
-        float trg_time = 0.;
-        for (auto &cal : event["CaloSum"]) {
-            if (trg_pulse > cal.integral)
-                continue;
-            trg_time = cal.time;
-            trg_pulse = cal.integral;
-        }
-
-        if (trg_time == 0.)
-            continue;
-        trghist->Fill(trg_time);
-
-        int fire = 0;
-        float sum = 0;
-        for (auto &it : hists) {
-            auto hist = it.second;
-            float integral = thres;
-            for (auto &ev : event[it.first]) {
-                float tdiff = ev.time - trg_time;
-                // tdiff
-                hist[0]->Fill(tdiff);
-                if ((std::abs(tdiff - timing[it.first][0]) < nsig*timing[it.first][1]) &&
-                    (ev.integral > integral)) {
-                    pulses[it.first] = ev;
-                    integral = ev.integral;
-                    // tdiff_cut
-                    hist[1]->Fill(tdiff);
-                }
-            }
-            if (integral > thres) {
-                // pint
-                hist[2]->Fill(integral);
-                fire ++;
-                sum += integral;
-            }
-        }
-
-        if (fire > 0) {
-            sumhist->Fill(sum);
-            cnthist->Fill(fire);
-            auto it = sumhists.find(fire);
-            if (it != sumhists.end()) {
-                it->second->Fill(sum);
-            } else if (fire > sum_thres) {
-                sumhist2->Fill(sum);
-            }
-        }
-
-        if (fire >= 3) {
-            output << count << csv_sep << evdata->GetEvType() << csv_sep << fire << csv_sep << sum << csv_sep << 0;
-            for (auto &sig : signals) {
-                output << csv_sep << pulses[sig].integral;
-            }
-            output << std::endl;
-        }
-
+        // fill event
+        fill_tree(tree, brdata, init_tree, data_mode);
+        count ++;
     }
     std::cout << "Processed events - " << count << std::endl;
 
@@ -417,4 +315,3 @@ void process_data(const std::string &dpath, const std::string &opath, int nev, c
     hfile->Close();
 }
 
-
diff --git a/src/esb_analyze.cpp b/src/esb_analyze.cpp
index 5a34c6eda20791e0dd493c3132f0f359b25994d1..146b3bf759967701749cfda2a98f6b74cbe1697f 100644
--- a/src/esb_analyze.cpp
+++ b/src/esb_analyze.cpp
@@ -21,8 +21,7 @@
 #define PROGRESS_COUNT 1000
 
 
-std::vector<Module> read_modules(const std::string &path);
-void write_raw_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules, const std::string &wpath);
+void write_raw_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules);
 bool parseEvent(const uint32_t *buf, bool verbose);
 uint32_t parseBlock(const uint32_t *buf);
 
@@ -135,7 +134,6 @@ int main(int argc, char* argv[])
 
     int nev = -1;
     std::string mconf = "config/esb_module.conf";
-    std::string wpath = "";
 
     for (auto &opt : conf_opt.GetOptions()) {
         switch (opt.mark) {
@@ -145,9 +143,6 @@ int main(int argc, char* argv[])
         case 'm':
             mconf = opt.var.String();
             break;
-        case 'w':
-            wpath = opt.var.String();
-            break;
         default :
             std::cout << conf_opt.GetInstruction() << std::endl;
             return -1;
@@ -156,73 +151,7 @@ int main(int argc, char* argv[])
 
     auto modules = read_modules(mconf);
 
-    write_raw_data(conf_opt.GetArgument(0), conf_opt.GetArgument(1), nev, modules, wpath);
-}
-
-
-std::vector<Module> read_modules(const std::string &path)
-{
-    // read in file
-    std::string buffer = ConfigParser::file_to_string(path);
-
-    // remove comments
-    ConfigParser::comment_line(buffer, "#", "\n");
-
-    // get content blocks
-    auto text = ConfigParser::break_into_blocks(buffer, "{", "}");
-
-    ConfigObject conf;
-    std::vector<Module> res;
-    for(auto &b : text.blocks)
-    {
-        // module
-        if (!ConfigParser::case_ins_equal("Module", b.label))
-            continue;
-
-        auto btext = ConfigParser::break_into_blocks(b.content, "{", "}");
-        conf.ReadConfigString(btext.residual);
-
-        // module attributes
-        Module mod;
-        mod.crate = conf.GetConfigValue("crate").Int();
-        mod.slot = conf.GetConfigValue("slot").Int();
-        mod.type = str2ModuleType(conf.GetConfigValue("type").c_str());
-
-        // channels
-        for (auto &sub : btext.blocks) {
-            if (!ConfigParser::case_ins_equal("Channels", sub.label))
-                continue;
-            ConfigParser parser;
-            parser.ReadBuffer(sub.content.c_str());
-            while(parser.ParseLine()) {
-                mod.channels.emplace_back(Channel{
-                    parser.TakeFirst().Int(),
-                    parser.TakeFirst().String(),
-                    str2ChannelType(parser.TakeFirst().c_str())
-                });
-            }
-        }
-        res.emplace_back(mod);
-    }
-
-    /*
-    // print out all channels
-    std::cout << "Read-in " << res.size() << " modules from \"" << path << "\"." << std::endl;
-    for (auto &mod : res) {
-        std::cout << "Module: crate = " << mod.crate
-                  << ", slot = " << mod.slot
-                  << ", type = " << ModuleType2str(mod.type)
-                  << std::endl;
-        for (auto &ch : mod.channels) {
-            std::cout << "\t Channel " << ch.id
-                      << ": name =  " << ch.name
-                      << ", type = " << ChannelType2str(ch.type)
-                      << std::endl;
-        }
-    }
-    */
-
-    return res;
+    write_raw_data(conf_opt.GetArgument(0), conf_opt.GetArgument(1), nev, modules);
 }
 
 
@@ -390,7 +319,7 @@ void fill_tree(TTree *tree, std::unordered_map<std::string, BranchData> &brdata,
 }
 
 
-void write_raw_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules, const std::string &wpath)
+void write_raw_data(const std::string &dpath, const std::string &opath, int nev, const std::vector<Module> &modules)
 {
     Decoder::THaCodaFile datafile(dpath.c_str());
     THaEvData *evdata = new Decoder::CodaDecoder();
@@ -411,20 +340,6 @@ void write_raw_data(const std::string &dpath, const std::string &opath, int nev,
         }
     }
 
-    std::ofstream output;
-    char csv_sep = ',';
-    if (!wpath.empty()) {
-        output.open(wpath.c_str());
-        // csv file header
-        output << "Event Number";
-        for (auto &mod : modules) {
-            for (auto &ch : mod.channels) {
-                output << csv_sep << ch.name;
-            }
-        }
-        output << std::endl;
-    }
-
     int count = 0;
     bool init_tree = false;
     while ((datafile.codaRead() == S_SUCCESS) && (nev-- != 0)) {
@@ -482,21 +397,6 @@ void write_raw_data(const std::string &dpath, const std::string &opath, int nev,
             // fill event
             fill_tree(tree, brdata, init_tree, data_mode);
             count ++;
-
-            // output csv file
-            if (wpath.empty()) { continue; }
-            output << count;
-            for (auto &mod : modules) {
-                for (auto &ch : mod.channels) {
-                    auto &bd = brdata[ch.name];
-                    output << csv_sep << "\"[";
-                    for (uint32_t i = 0; i < 192; ++i) {
-                        output << bd.raw[i] << csv_sep;
-                    }
-                    output << "]\"";
-                }
-            }
-            output << std::endl;
         }
 
     }