diff --git a/src/showers.jl b/src/showers.jl
new file mode 100644
index 0000000000000000000000000000000000000000..6cf054de40ea638f029e2e6b2bab66521838cd17
--- /dev/null
+++ b/src/showers.jl
@@ -0,0 +1,404 @@
+"""
+    directlightfromEMshower(params::LMParameters, pmt::PMTModel, D, cd, θ, ϕ, Δt)
+
+Probability density function for direct light from EM-shower. Returns
+``d^2P/dt/dE`` [npe/ns/GeV].
+
+# Arguments
+- `params`: parameters of the setup
+- `pmt`: PMT model
+- `D`: distance between EM-shower and PMT [m]
+- `cd`: cosine angle of EM-shower direction and EM-shower - PMT
+- `θ`: zenith  angle orientation PMT [rad]
+- `ϕ`: azimuth angle orientation PMT [rad]
+- `Δt`: time difference relative to direct Cherenkov light
+"""
+function directlightfromEMshower(params::LMParameters, pmt::PMTModel, D, cd, θ, ϕ, Δt)
+    const double ct0 = cd;
+    const double st0 = sqrt((1.0 + ct0) * (1.0 - ct0));
+
+    const double D = std::max(D, getRmin());
+    const double t = D * getIndexOfRefraction() / C + t_ns; // time [ns]
+    const double A = getPhotocathodeArea();
+
+    const double px = sin(theta) * cos(phi);
+    const double pz = cos(theta);
+
+    const double n0 = getIndexOfRefractionGroup(wmax);
+    const double n1 = getIndexOfRefractionGroup(wmin);
+    const double ng = C * t / D; // index of refraction
+
+    if (n0 >= ng) {
+      return 0.0;
+    }
+    if (n1 <= ng) {
+      return 0.0;
+    }
+
+    const double w = getWavelength(ng);
+    const double n = getIndexOfRefractionPhase(w);
+
+    const double l_abs = getAbsorptionLength(w);
+    const double ls = getScatteringLength(w);
+
+    const double npe = cherenkov(w, n) * getQE(w);
+
+    const double ct = st0 * px + ct0 * pz; // cosine angle of incidence on PMT
+
+    const double U = getAngularAcceptance(ct); // PMT angular acceptance
+    const double V = exp(-D / l_abs - D / ls); // absorption & scattering
+    const double W = A / (D * D);              // solid angle
+
+    const double ngp = getDispersionGroup(w);
+
+    const double Ja = D * ngp / C;   // dt/dlambda
+    const double Jb = geant(n, ct0); // d^2N/dcos/dphi
+
+    return npe * geanc() * U * V * W * Jb / fabs(Ja);
+  }
+
+"""
+    scatteredlightfromEMshower(params::LMParameters, pmt::PMTModel, D, cd, θ, ϕ, Δt)
+
+Probability density function for scattered light from EM-shower. Returns
+``d^2P/dt/dE`` [npe/ns/GeV].
+
+# Arguments
+- `params`: parameters of the setup
+- `pmt`: PMT model
+- `D`: distance between EM-shower and PMT [m]
+- `cd`: cosine angle of EM-shower direction and EM-shower - PMT
+- `θ`: zenith  angle orientation PMT [rad]
+- `ϕ`: azimuth angle orientation PMT [rad]
+- `Δt`: time difference relative to direct Cherenkov light
+"""
+function scatteredlightfromEMshower(params::LMParameters, pmt::PMTModel, D, cd, θ, ϕ, Δt)
+    double value = 0;
+
+    const double sd = sqrt((1.0 + cd) * (1.0 - cd));
+    const double D = std::max(D_m, getRmin());
+    const double L = D;
+    const double t = D * getIndexOfRefraction() / C + t_ns; // time [ns]
+
+    const double A = getPhotocathodeArea();
+
+    const double px = sin(theta) * cos(phi);
+    const double py = sin(theta) * sin(phi);
+    const double pz = cos(theta);
+
+    const double qx = cd * px + 0 - sd * pz;
+    const double qy = 1 * py;
+    const double qz = sd * px + 0 + cd * pz;
+
+    const double n0 = getIndexOfRefractionGroup(wmax);
+    const double n1 = getIndexOfRefractionGroup(wmin);
+
+    const double ni = C * t / L; // maximal index of refraction
+
+    if (n0 >= ni) {
+      return value;
+    }
+
+    const double nj = std::min(ni, n1);
+
+    double w = wmax;
+
+    for (const_iterator i = begin(); i != end(); ++i) {
+
+      const double ng = 0.5 * (nj + n0) + i->getX() * 0.5 * (nj - n0);
+      const double dn = i->getY() * 0.5 * (nj - n0);
+
+      w = getWavelength(ng, w, 1.0e-5);
+
+      const double dw = dn / fabs(getDispersionGroup(w));
+
+      const double n = getIndexOfRefractionPhase(w);
+
+      const double l_abs = getAbsorptionLength(w);
+      const double ls = getScatteringLength(w);
+
+      const double npe = cherenkov(w, n) * dw * getQE(w);
+
+      if (npe <= 0) {
+        continue;
+      }
+
+      const double Jc = 1.0 / ls; // dN/dx
+
+      const double d = C * t / ng; // photon path
+
+      // const double V  = exp(-d/l_abs);                           //
+      // absorption
+
+      const double ds = 2.0 / (size() + 1);
+
+      for (double sb = 0.5 * ds; sb < 1.0 - 0.25 * ds; sb += ds) {
+
+        for (int buffer[] = {-1, +1, 0}, *k = buffer; *k != 0; ++k) {
+
+          const double cb = (*k) * sqrt((1.0 + sb) * (1.0 - sb));
+          const double dcb = (*k) * ds * sb / cb;
+
+          const double v = 0.5 * (d + L) * (d - L) / (d - L * cb);
+          const double u = d - v;
+
+          if (u <= 0) {
+            continue;
+          }
+          if (v <= 0) {
+            continue;
+          }
+
+          const double cts = (L * cb - v) / u; // cosine scattering angle
+
+          const double V =
+              exp(-d * getInverseAttenuationLength(l_abs, ls, cts));
+
+          if (cts < 0.0 &&
+              v * sqrt((1.0 + cts) * (1.0 - cts)) < MODULE_RADIUS_M) {
+            continue;
+          }
+
+          const double W = std::min(A / (v * v), 2.0 * PI); // solid angle
+          const double Ja = getScatteringProbability(cts);  // d^2P/dcos/dphi
+          const double Jd = ng * (1.0 - cts) / C;           // dt/du
+
+          const double ca = (L - v * cb) / u;
+          const double sa = v * sb / u;
+
+          const double dp = PI / phd.size();
+          const double dom = dcb * dp * v * v / (u * u);
+          const double dos = sqrt(dom);
+
+          for (const_iterator l = phd.begin(); l != phd.end(); ++l) {
+
+            const double cp = l->getX();
+            const double sp = l->getY();
+
+            const double ct0 = cd * ca - sd * sa * cp;
+
+            const double vx = -sb * cp * qx;
+            const double vy = -sb * sp * qy;
+            const double vz = cb * qz;
+
+            const double U =
+                getAngularAcceptance(vx + vy + vz) +
+                getAngularAcceptance(vx - vy + vz); // PMT angular acceptance
+
+            // const double Jb = geant(n,ct0);                      //
+            // d^2N/dcos/dphi
+
+            // value += npe * geanc() * dom * U * V * W * Ja * Jb * Jc /
+            // fabs(Jd);
+
+            const double Jb = geant(n, ct0 - 0.5 * dos,
+                                    ct0 + 0.5 * dos); // dN/dphi
+
+            value += npe * geanc() * dos * U * V * W * Ja * Jb * Jc / fabs(Jd);
+          }
+        }
+      }
+    }
+
+    return value;
+  }
+
+"""
+    directlightfromEMshower(params::LMParameters, pmt::PMTModel, E, D, cd, θ, ϕ, Δt)
+
+Probability density function for direct light from EM-shower. Returns
+``dP/dt`` [npe/ns].
+
+# Arguments
+- `params`: parameters of the setup
+- `pmt`: PMT model
+- `E`: EM-shower energy [GeV]
+- `D`: distance between EM-shower and PMT [m]
+- `cd`: cosine angle of EM-shower direction and EM-shower - PMT
+- `θ`: zenith  angle orientation PMT [rad]
+- `ϕ`: azimuth angle orientation PMT [rad]
+- `Δt`: time difference relative to direct Cherenkov light
+"""
+function directlightfromEMshower(params::LMParameters, pmt::PMTModel, E, D, cd, θ, ϕ, Δt)
+    double value = 0;
+
+    const double sd = sqrt((1.0 + cd) * (1.0 - cd));
+    const double D = std::max(D_m, getRmin());
+    const double R = D * sd; // minimal distance of approach [m]
+    const double Z = -D * cd;
+    const double t = D * getIndexOfRefraction() / C + t_ns; // time [ns]
+
+    const double n0 = getIndexOfRefractionGroup(wmax);
+    const double n1 = getIndexOfRefractionGroup(wmin);
+
+    double zmin = 0.0;                     // minimal shower length [m]
+    double zmax = geanz.getLength(E, 1.0); // maximal shower length [m]
+
+    const double d = sqrt((Z + zmax) * (Z + zmax) + R * R);
+
+    if (C * t > std::max(n1 * D, zmax + n1 * d)) {
+      return value;
+    }
+
+    if (C * t < n0 * D) {
+
+      JRoot rz(R, n0, t + Z / C); // square root
+
+      if (!rz.is_valid) {
+        return value;
+      }
+
+      if (rz.second > Z) {
+        zmin = rz.second - Z;
+      }
+      if (rz.first > Z) {
+        zmin = rz.first - Z;
+      }
+    }
+
+    if (C * t > n1 * D) {
+
+      JRoot rz(R, n1, t + Z / C); // square root
+
+      if (!rz.is_valid) {
+        return value;
+      }
+
+      if (rz.second > Z) {
+        zmin = rz.second - Z;
+      }
+      if (rz.first > Z) {
+        zmin = rz.first - Z;
+      }
+    }
+
+    if (C * t < zmax + n0 * d) {
+
+      JRoot rz(R, n0, t + Z / C); // square root
+
+      if (!rz.is_valid) {
+        return value;
+      }
+
+      if (rz.first > Z) {
+        zmax = rz.first - Z;
+      }
+      if (rz.second > Z) {
+        zmax = rz.second - Z;
+      }
+    }
+
+    if (zmin < 0.0) {
+      zmin = 0.0;
+    }
+
+    if (zmax > zmin) {
+
+      const double ymin = geanz.getIntegral(E, zmin);
+      const double ymax = geanz.getIntegral(E, zmax);
+      const double dy = (ymax - ymin) / size();
+
+      if (dy > 2 * std::numeric_limits<double>::epsilon()) {
+
+        for (double y = ymin + 0.5 * dy; y < ymax; y += dy) {
+
+          const double z = Z + geanz.getLength(E, y);
+          const double d = sqrt(R * R + z * z);
+          const double t1 = t + (Z - z) / C - d * getIndexOfRefraction() / C;
+
+          value +=
+              dy * E * getDirectLightFromEMshower(d, -z / d, theta, phi, t1);
+        }
+      }
+    }
+
+    return value;
+  }
+
+"""
+    scatteredlightfromEMshower(params::LMParameters, pmt::PMTModel, E, D, cd, θ, ϕ, Δt)
+
+Probability density function for scattered light from EM-shower. Returns
+``dP/dt`` [npe/ns].
+
+# Arguments
+- `params`: parameters of the setup
+- `pmt`: PMT model
+- `E`: EM-shower energy [GeV]
+- `D`: distance between EM-shower and PMT [m]
+- `cd`: cosine angle of EM-shower direction and EM-shower - PMT
+- `θ`: zenith  angle orientation PMT [rad]
+- `ϕ`: azimuth angle orientation PMT [rad]
+- `Δt`: time difference relative to direct Cherenkov light
+"""
+function scatteredlightfromEMshower(params::LMParameters, pmt::PMTModel, E, D, cd, θ, ϕ, Δt)
+  double getScatteredLightFromEMshower(const double E, const double D_m,
+                                       const double cd, const double theta,
+                                       const double phi,
+                                       const double t_ns) const {
+    double value = 0;
+
+    const double sd = sqrt((1.0 + cd) * (1.0 - cd));
+    const double D = std::max(D_m, getRmin());
+    const double R = D * sd; // minimal distance of approach [m]
+    const double Z = -D * cd;
+    const double t = D * getIndexOfRefraction() / C + t_ns; // time [ns]
+
+    const double n0 = getIndexOfRefractionGroup(wmax);
+
+    double zmin = 0.0;                     // minimal shower length [m]
+    double zmax = geanz.getLength(E, 1.0); // maximal shower length [m]
+
+    const double d = sqrt((Z + zmax) * (Z + zmax) + R * R);
+
+    if (C * t < n0 * D) {
+
+      JRoot rz(R, n0, t + Z / C); // square root
+
+      if (!rz.is_valid) {
+        return value;
+      }
+
+      if (rz.second > Z) {
+        zmin = rz.second - Z;
+      }
+      if (rz.first > Z) {
+        zmin = rz.first - Z;
+      }
+    }
+
+    if (C * t < zmax + n0 * d) {
+
+      JRoot rz(R, n0, t + Z / C); // square root
+
+      if (!rz.is_valid) {
+        return value;
+      }
+
+      if (rz.first > Z) {
+        zmax = rz.first - Z;
+      }
+      if (rz.second > Z) {
+        zmax = rz.second - Z;
+      }
+    }
+
+    const double ymin = geanz.getIntegral(E, zmin);
+    const double ymax = geanz.getIntegral(E, zmax);
+    const double dy = (ymax - ymin) / size();
+
+    if (dy > 2 * std::numeric_limits<double>::epsilon()) {
+
+      for (double y = ymin + 0.5 * dy; y < ymax; y += dy) {
+
+        const double z = Z + geanz.getLength(E, y);
+        const double d = sqrt(R * R + z * z);
+        const double t1 = t + (Z - z) / C - d * getIndexOfRefraction() / C;
+
+        value +=
+            dy * E * getScatteredLightFromEMshower(d, -z / d, theta, phi, t1);
+      }
+    }
+
+    return value;
+  }