#*****************************************************************************
# fseed.py
#
# SEED builder for SeisComP
#
# (c) 2005 Andres Heinloo, GFZ Potsdam
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version. For more information, see http://www.gnu.org/
#*****************************************************************************

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

try: str = unicode
except NameError: pass

import sys
import re
import json
import datetime
import fdsnwsscripts.seiscomp.mseedlite as mseed
from tempfile import TemporaryFile
from shutil import copyfileobj
from fdsnwsscripts.seiscomp import logs

_RECLEN_EXP = 12

def _min_data_gap(fsamp):
    return datetime.timedelta(microseconds=1000000/(fsamp * 10))

#_min_ts_gap = datetime.timedelta(minutes=1)
_min_ts_gap = datetime.timedelta(days=3650)

class SEEDError(Exception):
    pass

_rx_coeff = re.compile(r'\s*(\S+)\s*')

def _mkseedcoeff_fir(nblk, nfld, ncoeff, s):
    pos = 0
    n = 0
    c = ""

    while pos < len(s):
        m = _rx_coeff.match(s, pos)
        if m is None:
            raise SEEDError("blockette %d, field %d: error parsing FIR coefficients at '%s'" % (nblk, nfld, s[pos:]))

        try:
            v = float(m.group(1))
        except ValueError:
            raise SEEDError("blockette %d, field %d: error parsing FIR coefficients at '%s'" % (nblk, nfld, s[pos:]))

        c += "%14.7E" % (v,)
        n += 1
        pos = m.end()

    if n != ncoeff:
        raise SEEDError("blockette %d, field %d: expected %d coefficients, found %d" % (nblk, nfld, ncoeff, n))

    return c

def _mkseedcoeff_iir(nblk, nfld, ncoeff, s):
    pos = 0
    n = 0
    c = ""

    while pos < len(s):
        m = _rx_coeff.match(s, pos)
        if m is None:
            raise SEEDError("blockette %d, field %d: error parsing IIR coefficients at '%s'" % (nblk, nfld, s[pos:]))

        try:
            v = float(m.group(1))
        except ValueError:
            raise SEEDError("blockette %d, field %d: error parsing IIR coefficients at '%s'" % (nblk, nfld, s[pos:]))

        c += "%12.5E%12.5E" % (v,0)
        n += 1
        pos = m.end()

    if n != ncoeff:
        raise SEEDError("blockette %d, field %d: expected %d coefficients, found %d" % (nblk, nfld, ncoeff, n))

    return c

def _mkseedcoeff_polynomial(nblk, nfld, ncoeff, s, gain=1.0):
    pos = 0
    n = 0
    c = ""

    while pos < len(s):
        m = _rx_coeff.match(s, pos)
        if m is None:
            raise SEEDError("blockette %d, field %d: error parsing polynomial coefficients at '%s'" % (nblk, nfld, s[pos:]))

        try:
            v = float(m.group(1))
        except ValueError:
            raise SEEDError("blockette %d, field %d: error parsing polynomial coefficients at '%s'" % (nblk, nfld, s[pos:]))

        c += "%12.5E%12.5E" % (v/(gain**n),0)
        n += 1
        pos = m.end()

    if n != ncoeff:
        raise SEEDError("blockette %d, field %d: expected %d coefficients, found %d" % (nblk, nfld, ncoeff, n))

    return c

_rx_paz = re.compile(r'\s*([0-9]*)\(\s*([^,]+),\s*([^)]+)\)\s*')

def _mkseedpaz(nblk, nfld, npaz, s):
    pos = 0
    n = 0
    c = ""

    l = 0
    if not s is None: l = len(s)

    while pos < l:
        m = _rx_paz.match(s, pos)
        if m is None:
            raise SEEDError("blockette %d, field %d: error parsing PAZ at '%s'" % (nblk, nfld, s[pos:]))

        try:
            if len(m.group(1)) > 0:
                x = int(m.group(1))
            else:
                x = 1

            rv = float(m.group(2))
            iv = float(m.group(3))

        except ValueError:
            raise SEEDError("blockette %d, field %d: error parsing PAZ at '%s'" % (nblk, nfld, s[pos:]))

        for i in range(0, x):
            c += "%12.5E%12.5E 0.00000E-00 0.00000E-00" % (rv, iv)

        n += x
        pos = m.end()

    if not npaz is None and (n != npaz):
        raise SEEDError("blockette %d, field %d: expected %d PAZ, found %d" % (nblk, nfld, npaz, n))

    return (c,n)

def _mkseedfap(nblk, nfld, nfap, s):
    pos = 0
    n = 0
    c = ""

    values = s.split()
    l = len(values)
    while pos < l:
      try:
        f = float(values[pos])
      except:
        raise SEEDError("blockette %d, field %d: error parsing FAP at '%s'" % (nblk, nfld, values[pos]))

      pos += 1

      try:
        a = float(values[pos])
      except:
        raise SEEDError("blockette %d, field %d: error parsing FAP at '%s'" % (nblk, nfld, values[pos]))

      pos += 1

      try:
        p = float(values[pos])
      except:
        raise SEEDError("blockette %d, field %d: error parsing FAP at '%s'" % (nblk, nfld, values[pos]))

      pos += 1

      c += "%12.5E%12.5E 0.00000E-00%12.5E 0.00000E-00" % (f, a, p)

      n += 1

    if not nfap is None and (n != nfap):
        raise SEEDError("blockette %d, field %d: expected %d FAP, found %d" % (nblk, nfld, nfap, n))

    return (c,n)

def _mkseedstring(nblk, nfld, s, min_length, max_length, flags):
    U = L = N = P = S = X = False
    rx_list = []

    if flags.find("U") != -1:
        U = True
        rx_list.append("[A-Z]")

    if flags.find("L") != -1:
        L = True
        rx_list.append("[a-z]")

    if flags.find("N") != -1:
        N = True
        rx_list.append("[0-9]")

    if flags.find("P") != -1:
        P = True
        rx_list.append("[^A-Za-z0-9 ]")

    if flags.find("S") != -1:
        S = True
        rx_list.append(" ")

    if flags.find("_") != -1:
        X = True
        rx_list.append("_")

    if s is None:
        s = ""

    sn = s.strip()[:max_length]

    if U and not L:
        sn = sn.upper()
    elif L and not U:
        sn = sn.lower()

    if S and not X:
        sn = sn.replace("_", " ")
    elif X and not S:
        sn = sn.replace(" ", "_")

    rx = "|".join(rx_list)
    sn = "".join(re.findall(rx, sn))

    if re.match("(" + rx + ")*$", sn) is None:
        raise SEEDError("blockette %d, field %d: cannot convert string \"%s\" with flags %s" % \
          (nblk, nfld, s, flags))

    if len(sn) < min_length:
        if min_length != max_length:
            raise SEEDError("blockette %d, field %d: cannot extend string \"%s\" to minimum length %d with flags %s" % \
              (nblk, nfld, s, min_length, flags))
        else:
            sn = (sn + min_length * " ")[:min_length]

    if min_length != max_length:
        sn += "~"

    return sn

def _mkseedtime(nblk, nfld, t):
    if t is None:
        return "~"

    if isinstance(t, datetime.datetime):
        tt = t.utctimetuple()
        return "%04d,%03d,%02d:%02d:%02d.%04d~" % (t.year, tt[7],
            t.hour, t.minute, t.second, t.microsecond // 100)
    elif isinstance(t, datetime.date):
        tt = datetime.datetime.combine(t, datetime.time(0, 0, 0)).utctimetuple()
        return "%04d,%03d~" % (t.year, tt[7])

    raise SEEDError("blockette %d, field %d: invalid time object: %s" % (nblk, nfld, str(t)))

def _cmptime(t1, t2):
    if t1 is None and t2 is None:
        return 0
    elif t2 is None or (t1 is not None and t1 < t2):
        return -1
    elif t1 is None or (t2 is not None and t1 > t2):
        return 1

    return 0

def _is_fir_response(obj):
    return hasattr(obj, "symmetry")

def _is_iir_response(obj):
    return hasattr(obj, "denominators")

def _is_paz_response(obj):
    return hasattr(obj, "poles")

def _is_poly_response(obj):
    return hasattr(obj, "approximationType")

def _is_fap_response(obj):
    return hasattr(obj, "tuples")

class _Blockette10(object):
    def __init__(self, record_length, start_time, end_time, vol_time,
        organization, label):
        self.__record_length = record_length
        self.__start_time = _mkseedtime(10, 5, start_time)
        self.__end_time = _mkseedtime(10, 6, end_time)
        self.__vol_time = _mkseedtime(10, 7, vol_time)
        self.__organization = _mkseedstring(10, 8, organization, 1, 80, "UNLPS_")
        self.__label = _mkseedstring(10, 9, label, 0, 80, "UNLPS_")
        self.__len = 13 + len(self.__start_time) + len(self.__end_time) + \
            len(self.__vol_time) + len(self.__organization) + \
            len(self.__label)

    def output(self, f):
        blk = "010%4d 2.3%2d%s%s%s%s%s" % (self.__len, self.__record_length,
            self.__start_time, self.__end_time, self.__vol_time,
            self.__organization, self.__label)

        if len(blk) != self.__len:
            raise SEEDError("blockette 10 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette11(object):
    def __init__(self):
        self.__nstations = 0
        self.__stat_rec = ()
        self.__len = 10

    def add_station(self, code, recno):
        self.__stat_rec += (_mkseedstring(11, 4, code, 5, 5, "UN"), recno)
        self.__nstations += 1
        self.__len += 11

    def __output_huge(self, f):
        n = 0
        while n < self.__nstations:
            ns = min(self.__nstations - n, 908)
            blen = 10 + 11 * ns

            blk = ("011%4d%3d" + ns * "%s%6d") % \
                ((blen, ns) + self.__stat_rec[2*n:2*(n+ns)])

            if len(blk) != blen:
                raise SEEDError("blockette 11 has invalid length: %d instead of %d" % (len(blk), blen))

            f.write_blk(blk)
            n += ns

    def output(self, f):
        if self.__len > 9999:
            self.__output_huge(f)
            return

        blk = ("011%4d%3d" + self.__nstations * "%s%6d") % \
            ((self.__len, self.__nstations) + self.__stat_rec)

        if len(blk) != self.__len:
            raise SEEDError("blockette 11 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette12(object):
    def __init__(self):
        self.__nspans = 0
        self.__span_rec = ()
        self.__len = 11

    def add_span(self, begin, end, recno):
        self.__span_rec += (_mkseedtime(12, 4, begin), _mkseedtime(12, 5, end), recno)
        self.__nspans += 1
        self.__len += 52

    def __output_huge(self, f):
        n = 0
        while n < self.__nspans:
            ns = min(self.__nspans - n, 192)
            blen = 11 + 52 * ns

            blk = ("012%4d%4d" + ns * "%s%s%6d") % \
                ((blen, ns) + self.__span_rec[3*n:3*(n+ns)])

            if len(blk) != blen:
                raise SEEDError("blockette 12 has invalid length: %d instead of %d" % (len(blk), blen))

            f.write_blk(blk)
            n += ns

    def output(self, f):
        if self.__len > 9999:
            self.__output_huge(f)
            return

        blk = ("012%4d%4d" + self.__nspans * "%s%s%6d") % \
            ((self.__len, self.__nspans) + self.__span_rec)

        if len(blk) != self.__len:
            raise SEEDError("blockette 12 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette30(object):
    def __init__(self, name, key, family, ddl):
        self.__name = _mkseedstring(30, 3, name, 1, 50, "UNLPS")
        self.__key = key
        self.__family = family
        self.__ddl = "~".join(ddl) + "~"
        self.__nddl = len(ddl)
        self.__len = 16 + len(self.__name) + len(self.__ddl)

    def output(self, f):
        blk = "030%4d%s%4d%3d%2d%s" % (self.__len, self.__name,
            self.__key, self.__family, self.__nddl, self.__ddl)

        if len(blk) != self.__len:
            raise SEEDError("blockette 30 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette31(object):
    def __init__(self, key, cclass, comment):
        self.__key = key
        self.__cclass = cclass
        self.__comment = _mkseedstring(31, 5, comment, 1, 70, "UNLPS")
        self.__len = 15 + len(self.__comment)

    def output(self, f):
        blk = "031%4d%4d%s%s  0" % (self.__len, self.__key, self.__cclass, self.__comment)

        if len(blk) != self.__len:
            raise SEEDError("blockette 31 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette33(object):
    def __init__(self, key, desc):
        self.__key = key
        self.__desc = _mkseedstring(33, 4, desc, 1, 50, "UNLPS")
        self.__len = 10 + len(self.__desc)

    def output(self, f):
        blk = "033%4d%3d%s" % (self.__len, self.__key, self.__desc)

        if len(blk) != self.__len:
            raise SEEDError("blockette 33 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette34(object):
    def __init__(self, key, name, desc):
        self.__key = key
        self.__name = _mkseedstring(34, 4, name, 1, 20, "UNP")
        self.__desc = _mkseedstring(34, 5, desc, 1, 50, "UNLPS")
        self.__len = 10 + len(self.__name) + len(self.__desc)

    def output(self, f):
        blk = "034%4d%3d%s%s" % (self.__len, self.__key, self.__name,
            self.__desc)

        if len(blk) != self.__len:
            raise SEEDError("blockette 34 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette41(object):
    def __init__(self, key, name, symmetry, input_units, output_units, ncoeff,
        coeff):

        self.__key = key
        self.__name = _mkseedstring(41, 4, name, 1, 25, "UN")
        self.__symmetry = _mkseedstring(41, 5, symmetry, 1, 1, "U")
        self.__input_units = input_units
        self.__output_units = output_units
        self.__ncoeff = ncoeff
        self.__coeff = _mkseedcoeff_fir(41, 9, ncoeff, coeff)
        self.__len = 22 + 14 * ncoeff + len(self.__name)

    def __output_huge(self, f):
        n = 0
        while n < self.__ncoeff:
            nc = min(self.__ncoeff - n, (9977 - len(self.__name)) // 14)
            blen = 22 + 14 * nc + len(self.__name)

            blk = "041%4d%4d%s%s%3d%3d%4d%s" % (blen, self.__key,
                self.__name, self.__symmetry, self.__input_units,
                self.__output_units, self.__ncoeff, self.__coeff[14*n:14*(n+nc)])

            if len(blk) != blen:
                raise SEEDError("blockette 41 has invalid length: %d instead of %d" % (len(blk), blen))

            f.write_blk(blk)
            n += nc

    def output(self, f):
        if self.__len > 9999:
            self.__output_huge(f)
            return

        blk = "041%4d%4d%s%s%3d%3d%4d%s" % (self.__len, self.__key,
            self.__name, self.__symmetry, self.__input_units,
            self.__output_units, self.__ncoeff, self.__coeff)

        if len(blk) != self.__len:
            raise SEEDError("blockette 41 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette42(object):
    def __init__(self, key, name, input_units, output_units, freq_unit, low_freq,
       high_freq, approx_type, approx_lower_bound, approx_upper_bound,
       approx_error, ncoeff, coeff):

        self.__key = key
        self.__name = _mkseedstring(42, 4, name, 1, 25, "UN")
        self.__input_units = input_units
        self.__output_units = output_units
        self.__freq_unit = freq_unit
        self.__low_freq = low_freq
        self.__high_freq = high_freq
        self.__approx_type = approx_type
        self.__approx_lower_bound = approx_lower_bound
        self.__approx_upper_bound = approx_upper_bound
        self.__approx_error = approx_error
        self.__ncoeff = ncoeff
        self.__coeff = _mkseedcoeff_polynomial(42, 16, ncoeff, coeff)
        self.__len = 83 + 24 * ncoeff + len(self.__name)

    def output(self, f):
        blk = "042%4d%4d%sP%3d%3d%1s%1s%12.5E%12.5E%12.5E%12.5E%12.5E%3d%s" % (self.__len,
            self.__key, self.__name, self.__input_units, self.__output_units,
            self.__approx_type, self.__freq_unit, self.__low_freq, self.__high_freq,
            self.__approx_lower_bound, self.__approx_upper_bound, self.__approx_error,
            self.__ncoeff, self.__coeff)

        if len(blk) != self.__len:
            raise SEEDError("blockette 42 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette43(object):
    def __init__(self, key, name, type, input_units, output_units, norm_fac,
        norm_freq, nzeros, zeros, npoles, poles):

        self.__key = key
        self.__name = _mkseedstring(43, 4, name, 1, 25, "UN")
        self.__type = _mkseedstring(43, 5, type, 1, 1, "U")
        self.__input_units = input_units
        self.__output_units = output_units
        self.__norm_fac = norm_fac
        self.__norm_freq = norm_freq
        self.__zeros, nzeros = _mkseedpaz(43, 11, nzeros, zeros)
        self.__nzeros = nzeros
        self.__poles, npoles = _mkseedpaz(43, 16, npoles, poles)
        self.__npoles = npoles
        self.__len = 48 + 48 * (nzeros + npoles) + len(self.__name)

    def output(self, f):
        blk = "043%4d%4d%s%s%3d%3d%12.5E%12.5E%3d%s%3d%s" % \
            (self.__len, self.__key, self.__name, self.__type,
            self.__input_units, self.__output_units, self.__norm_fac,
            self.__norm_freq, self.__nzeros, self.__zeros, self.__npoles,
            self.__poles)

        if len(blk) != self.__len:
            raise SEEDError("blockette 43 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette44(object):
    def __init__(self, key, name, type, input_units, output_units,
        n_numerators=0, numerators="", n_denominators=0, denominators=""):

        self.__key = key
        self.__name = _mkseedstring(44, 4, name, 1, 25, "UN")
        self.__type = _mkseedstring(44, 5, type, 1, 1, "U")
        self.__input_units = input_units
        self.__output_units = output_units
        self.__n_numerators = n_numerators
        self.__numerators = _mkseedcoeff_iir(54, 8, n_numerators, numerators)
        self.__n_denominators = n_denominators
        self.__denominators = _mkseedcoeff_iir(54, 11, n_denominators, denominators)
        self.__len = 26 + len(self.__name) + 24 * n_numerators + 24 * n_denominators

    def output(self, f):
        blk = "044%4d%4d%s%s%3d%3d%4d%s%4d%s" % (self.__len, self.__key,
            self.__name, self.__type, self.__input_units, self.__output_units,
            self.__n_numerators, self.__numerators, self.__n_denominators, self.__denominators)

        if len(blk) != self.__len:
            raise SEEDError("blockette 44 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette45(object):
    def __init__(self, key, name, type, input_units, output_units, ntuples, tuples):

        self.__key = key
        self.__name = _mkseedstring(45, 4, name, 1, 25, "UN")
        self.__input_units = input_units
        self.__output_units = output_units
        self.__tuples, ntuples = _mkseedfap(55, 7, ntuples, tuples)
        self.__ntuples = ntuples
        self.__len = 21 + 60 * ntuples + len(self.__name)

    def output(self, f):
        if self.__len < 10000:
            blk = "055%4d%4d%s%3d%3d%4d%s" % \
                (self.__len, self.__key, self.__name,
                 self.__input_units, self.__output_units,
                 self.__ntuples, self.__tuples)

            if len(blk) != self.__len:
                raise SEEDError("blockette 45 has invalid length: %d instead of %d" % (len(blk), self.__len))

            f.write_blk(blk)
        else:
            btuples = int((9999-21-len(self.__name))/60)
            l = 21 + 60 * btuples + len(self.__name)
            blk = "055%4d%4d%s%3d%3d%4d%s" % \
                (l, self.__key, self.__name,
                 self.__input_units, self.__output_units,
                 btuples, self.__tuples[:btuples*60])

            if len(blk) != l:
                raise SEEDError("blockette 45 has invalid length: %d instead of %d" % (len(blk), l))

            f.write_blk(blk)

            left_tuples = self.__ntuples-btuples
            ofs = btuples*60
            while left_tuples > 0:
                if left_tuples > btuples:
                    ntuples = btuples
                else:
                    ntuples = left_tuples
                l = 21 + ntuples * 60 + len(self.__name)
                blk = "055%4d%4d%s%3d%3d%4d" % \
                (l, self.__key, self.__name,
                 self.__input_units, self.__output_units,
                 ntuples, self.__tuples[ofs:ofs+ntuples*60])

                if len(blk) != l:
                    raise SEEDError("blockette 45 has invalid length: %d instead of %d" % (len(blk), l))

                f.write_blk(blk)

                ofs += ntuples*60
                left_tuples -= ntuples

class _Blockette47(object):
    def __init__(self, key, name, input_rate, deci_fac, deci_offset,
        delay, correction):

        self.__key = key
        self.__name = _mkseedstring(47, 4, name, 1, 25, "UN")
        self.__input_rate = input_rate
        self.__deci_fac = deci_fac
        self.__deci_offset = deci_offset
        self.__delay = delay
        self.__correction = correction
        self.__len = 53 + len(self.__name)

    def output(self, f):
        blk = "047%4d%4d%s%10.4E%5d%5d%11.4E%11.4E" % (self.__len,
            self.__key, self.__name, self.__input_rate, self.__deci_fac,
            self.__deci_offset, self.__delay, self.__correction)

        if len(blk) != self.__len:
            raise SEEDError("blockette 47 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette48(object):
    def __init__(self, key, name, gain, gain_freq):
        self.__key = key
        self.__name = _mkseedstring(48, 4, name, 1, 25, "UN")
        self.__gain = gain
        self.__gain_freq = gain_freq
        self.__len = 37 + len(self.__name)

    def output(self, f):
        blk = "048%4d%4d%s%12.5E%12.5E 0" % (self.__len, self.__key,
            self.__name, self.__gain, self.__gain_freq)

        if len(blk) != self.__len:
            raise SEEDError("blockette 48 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette50(object):
    def __init__(self, stat_code, latitude, longitude, elevation,
        site_name, net_id, net_code, start_date, end_date):
        self.__stat_code = _mkseedstring(50, 3, stat_code, 5, 5, "UN")
        self.__latitude = latitude
        self.__longitude = longitude
        self.__elevation = elevation
        self.__site_name = _mkseedstring(50, 9, site_name, 1, 60, "UNLPS")
        self.__net_id = net_id
        self.__start_date = _mkseedtime(50, 13, start_date)
        self.__end_date = _mkseedtime(50, 14, end_date)
        self.__net_code = _mkseedstring(50, 16, net_code, 2, 2, "UN")
        self.__len = 59 + len(self.__site_name) + len(self.__start_date) + \
            len(self.__end_date)

        error = False
        if not isinstance(self.__stat_code, str):
            print("blockette 50 - station code not set or not of type string", file=sys.stderr)
            error = True
        if not isinstance(self.__latitude, float):
            print("blockette 50 - latitude not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__longitude, float):
            print("blockette 50 - longitude not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__elevation, float):
            print("blockette 50 - elevation not set or not of type float", file=sys.stderr)
            error = True

        if error:
            raise SEEDError("blockette 50 - provide correct values. Adjust your inventory XML!")

    def output(self, f):
        blk = "050%4d%s%10.6f%11.6f%7.1f       %s%3d321010%s%sN%s" % \
            (self.__len, self.__stat_code, self.__latitude, self.__longitude,
            self.__elevation, self.__site_name, self.__net_id,
            self.__start_date, self.__end_date, self.__net_code)

        if len(blk) != self.__len:
            raise SEEDError("blockette 50 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette51(object):
    def __init__(self, start_time, end_time, comment_key):
        self.__start_time = _mkseedtime(51, 3, start_time)
        self.__end_time = _mkseedtime(51, 4, end_time)
        self.__comment_key = comment_key
        self.__len = 17 + len(self.__start_time) + len(self.__end_time)

    def output(self, f):
        blk = "051%4d%s%s%4d     0" % (self.__len, self.__start_time,
            self.__end_time, self.__comment_key)

        if len(blk) != self.__len:
            raise SEEDError("blockette 51 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette52(object):
    def __init__(self, loc_id, chan_id, instr_id, comment, signal_units,
        calibration_units, latitude, longitude, elevation, local_depth,
        azimuth, dip, data_format, record_length, sample_rate, clock_drift,
        flags, start_date, end_date):
        self.__loc_id = _mkseedstring(52, 3, loc_id, 2, 2, "UN")
        self.__chan_id = _mkseedstring(52, 4, chan_id, 3, 3, "UN")
        self.__instr_id = instr_id
        self.__comment = _mkseedstring(52, 7, comment, 0, 30, "UNLPS")
        self.__signal_units = signal_units
        self.__calibration_units = calibration_units
        self.__latitude = latitude
        self.__longitude = longitude
        self.__elevation = elevation
        self.__local_depth = local_depth
        self.__azimuth = azimuth
        self.__dip = dip
        self.__data_format = data_format
        self.__record_length = record_length
        self.__sample_rate = sample_rate
        self.__clock_drift = clock_drift
        self.__flags = _mkseedstring(52, 21, flags, 0, 26, "U")
        self.__raw_start_date = start_date
        self.__raw_end_date = end_date
        self.__len = 0

        error = False
        if not isinstance(self.__loc_id, str):
            print("blockette 52 - location ID not set or not of type string", file=sys.stderr)
            error = True
        if not isinstance(self.__chan_id, str):
            print("blockette 52 - channel ID not set or not of type string", file=sys.stderr)
            error = True
        if not isinstance(self.__len, int):
            print("blockette 52 - len not set or not of type int", file=sys.stderr)
            error = True
        if not isinstance(self.__instr_id, int):
            print("blockette 52 - instrument ID not set or not of type integer", file=sys.stderr)
            error = True
        if not isinstance(self.__comment, str):
            print("blockette 52 - comment not set or not of type sting", file=sys.stderr)
            error = True
        if not isinstance(self.__signal_units, int):
            print("blockette 52 - signal_units not set or not of type integer", file=sys.stderr)
            error = True
        if not isinstance(self.__calibration_units, int):
            print("blockette 52 - calibration units not set or not of type integer", file=sys.stderr)
            error = True
        if not isinstance(self.__latitude, float):
            print("blockette 52 - latitude not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__longitude, float):
            print("blockette 52 - longitude not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__elevation, float):
            print("blockette 52 - elevation not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__local_depth, float):
            print("Warning: blockette 52 - local depth not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__azimuth, float):
            print("blockette 52 - azimuth not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__dip, float):
            print("blockette 52 - dip not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__data_format, int):
            print("blockette 52 - data format not set or not of type integer", file=sys.stderr)
            error = True
        if not isinstance(self.__record_length, int):
            print("blockette 52 - record length not set or not of type integer", file=sys.stderr)
            error = True
        if not isinstance(self.__sample_rate, float):
            print("blockette 52 - sample rate not set or not of type float", file=sys.stderr)
            error = True
        if not isinstance(self.__clock_drift, float):
            print("Warning: blockette 52 - clock drift not set or not of type float", file=sys.stderr)
            error = True

        if error:
            raise SEEDError("blockette 52 - provide correct values. Adjust your inventory XML!")

    def set_vol_span(self, vol_start, vol_end):
        # make verseed happy
        if _cmptime(self.__raw_end_date, vol_end) > 0:
            self.__raw_end_date = vol_end

        if _cmptime(self.__raw_start_date, vol_start) < 0:
            self.__raw_start_date = vol_start

    def output(self, f):
        self.__start_date = _mkseedtime(52, 22, self.__raw_start_date)
        self.__end_date = _mkseedtime(52, 23, self.__raw_end_date)
        self.__len = 99 + len(self.__comment) + len(self.__flags) + \
            len(self.__start_date) + len(self.__end_date)

        blk = "052%4d%s%s   0%3d%s%3d%3d%10.6f%11.6f%7.1f%5.1f%5.1f%5.1f%4d%2d%10.4E%10.4E    %s%s%sN" % \
            (self.__len, self.__loc_id, self.__chan_id, self.__instr_id,
            self.__comment, self.__signal_units, self.__calibration_units,
            self.__latitude, self.__longitude, self.__elevation,
            self.__local_depth, self.__azimuth, self.__dip, self.__data_format,
            self.__record_length, self.__sample_rate, self.__clock_drift,
            self.__flags, self.__start_date, self.__end_date)

        if len(blk) != self.__len:
            raise SEEDError("blockette 52 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette53(object):
    def __init__(self, type, input_units, output_units, norm_fac,
        norm_freq, nzeros, zeros, npoles, poles):

        self.__type = _mkseedstring(53, 3, type, 1, 1, "U")
        self.__stage = 0
        self.__input_units = input_units
        self.__output_units = output_units
        self.__norm_fac = norm_fac
        self.__norm_freq = norm_freq
        self.__zeros, nzeros = _mkseedpaz(53, 10, nzeros, zeros)
        self.__nzeros = nzeros
        self.__poles, npoles = _mkseedpaz(53, 15, npoles, poles)
        self.__npoles = npoles
        self.__len = 46 + 48 * (nzeros + npoles)

    def set_stage(self, stage):
        self.__stage = stage

    def output(self, f):
        blk = "053%4d%s%2d%3d%3d%12.5E%12.5E%3d%s%3d%s" % \
            (self.__len, self.__type, self.__stage,
            self.__input_units, self.__output_units, self.__norm_fac,
            self.__norm_freq, self.__nzeros, self.__zeros, self.__npoles,
            self.__poles)

        if len(blk) != self.__len:
            raise SEEDError("blockette 53 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette54(object):
    def __init__(self, type, input_units, output_units,
        n_numerators=0, numerators="", n_denominators=0, denominators=""):

        self.__type = _mkseedstring(54, 3, type, 1, 1, "U")
        self.__stage = 0
        self.__input_units = input_units
        self.__output_units = output_units
        self.__n_numerators = n_numerators
        self.__numerators = _mkseedcoeff_iir(54, 8, n_numerators, numerators)
        self.__n_denominators = n_denominators
        self.__denominators = _mkseedcoeff_iir(54, 11, n_denominators, denominators)
        self.__len = 24 + 24 * n_numerators + 24 * n_denominators

    def set_stage(self, stage):
        self.__stage = stage

    def output(self, f):
        blk = "054%4d%s%2d%3d%3d%4d%s%4d%s" % (self.__len,
            self.__type, self.__stage, self.__input_units, self.__output_units,
            self.__n_numerators, self.__numerators, self.__n_denominators, self.__denominators)

        if len(blk) != self.__len:
            raise SEEDError("blockette 54 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette55(object):
    def __init__(self, input_units, output_units, ntuples, tuples):
        self.__stage = 0
        self.__input_units = input_units
        self.__output_units = output_units
        self.__tuples, ntuples = _mkseedfap(55, 7, ntuples, tuples)
        self.__ntuples = ntuples
        self.__len = 19 + 60 * ntuples

    def set_stage(self, stage):
        self.__stage = stage

    def output(self, f):
        if self.__len < 10000:
            blk = "055%4d%2d%3d%3d%4d%s" % \
                (self.__len, self.__stage,
                 self.__input_units, self.__output_units,
                 self.__ntuples, self.__tuples)

            if len(blk) != self.__len:
                raise SEEDError("blockette 55 has invalid length: %d instead of %d" % (len(blk), self.__len))

            f.write_blk(blk)
        else:
            l = 19 + 166*60
            blk = "055%4d%2d%3d%3d%4d%s" % \
                (l, self.__stage,
                 self.__input_units, self.__output_units,
                 166, self.__tuples[:166*60])

            if len(blk) != l:
                raise SEEDError("blockette 55 has invalid length: %d instead of %d" % (len(blk), l))

            f.write_blk(blk)

            left_tuples = self.__ntuples-166
            ofs = 166*60
            while left_tuples > 0:
                if left_tuples > 166:
                    ntuples = 166
                else:
                    ntuples = left_tuples
                l = 19 + ntuples * 60
                blk = "055%4d%2d%3d%3d%4d%s" % \
                (l, self.__stage,
                 self.__input_units, self.__output_units,
                 ntuples, self.__tuples[ofs:ofs+ntuples*60])

                if len(blk) != l:
                    raise SEEDError("blockette 55 has invalid length: %d instead of %d" % (len(blk), l))

                f.write_blk(blk)

                ofs += ntuples*60
                left_tuples -= ntuples

class _Blockette57(object):
    def __init__(self, input_rate, deci_fac, deci_offset, delay, correction):
        self.__stage = 0
        self.__input_rate = input_rate
        self.__deci_fac = deci_fac
        self.__deci_offset = deci_offset
        self.__delay = delay
        self.__correction = correction
        self.__len = 51

    def set_stage(self, stage):
        self.__stage = stage

    def output(self, f):
        blk = "057%4d%2d%10.4E%5d%5d%11.4E%11.4E" % (self.__len,
            self.__stage, self.__input_rate, self.__deci_fac,
            self.__deci_offset, self.__delay, self.__correction)

        if len(blk) != self.__len:
            raise SEEDError("blockette 57 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette58(object):
    def __init__(self, gain, gain_freq):
        self.__stage = 0
        self.__gain = gain
        self.__gain_freq = gain_freq
        self.__len = 35

    def set_stage(self, stage):
        self.__stage = stage

    def output(self, f):
        blk = "058%4d%2d%12.5E%12.5E 0" % (self.__len, self.__stage,
            self.__gain, self.__gain_freq)

        if len(blk) != self.__len:
            raise SEEDError("blockette 58 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette59(object):
    def __init__(self, start_time, end_time, comment_key):
        self.__start_time = _mkseedtime(51, 3, start_time)
        self.__end_time = _mkseedtime(51, 4, end_time)
        self.__comment_key = comment_key
        self.__len = 17 + len(self.__start_time) + len(self.__end_time)

    def output(self, f):
        blk = "059%4d%s%s%4d     0" % (self.__len, self.__start_time,
            self.__end_time, self.__comment_key)

        if len(blk) != self.__len:
            raise SEEDError("blockette 59 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette60(object):
    def __init__(self, start_stage):
        self.__start_stage = start_stage
        self.__ref_list = []
        self.__len = 9

    def add_stage(self, *keyref):
        self.__ref_list.append(keyref)
        self.__len += 4 + 4 * len(keyref)

    def output(self, f):
        blk = "060%4d%2d" % (self.__len, len(self.__ref_list))

        for (n, r) in enumerate(self.__ref_list):
            blk += ("%2d%2d" + len(r) * "%4d") % \
               ((self.__start_stage + n, len(r)) + r)

        if len(blk) != self.__len:
            raise SEEDError("blockette 60 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette61(object):
    def __init__(self, name, symmetry, input_units, output_units, ncoeff,
        coeff):

        self.__stage = 0
        self.__name = _mkseedstring(61, 4, name, 1, 25, "UN")
        self.__symmetry = _mkseedstring(61, 5, symmetry, 1, 1, "U")
        self.__input_units = input_units
        self.__output_units = output_units
        self.__ncoeff = ncoeff
        self.__coeff = _mkseedcoeff_fir(61, 9, ncoeff, coeff)
        self.__len = 20 + 14 * ncoeff + len(self.__name)

    def set_stage(self, stage):
        self.__stage = stage

    def __output_huge(self, f):
        n = 0
        while n < self.__ncoeff:
            nc = min(self.__ncoeff - n, (9977 - len(self.__name)) // 14)
            blen = 20 + 14 * nc + len(self.__name)

            blk = "061%4d%2d%s%s%3d%3d%4d%s" % (blen, self.__stage,
                self.__name, self.__symmetry, self.__input_units,
                self.__output_units, nc, self.__coeff[14*n:14*(n+nc)])

            if len(blk) != blen:
                raise SEEDError("blockette 61 has invalid length: %d instead of %d" % (len(blk), self.__len))

            f.write_blk(blk)
            n += nc

    def output(self, f):
        if self.__len > 9999:
            self.__output_huge(f)
            return

        blk = "061%4d%2d%s%s%3d%3d%4d%s" % (self.__len, self.__stage,
            self.__name, self.__symmetry, self.__input_units,
            self.__output_units, self.__ncoeff, self.__coeff)

        if len(blk) != self.__len:
            raise SEEDError("blockette 61 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette62(object):
    def __init__(self, input_units, output_units, freq_unit, low_freq,
        high_freq, approx_type, approx_lower_bound, approx_upper_bound,
        approx_error, ncoeff, coeff, gain=1.0):

        self.__stage = 0
        self.__input_units = input_units
        self.__output_units = output_units
        self.__freq_unit = freq_unit
        self.__low_freq = low_freq
        self.__high_freq = high_freq
        self.__approx_type = approx_type
        self.__approx_lower_bound = approx_lower_bound
        self.__approx_upper_bound = approx_upper_bound
        self.__approx_error = approx_error
        self.__ncoeff = ncoeff
        self.__coeff = _mkseedcoeff_polynomial(62, 15, ncoeff, coeff, gain)
        self.__len = 81 + 24 * ncoeff

    def set_stage(self, stage):
        self.__stage = stage

    def output(self, f):
        blk = "062%4dP%2d%3d%3d%1s%1s%12.5E%12.5E%12.5E%12.5E%12.5E%3d%s" % (self.__len,
            self.__stage, self.__input_units, self.__output_units,
            self.__approx_type[:1], self.__freq_unit[:1], self.__low_freq, self.__high_freq,
            self.__approx_lower_bound, self.__approx_upper_bound, self.__approx_error,
            self.__ncoeff, self.__coeff)

        if len(blk) != self.__len:
            raise SEEDError("blockette 62 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette70(object):
    def __init__(self, flag, begin, end):
        self.__flag = flag
        self.__begin = _mkseedtime(70, 4, begin)
        self.__end = _mkseedtime(70, 5, end)
        self.__len = 54

    def output(self, f):
        blk = "070%4d%c%s%s" % (self.__len, self.__flag, self.__begin,
            self.__end)

        if len(blk) != self.__len:
            raise SEEDError("blockette 70 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _Blockette74(object):
    def __init__(self, net_code, stat_code, loc_id, chan_id,
        start_time, start_recno, end_time, end_recno):
        self.__net_code = _mkseedstring(74, 16, net_code, 2, 2, "UN")
        self.__stat_code = _mkseedstring(74, 3, stat_code, 5, 5, "UN")
        self.__loc_id = _mkseedstring(74, 4, loc_id, 2, 2, "UN")
        self.__chan_id = _mkseedstring(74, 5, chan_id, 3, 3, "UN")
        self.__start_time = _mkseedtime(74, 6, start_time)
        self.__start_recno = start_recno
        self.__end_time = _mkseedtime(74, 9, end_time)
        self.__end_recno = end_recno
        self.__len = 84

    def add_accelerator(self, recno):
        pass

    def output(self, f):
        blk = "074%4d%s%s%s%s%6d 1%s%6d 1  0%s" % (self.__len,
            self.__stat_code, self.__loc_id, self.__chan_id,
            self.__start_time, self.__start_recno, self.__end_time,
            self.__end_recno, self.__net_code)

        if len(blk) != self.__len:
            raise SEEDError("blockette 74 has invalid length: %d instead of %d" % (len(blk), self.__len))

        f.write_blk(blk)

class _FormatDict(object):
    __formats = {
        "Steim1": ("Steim1 Integer Compression Format", 50,
            "F1 P4 W4 D C2 R1 P8 W4 D C2",
            "P0 W4 N15 S2,0,1",
            "T0 X W4",
            "T1 Y4 W7 D C2",
            "T2 Y2 W2 D C2",
            "T3 N0 W4 D C2"),

        "steim1": ("Steim1 Integer Compression Format", 50,
            "F1 P4 W4 D C2 R1 P8 W4 D C2",
            "P0 W4 N15 S2,0,1",
            "T0 X W4",
            "T1 Y4 W7 D C2",
            "T2 Y2 W2 D C2",
            "T3 N0 W4 D C2"),

        "Steim2": ("Steim2 Integer Compression Format", 50,
            "F1 P4 W4 D C2 R1 P8 W4 D C2",
            "P0 W4 N15 S2,0,1",
            "T0 X W4",
            "T1 Y4 W1 D C2",
            "T2 W4 I D2",
            "K0 X D30",
            "K1 N0 D30 C2",
            "K2 Y2 D15 C2",
            "K3 Y3 D10 C2",
            "T3 W4 I D2",
            "K0 Y5 D6 C2",
            "K1 Y6 D5 C2",
            "K2 X D2 Y7 D4 C2",
            "K3 X D30"),

        "steim2": ("Steim2 Integer Compression Format", 50,
            "F1 P4 W4 D C2 R1 P8 W4 D C2",
            "P0 W4 N15 S2,0,1",
            "T0 X W4",
            "T1 Y4 W1 D C2",
            "T2 W4 I D2",
            "K0 X D30",
            "K1 N0 D30 C2",
            "K2 Y2 D15 C2",
            "K3 Y3 D10 C2",
            "T3 W4 I D2",
            "K0 Y5 D6 C2",
            "K1 Y6 D5 C2",
            "K2 X D2 Y7 D4 C2",
            "K3 X D30"),

        "mseed10": ("Steim1 Integer Compression Format", 50,
            "F1 P4 W4 D C2 R1 P8 W4 D C2",
            "P0 W4 N15 S2,0,1",
            "T0 X W4",
            "T1 Y4 W7 D C2",
            "T2 Y2 W2 D C2",
            "T3 N0 W4 D C2"),

        "mseed11": ("Steim2 Integer Compression Format", 50,
            "F1 P4 W4 D C2 R1 P8 W4 D C2",
            "P0 W4 N15 S2,0,1",
            "T0 X W4",
            "T1 Y4 W1 D C2",
            "T2 W4 I D2",
            "K0 X D30",
            "K1 N0 D30 C2",
            "K2 Y2 D15 C2",
            "K3 Y3 D10 C2",
            "T3 W4 I D2",
            "K0 Y5 D6 C2",
            "K1 Y6 D5 C2",
            "K2 X D2 Y7 D4 C2",
            "K3 X D30"),

        "mseed13": ("GEOSCOPE Multiplexed Format 16 bit gain ranged, 3 bit exponent", 1,
            "M0",
            "W2 D0-11 A-2048",
            "D12-14",
            "E2:0:-1"),

        "mseed14": ("GEOSCOPE Multiplexed Format 16 bit gain ranged, 4 bit exponent", 1,
            "M0",
            "W2 D0-11 A-2048",
            "D12-15",
            "E2:0:-1"),

        "mseed0": ("ASCII console log", 80, ""),

        "ASCII": ("ASCII console log", 80, ""),

        "unknown": ("unknown", 99, "") }

    def __init__(self):
        self.__num = 0
        self.__used = {}
        self.__blk = []

    def lookup(self, name):
        if not name:
            name = "unknown"

        try:
            return self.__used[name]

        except KeyError:
            pass

        self.__num += 1
        k = self.__num
        self.__used[name] = k

        try:
            f = self.__formats[name]

        except KeyError:
            f = (name, 99, "")

        b = _Blockette30(name = f[0], key = k, family = f[1], ddl = f[2:])
        self.__blk.append(b)
        return k

    def output(self, f):
        for b in self.__blk:
            b.output(f)

class _UnitDict(object):
    __units = {
        "COUNTS": "Digital Counts",
        "COUNTS/V": "Counts per Volt",
        "M": "Displacement in Meters",
        "M/S": "Velocity in Meters per Second",
        "M/S**2": "Acceleration in Meters per Second per Second",
        "RAD/S": "Angular Velocity in Radians per Second",
        "V": "Volts",
        "A": "Amperes",
        "PA": "Pascal",
        "C": "Degree Celsius",
        "DEG": "Degree" }

    def __init__(self):
        self.__num = 0
        self.__used = {}
        self.__blk = []

    def lookup(self, name, remark=None):
        if not name:
            name = "unknown"

        try:
            return self.__used[(name, remark)]

        except KeyError:
            pass

        self.__num += 1
        k = self.__num
        self.__used[(name, remark)] = k

        try:
            desc = json.loads(remark)['unit']

        except Exception:
            try:
                desc = self.__units[name]

            except KeyError:
                desc = name

        b = _Blockette34(key = k, name = name, desc = desc)
        self.__blk.append(b)
        return k

    def output(self, f):
        for b in self.__blk:
            b.output(f)

class _CommentDict(object):
    def __init__(self):
        self.__num = 0
        self.__used = {}
        self.__blk = []

    def lookup(self, cclass, comment):
        k = self.__used.get((cclass, comment))
        if k is not None:
            return k

        self.__num += 1
        k = self.__num
        self.__used[(cclass, comment)] = k

        b = _Blockette31(key = k, cclass = cclass, comment = comment)
        self.__blk.append(b)
        return k

    def output(self, f):
        for b in self.__blk:
            b.output(f)

class _GenericAbbreviationDict(object):
    def __init__(self, inventory):
        self.__inventory = inventory
        self.__num = 0
        self.__used_sensor = {}
        self.__used_network = {}
        self.__blk = []   # blk33

    def lookup_sensor(self, name):     # instrument id for blk52
        sensor = self.__inventory.object.get(name)
        if sensor is None:
            raise SEEDError("unknown sensor: " + name)

        desc = sensor.description
        if not desc:
            desc = "unknown"

        k = self.__used_sensor.get(desc)
        if k is not None:
            return k

        self.__num += 1
        k = self.__num
        self.__used_sensor[desc] = k

        self.__blk.append(_Blockette33(k, desc))
        return k

    def lookup_network(self, code, start):    # network id for blk50
        k = self.__used_network.get((code, start))
        if k is not None:
            return k

        self.__num += 1
        k = self.__num
        self.__used_network[(code, start)] = k

        net_tp = self.__inventory.network.get(code)
        if net_tp is None:
            raise SEEDError("unknown network: %s" % (code,))

        netcfg = net_tp.get(start)
        if net_tp is None:
            raise SEEDError("unknown network: %s.%s" % \
                (code, start.isoformat()))

        self.__blk.append(_Blockette33(k, netcfg.description))
        return k

    def output(self, f):
        for b in self.__blk:
            b.output(f)

class _ResponseContainer(object):
    def __init__(self, fac):
        self.__fac = fac

    def add_sensor(self, name, dev_id, compn):
        (x1, x2, sens, sens_freq) = self.__fac._lookup_sensor(name,
            dev_id, compn)

        if x2 is not None:
            self._add_stage(x1, x2)

        else:
            self._add_stage(x1)

        return (sens, sens_freq)

    def add_analogue_paz(self, name):
        (x1, x2, gain) = self.__fac._lookup_analogue_paz(name)

        self._add_stage(x1, x2)
        return gain

    def add_analogue_iir(self, name):
        (x1, x2, gain) = self.__fac._lookup_analogue_iir(name)

        self._add_stage(x1, x2)
        return gain

    def add_analogue_fap(self, name):
        (x1, x2, gain) = self.__fac._lookup_analogue_fap(name)

        self._add_stage(x1, x2)
        return gain

    def add_digitizer(self, name, dev_id, compn, sample_rate, sample_rate_div):
        (x1, x2, x3, rate, gain) = self.__fac._lookup_digitizer(name,
            dev_id, compn, sample_rate, sample_rate_div)

        self._add_stage(x1, x2, x3)
        return (rate, gain)

    def add_digital_paz(self, name, input_rate):
        (x1, x2, x3, rate, gain) = self.__fac._lookup_digital_paz(name, input_rate)

        self._add_stage(x1, x2, x3)
        return (rate, gain)

    def add_digital_iir(self, name, input_rate):
        (x1, x2, x3, rate, gain) = self.__fac._lookup_digital_iir(name, input_rate)

        self._add_stage(x1, x2, x3)
        return (rate, gain)

    def add_fir(self, name, input_rate):
        (x1, x2, x3, rate, gain) = self.__fac._lookup_fir(name, input_rate)

        self._add_stage(x1, x2, x3)
        return (rate, gain)

class _Response4xContainer(_ResponseContainer):
    def __init__(self, fac):
        _ResponseContainer.__init__(self, fac)
        self.__blk = _Blockette60(1)

    def _add_stage(self, *blkref):
        self.__blk.add_stage(*blkref)

    def output(self, f):
        self.__blk.output(f)

class _Response5xContainer(_ResponseContainer):
    def __init__(self, fac):
        _ResponseContainer.__init__(self, fac)
        self.__stage = 1
        self.__blk = []

    def _add_stage(self, *blk):
        for b in blk:
            b.set_stage(self.__stage)
            self.__blk.append(b)

        self.__stage += 1

    def output(self, f):
        for b in self.__blk:
            b.output(f)

class _Response4xFactory(object):
    def __init__(self, inventory, unit_dict):
        self.__inventory = inventory
        self.__unit_dict = unit_dict
        self.__num = 0
        self.__used_sensor = {}
        self.__used_sensor_calib = {}
        self.__used_digitizer = {}
        self.__used_digitizer_calib = {}
        self.__used_analogue_paz = {}
        self.__used_analogue_iir = {}
        self.__used_analogue_fap = {}
        self.__used_digital_paz = {}
        self.__used_digital_iir = {}
        self.__used_fir = {}
        self.__used_fir_deci = {}
        self.__blk41 = []
        self.__blk42 = []
        self.__blk43 = []
        self.__blk44 = []
        self.__blk45 = []
        self.__blk47 = []
        self.__blk48 = []

    def new_response(self):
        return _Response4xContainer(self)

    def _lookup_sensor(self, name, dev_id, compn):
        sensor = self.__inventory.object.get(name)
        if sensor is None:
            raise SEEDError("unknown sensor: " + name)

        resp = self.__inventory.object.get(sensor.response)
        if resp is None:
            raise SEEDError("cannot find response for sensor " + sensor.name)

        k1 = self.__used_sensor.get(name)
        if k1 is None:
            unit = None
            try:
                unit = sensor.unit

            except AttributeError:
                pass

            if unit:
                input_units = self.__unit_dict.lookup(unit, sensor.remark)

            elif _is_paz_response(resp) and resp.numberOfZeros == 0:
                input_units = self.__unit_dict.lookup("M/S**2")

            else:
                input_units = self.__unit_dict.lookup("M/S")

            k1 = self.__num + 1

            if _is_paz_response(resp):
                if resp.type != "A" and resp.type != "B":
                    raise SEEDError("invalid PAZ response type of " + resp.name)

                b1 = _Blockette43(key = k1,
                    name = "RS" + name,
                    type = resp.type,
                    input_units = input_units,
                    output_units = self.__unit_dict.lookup("V"),
                    norm_fac = resp.normalizationFactor,
                    norm_freq = resp.normalizationFrequency,
                    nzeros = resp.numberOfZeros,
                    zeros = resp.zeros,
                    npoles = resp.numberOfPoles,
                    poles = resp.poles)

                self.__blk43.append(b1)

            elif _is_iir_response(resp):
                if resp.type != "A" and resp.type != "B":
                    raise SEEDError("invalid IIR response type of " + resp.name)

                b1 = _Blockette44(key = k1,
                    name = "RS" + name,
                    type = resp.type,
                    input_units = input_units,
                    output_units = self.__unit_dict.lookup("V"),
                    n_numerators = resp.numberOfNumerators,
                    numerators = resp.numerators,
                    n_denominators = resp.numberOfDenominators,
                    denominators = resp.denominators)

                self.__blk44.append(b1)

            elif _is_poly_response(resp):
                b1 = _Blockette42(key = k1,
                    name = "RS" + name,
                    input_units = input_units,
                    output_units = self.__unit_dict.lookup("V"),
                    freq_unit = resp.frequencyUnit,
                    low_freq = sensor.lowFrequency,
                    high_freq = sensor.highFrequency,
                    approx_type = resp.approximationType,
                    approx_lower_bound = resp.approximationLowerBound,
                    approx_upper_bound = resp.approximationUpperBound,
                    approx_error = resp.approximationError,
                    ncoeff = resp.numberOfCoefficients,
                    coeff = resp.coefficients)

                self.__blk42.append(b1)

            elif _is_fap_response(resp):
                b1 = _Blockette45(key = k1,
                    name = "RS" + name,
                    input_units = input_units,
                    output_units = self.__unit_dict.lookup("V"),
                    ntuples = resp.numberOfTuples,
                    tuples = resp.tuples)

                self.__blk45.append(b1)

            else:
                raise SEEDError("unknown response type of sensor " + sensor.name)

            self.__num += 1
            self.__used_sensor[name] = k1

        try:
            calib = sensor.calibration[dev_id][compn]

        except KeyError:
            calib = None

        if calib is not None and len(calib) > 0:
            calib_list = list(calib.items())
            calib_list.sort()
            resp_name = "GS" + sensor.name + "_" + dev_id
            gain = calib_list[-1][1].gain
        else:
            calib_list = []
            resp_name = "GS" + sensor.name
            gain = resp.gain
            dev_id = None
            compn = None

        if gain == 0.0 or gain is None or resp.gainFrequency is None:
            return (k1, None, 1.0, 0.0)

        k2 = self.__used_sensor_calib.get((name, dev_id, compn))
        if k2 is not None:
            return (k1, k2, gain, resp.gainFrequency)

        k2 = self.__num + 1

        b2 = _Blockette48(key = k2,
            name = resp_name,
            gain = gain,
            gain_freq = resp.gainFrequency) #,
            #calib_list = calib_list)

        self.__blk48.append(b2)
        self.__num += 1
        self.__used_sensor_calib[(name, dev_id, compn)] = k2
        return (k1, k2, gain, resp.gainFrequency)

    def _lookup_analogue_paz(self, name):
        resp_paz = self.__inventory.object.get(name)
        if resp_paz is None:
            raise SEEDError("unknown PAZ response: " + name)

        k = self.__used_analogue_paz.get(name)
        if k is not None:
            (k1, k2) = k
            return (k1, k2, resp_paz.gain)

        #if resp_paz.deci_fac is not None:
        #    raise SEEDError("expected analogue response, found digital")

        if resp_paz.type != "A" and resp_paz.type != "B":
            raise SEEDError("invalid PAZ response type of " + resp_paz.name)

        k1 = self.__num + 1
        k2 = self.__num + 2

        b1 = _Blockette43(key = k1,
            name = "RA" + name,
            type = resp_paz.type,
            input_units = self.__unit_dict.lookup("V"),
            output_units = self.__unit_dict.lookup("V"),
            norm_fac = resp_paz.normalizationFactor,
            norm_freq = resp_paz.normalizationFrequency,
            nzeros = resp_paz.numberOfZeros,
            zeros = resp_paz.zeros,
            npoles = resp_paz.numberOfPoles,
            poles = resp_paz.poles)

        b2 = _Blockette48(key = k2,
            name = "GA" + name,
            gain = resp_paz.gain,
            gain_freq = resp_paz.gainFrequency)

        self.__blk43.append(b1)
        self.__blk48.append(b2)
        self.__num += 2
        self.__used_analogue_paz[name] = (k1, k2)
        return (k1, k2, resp_paz.gain)

    def _lookup_analogue_iir(self, name):
        resp_iir = self.__inventory.object.get(name)
        if resp_iir is None:
            raise SEEDError("unknown IIR response: " + name)

        k = self.__used_analogue_iir.get(name)
        if k is not None:
            (k1, k2) = k
            return (k1, k2, resp_iir.gain)

        #if resp_iir.deci_fac is not None:
        #    raise SEEDError("expected analogue response, found digital")

        if resp_iir.type != "A" and resp_iir.type != "B":
            raise SEEDError("invalid IIR response type of " + resp_iir.name)

        k1 = self.__num + 1
        k2 = self.__num + 2

        b1 = _Blockette44(key = k1,
            name = "RA" + name,
            type = resp_iir.type,
            input_units = self.__unit_dict.lookup("V"),
            output_units = self.__unit_dict.lookup("V"),
            n_numerators = resp_iir.numberOfNumerators,
            numerators = resp_iir.numerators,
            n_denominators = resp_iir.numberOfDenominators,
            denominators = resp_iir.denominators)

        b2 = _Blockette48(key = k2,
            name = "GA" + name,
            gain = resp_iir.gain,
            gain_freq = resp_iir.gainFrequency)

        self.__blk44.append(b1)
        self.__blk48.append(b2)
        self.__num += 2
        self.__used_analogue_iir[name] = (k1, k2)
        return (k1, k2, resp_iir.gain)

    def _lookup_analogue_fap(self, name):
        resp_fap = self.__inventory.object.get(name)
        if resp_fap is None:
            raise SEEDError("unknown FAP response: " + name)

        k = self.__used_analogue_fap.get(name)
        if k is not None:
            (k1, k2) = k
            return (k1, k2, resp_fap.gain)

        k1 = self.__num + 1
        k2 = self.__num + 2

        b1 = _Blockette45(key = k1,
            name = "RA" + name,
            input_units = self.__unit_dict.lookup("V"),
            output_units = self.__unit_dict.lookup("V"),
            ntuples = resp_fap.numberOfTuples,
            tuples = resp_fap.tuples)

        b2 = _Blockette48(key = k2,
            name = "GA" + name,
            gain = resp_fap.gain,
            gain_freq = resp_fap.gainFrequency)

        self.__blk45.append(b1)
        self.__blk48.append(b2)
        self.__num += 2
        self.__used_analogue_fap[name] = (k1, k2)
        return (k1, k2, resp_fap.gain)

    def _lookup_digitizer(self, name, dev_id, compn, sample_rate, sample_rate_div):
        digi = self.__inventory.object.get(name)
        if digi is None:
            raise SEEDError("unknown datalogger: " + name)

        input_rate = float(sample_rate) / float(sample_rate_div)

        try:
            stream_deci = digi.decimation[sample_rate][sample_rate_div]
            if stream_deci.digitalFilterChain and \
                len(stream_deci.digitalFilterChain) > 0:
                for f in stream_deci.digitalFilterChain.split():
                    obj = self.__inventory.object[f]
                    input_rate *= (obj.decimationFactor or 1)

        except KeyError:
            pass

        k = self.__used_digitizer.get((name, input_rate))
        if k is None:
            k1 = self.__num + 1
            k2 = self.__num + 2

            b1 = _Blockette44(key = k1,
                name = "RL" + name,
                type = "D",
                input_units = self.__unit_dict.lookup("V"),
                output_units = self.__unit_dict.lookup("COUNTS"))

            b2 = _Blockette47(key = k2,
                name = "DL" + name,
                input_rate = input_rate,
                deci_fac = 1,
                deci_offset = 0,
                delay = 0,
                correction = 0)

            self.__blk44.append(b1)
            self.__blk47.append(b2)
            self.__num += 2
            self.__used_digitizer[(name, input_rate)] = (k1, k2)
        else:
            (k1, k2) = k

        try:
            calib = digi.calibration[dev_id][compn]

        except KeyError:
            calib = None

        if calib is not None and len(calib) > 0:
            calib_list = list(calib.items())
            calib_list.sort()
            resp_name = "GL" + digi.name + "_" + dev_id
            gain = calib_list[-1][1].gain
        else:
            calib_list = []
            resp_name = "GL" + digi.name
            gain = digi.gain
            dev_id = None
            compn = None

        k3 = self.__used_digitizer_calib.get((name, dev_id, compn))
        if k3 is not None:
            return (k1, k2, k3, input_rate, gain)

        k3 = self.__num + 1

        b3 = _Blockette48(key = k3,
            name = resp_name,
            gain = gain,
            gain_freq = 0) #,
            #calib_list = calib_list)

        self.__blk48.append(b3)
        self.__num += 1
        self.__used_digitizer_calib[(name, dev_id, compn)] = k3
        return (k1, k2, k3, input_rate, gain)

    def _lookup_digital_paz(self, name, input_rate):
        resp_paz = self.__inventory.object.get(name)
        if resp_paz is None:
            raise SEEDError("unknown PAZ response: " + name)

        deci_fac = resp_paz.decimationFactor or 1
        delay = (resp_paz.delay or 0.0) / input_rate
        correction = (resp_paz.correction or 0.0) / input_rate
        gain = resp_paz.gain or 1.0
        gain_freq = resp_paz.gainFrequency or 0.0

        k = self.__used_digital_paz.get((name, input_rate))
        if k is not None:
            (k1, k2, k3) = k
            return (k1, k2, k3, input_rate / deci_fac, resp_paz.gain)

        #if resp_paz.deci_fac is None:
        #    raise SEEDError("expected digital response, found analogue")

        if resp_paz.type != "D":
            raise SEEDError("invalid PAZ response type of " + resp_paz.name)

        k1 = self.__num + 1
        k2 = self.__num + 2
        k3 = self.__num + 3

        b1 = _Blockette43(key = k1,
            name = "RD" + name,
            type = "D",
            input_units = self.__unit_dict.lookup("COUNTS"),
            output_units = self.__unit_dict.lookup("COUNTS"),
            norm_fac = resp_paz.normalizationFactor,
            norm_freq = resp_paz.normalizationFrequency,
            nzeros = resp_paz.numberOfZeros,
            zeros = resp_paz.zeros,
            npoles = resp_paz.numberOfPoles,
            poles = resp_paz.poles)

        b2 = _Blockette47(key = k2,
            name = "DD" + name,
            input_rate = input_rate,
            deci_fac = deci_fac,
            deci_offset = 0,
            delay = delay,
            correction = correction)

        b3 = _Blockette48(key = k3,
            name = "GD" + name,
            gain = gain,
            gain_freq = gain_freq)

        self.__blk43.append(b1)
        self.__blk47.append(b2)
        self.__blk48.append(b3)
        self.__num += 3
        self.__used_digital_paz[(name, input_rate)] = (k1, k2, k3)
        return (k1, k2, k3, input_rate / deci_fac, resp_paz.gain)

    def _lookup_digital_iir(self, name, input_rate):
        resp_iir = self.__inventory.object.get(name)
        if resp_iir is None:
            raise SEEDError("unknown IIR response: " + name)

        deci_fac = resp_iir.decimationFactor or 1
        delay = (resp_iir.delay or 0.0) / input_rate
        correction = (resp_iir.correction or 0.0) / input_rate
        gain = resp_iir.gain or 1.0
        gain_freq = resp_iir.gainFrequency or 0.0

        k = self.__used_digital_iir.get((name, input_rate))
        if k is not None:
            (k1, k2, k3) = k
            return (k1, k2, k3, input_rate / deci_fac, resp_iir.gain)

        #if resp_iir.deci_fac is None:
        #    raise SEEDError("expected digital response, found analogue")

        if resp_iir.type != "D":
            raise SEEDError("invalid IIR response type of " + resp_iir.name)

        k1 = self.__num + 1
        k2 = self.__num + 2
        k3 = self.__num + 3

        b1 = _Blockette44(key = k1,
            name = "RD" + name,
            type = "D",
            input_units = self.__unit_dict.lookup("COUNTS"),
            output_units = self.__unit_dict.lookup("COUNTS"),
            n_numerators = resp_iir.numberOfNumerators,
            numerators = resp_iir.numerators,
            n_denominators = resp_iir.numberOfDenominators,
            denominators = resp_iir.denominators)

        b2 = _Blockette47(key = k2,
            name = "DD" + name,
            input_rate = input_rate,
            deci_fac = deci_fac,
            deci_offset = 0,
            delay = delay,
            correction = correction)

        b3 = _Blockette48(key = k3,
            name = "GD" + name,
            gain = gain,
            gain_freq = gain_freq)

        self.__blk44.append(b1)
        self.__blk47.append(b2)
        self.__blk48.append(b3)
        self.__num += 3
        self.__used_digital_iir[(name, input_rate)] = (k1, k2, k3)
        return (k1, k2, k3, input_rate / deci_fac, resp_iir.gain)

    def _lookup_fir(self, name, input_rate):
        resp_fir = self.__inventory.object.get(name)
        if resp_fir is None:
            raise SEEDError("unknown FIR response: " + name)

        deci_fac = resp_fir.decimationFactor or 1
        delay = (resp_fir.delay or 0.0) / input_rate
        correction = (resp_fir.correction or 0.0) / input_rate
        gain = resp_fir.gain or 1.0
        gain_freq = resp_fir.gainFrequency or 0.0

        k = self.__used_fir.get(name)
        if k is None:
            k1 = self.__num + 1
            k3 = self.__num + 2

            b1 = _Blockette41(key = k1,
                name = "RF" + name,
                symmetry = resp_fir.symmetry,
                input_units = self.__unit_dict.lookup("COUNTS"),
                output_units = self.__unit_dict.lookup("COUNTS"),
                ncoeff = resp_fir.numberOfCoefficients,
                coeff = resp_fir.coefficients)

            b3 = _Blockette48(key = k3,
                name = "GF" + name,
                gain = gain,
                gain_freq = gain_freq)

            self.__blk41.append(b1)
            self.__blk48.append(b3)
            self.__num += 2
            self.__used_fir[name] = (k1, k3)
        else:
            (k1, k3) = k

        k2 = self.__used_fir_deci.get((name, input_rate))
        if k2 is None:
            k2 = self.__num + 1
            b2 = _Blockette47(key = k2,
                name = "DF" + name + "_" + str(input_rate).replace(".", "_"),
                input_rate = input_rate,
                deci_fac = deci_fac,
                deci_offset = 0,
                delay = delay,
                correction = correction)

            self.__blk47.append(b2)
            self.__num += 1
            self.__used_fir_deci[(name, input_rate)] = k2

        return (k1, k2, k3, input_rate / deci_fac, resp_fir.gain)

    def output(self, f):
        for b in self.__blk41:
            b.output(f)

        for b in self.__blk42:
            b.output(f)

        for b in self.__blk43:
            b.output(f)

        for b in self.__blk44:
            b.output(f)

        for b in self.__blk45:
            b.output(f)

        for b in self.__blk47:
            b.output(f)

        for b in self.__blk48:
            b.output(f)

class _Response5xFactory(object):
    def __init__(self, inventory, unit_dict):
        self.__inventory = inventory
        self.__unit_dict = unit_dict

    def new_response(self):
        return _Response5xContainer(self)

    def _lookup_sensor(self, name, dev_id, compn):
        sensor = self.__inventory.object.get(name)
        if sensor is None:
            raise SEEDError("unknown sensor: " + name)

        resp = self.__inventory.object.get(sensor.response)
        if resp is None:
            raise SEEDError("cannot find response for sensor " + sensor.name)

        unit = None
        try:
            unit = sensor.unit

        except AttributeError:
            pass

        if unit:
            input_units = self.__unit_dict.lookup(unit, sensor.remark)

        elif _is_paz_response(resp) and resp.numberOfZeros == 0:
            input_units = self.__unit_dict.lookup("M/S**2")

        else:
            input_units = self.__unit_dict.lookup("M/S")

        if _is_paz_response(resp):
            if resp.type != "A" and resp.type != "B":
                raise SEEDError("invalid PAZ response type of " + resp.name)

            b1 = _Blockette53(type = resp.type,
                input_units = input_units,
                output_units = self.__unit_dict.lookup("V"),
                norm_fac = resp.normalizationFactor,
                norm_freq = resp.normalizationFrequency,
                nzeros = resp.numberOfZeros,
                zeros = resp.zeros,
                npoles = resp.numberOfPoles,
                poles = resp.poles)

        elif _is_iir_response(resp):
            if resp.type != "A" and resp.type != "B":
                raise SEEDError("invalid IIR response type of " + resp.name)

            b1 = _Blockette54(type = resp.type,
                input_units = input_units,
                output_units = self.__unit_dict.lookup("V"),
                n_numerators = resp.numberOfNumerators,
                numerators = resp.numerators,
                n_denominators = resp.numberOfDenominators,
                denominators = resp.denominators)

            self.__blk54.append(b1)

        elif _is_poly_response(resp):
            b1 = _Blockette62(input_units = input_units,
                output_units = self.__unit_dict.lookup("V"),
                freq_unit = resp.frequencyUnit,
                low_freq = sensor.lowFrequency,
                high_freq = sensor.highFrequency,
                approx_type = resp.approximationType,
                approx_lower_bound = resp.approximationLowerBound,
                approx_upper_bound = resp.approximationUpperBound,
                approx_error = resp.approximationError,
                ncoeff = resp.numberOfCoefficients,
                coeff = resp.coefficients)

        elif _is_fap_response(resp):
            b1 = _Blockette55(input_units = input_units,
                output_units = self.__unit_dict.lookup("V"),
                ntuples = resp.numberOfTuples,
                tuples = resp.tuples)

        else:
            raise SEEDError("unknown response type of sensor " + sensor.name)

        try:
            calib = sensor.calibration[dev_id][compn]

        except KeyError:
            calib = None

        if calib is not None and len(calib) > 0:
            calib_list = list(calib.items())
            calib_list.sort()
            gain = calib_list[-1][1].gain
        else:
            calib_list = []
            gain = resp.gain
            dev_id = None
            compn = None

        if gain == 0.0 or gain is None or resp.gainFrequency is None:
            return (b1, None, 1.0, 0.0)

        b2 = _Blockette58(gain = gain,
            gain_freq = resp.gainFrequency) #,
            #calib_list = calib_list)

        return (b1, b2, gain, resp.gainFrequency)

    def _lookup_analogue_paz(self, name):
        resp_paz = self.__inventory.object.get(name)
        if resp_paz is None:
            raise SEEDError("unknown PAZ response: " + name)

        if resp_paz.type != "A" and resp_paz.type != "B":
            raise SEEDError("invalid PAZ response type of " + resp_paz.name)

        b1 = _Blockette53(type = resp_paz.type,
            input_units = self.__unit_dict.lookup("V"),
            output_units = self.__unit_dict.lookup("V"),
            norm_fac = resp_paz.normalizationFactor,
            norm_freq = resp_paz.normalizationFrequency,
            nzeros = resp_paz.numberOfZeros,
            zeros = resp_paz.zeros,
            npoles = resp_paz.numberOfPoles,
            poles = resp_paz.poles)

        b2 = _Blockette58(gain = resp_paz.gain,
            gain_freq = resp_paz.gainFrequency)

        return (b1, b2, resp_paz.gain)

    def _lookup_analogue_iir(self, name):
        resp_iir = self.__inventory.object.get(name)
        if resp_iir is None:
            raise SEEDError("unknown IIR response: " + name)

        if resp_iir.type != "A" and resp_iir.type != "B":
            raise SEEDError("invalid IIR response type of " + resp_iir.name)

        b1 = _Blockette54(type = resp_iir.type,
            input_units = self.__unit_dict.lookup("V"),
            output_units = self.__unit_dict.lookup("V"),
            n_numerators = resp_iir.numberOfNumerators,
            numerators = resp_iir.numerators,
            n_denominators = resp_iir.numberOfDenominators,
            denominators = resp_iir.denominators)

        b2 = _Blockette58(gain = resp_iir.gain,
            gain_freq = resp_iir.gainFrequency)

        return (b1, b2, resp_iir.gain)

    def _lookup_analogue_fap(self, name):
        resp_fap = self.__inventory.object.get(name)
        if resp_fap is None:
            raise SEEDError("unknown FAP response: " + name)

        gain = resp_fap.gain
        if gain is None: gain = 1.0

        b1 = _Blockette55(input_units = self.__unit_dict.lookup("V"),
            output_units = self.__unit_dict.lookup("V"),
            ntuples = resp_fap.numberOfTuples, tuples = resp_fap.tuples)

        b2 = _Blockette58(gain = gain,
            gain_freq = resp_fap.gainFrequency)

        return (b1, b2, gain)

    def _lookup_digitizer(self, name, dev_id, compn, sample_rate, sample_rate_div):
        digi = self.__inventory.object.get(name)
        if digi is None:
            raise SEEDError("unknown datalogger: " + name)

        input_rate = float(sample_rate) / float(sample_rate_div)

        try:
            stream_deci = digi.decimation[sample_rate][sample_rate_div]
            if stream_deci.digitalFilterChain and \
                len(stream_deci.digitalFilterChain) > 0:
                for f in stream_deci.digitalFilterChain.split():
                    obj = self.__inventory.object[f]
                    input_rate *= (obj.decimationFactor or 1)

        except KeyError:
            pass

        b1 = _Blockette54(type = "D",
            input_units = self.__unit_dict.lookup("V"),
            output_units = self.__unit_dict.lookup("COUNTS"))

        b2 = _Blockette57(input_rate = input_rate,
            deci_fac = 1,
            deci_offset = 0,
            delay = 0,
            correction = 0)

        try:
            calib = digi.calibration[dev_id][compn]

        except KeyError:
            calib = None

        if calib is not None and len(calib) > 0:
            calib_list = list(calib.items())
            calib_list.sort()
            gain = calib_list[-1][1].gain
        else:
            calib_list = []
            gain = digi.gain
            dev_id = None
            compn = None

        b3 = _Blockette58(gain = gain,
            gain_freq = 0) #,
            #calib_list = calib_list)

        return (b1, b2, b3, input_rate, gain)

    def _lookup_digital_paz(self, name, input_rate):
        resp_paz = self.__inventory.object.get(name)
        if resp_paz is None:
            raise SEEDError("unknown PAZ response: " + name)

        if resp_paz.type != "D":
            raise SEEDError("invalid PAZ response type of " + resp_paz.name)

        deci_fac = resp_paz.decimationFactor or 1
        delay = (resp_paz.delay or 0.0) / input_rate
        correction = (resp_paz.correction or 0.0) / input_rate
        gain = resp_paz.gain or 1.0
        gain_freq = resp_paz.gainFrequency or 0.0

        b1 = _Blockette53(type = "D",
            input_units = self.__unit_dict.lookup("COUNTS"),
            output_units = self.__unit_dict.lookup("COUNTS"),
            norm_fac = resp_paz.normalizationFactor,
            norm_freq = resp_paz.normalizationFrequency,
            nzeros = resp_paz.numberOfZeros,
            zeros = resp_paz.zeros,
            npoles = resp_paz.numberOfPoles,
            poles = resp_paz.poles)

        b2 = _Blockette57(input_rate = input_rate,
            deci_fac = deci_fac,
            deci_offset = 0,
            delay = delay,
            correction = correction)

        b3 = _Blockette58(gain = gain,
            gain_freq = gain_freq)

        return (b1, b2, b3, input_rate / deci_fac, resp_paz.gain)

    def _lookup_digital_iir(self, name, input_rate):
        resp_iir = self.__inventory.object.get(name)
        if resp_iir is None:
            raise SEEDError("unknown IIR response: " + name)

        if resp_iir.type != "D":
            raise SEEDError("invalid IIR response type of " + resp_iir.name)

        deci_fac = resp_iir.decimationFactor or 1
        delay = (resp_iir.delay or 0.0) / input_rate
        correction = (resp_iir.correction or 0.0) / input_rate
        gain = resp_iir.gain or 1.0
        gain_freq = resp_iir.gainFrequency or 0.0

        b1 = _Blockette54(type = "D",
            input_units = self.__unit_dict.lookup("COUNTS"),
            output_units = self.__unit_dict.lookup("COUNTS"),
            n_numerators = resp_iir.numberOfNumerators,
            numerators = resp_iir.numerators,
            n_denominators = resp_iir.numberOfDenominators,
            denominators = resp_iir.denominators)

        b2 = _Blockette57(input_rate = input_rate,
            deci_fac = deci_fac,
            deci_offset = 0,
            delay = delay,
            correction = correction)

        b3 = _Blockette58(gain = gain,
            gain_freq = gain_freq)

        return (b1, b2, b3, input_rate / deci_fac, resp_iir.gain)

    def _lookup_fir(self, name, input_rate):
        resp_fir = self.__inventory.object.get(name)
        if resp_fir is None:
            raise SEEDError("unknown FIR response: " + name)

        deci_fac = resp_fir.decimationFactor or 1
        delay = (resp_fir.delay or 0.0) / input_rate
        correction = (resp_fir.correction or 0.0) / input_rate
        gain = resp_fir.gain or 1.0
        gain_freq = resp_fir.gainFrequency or 0.0

        b1 = _Blockette61(name = "RF" + name,
            symmetry = resp_fir.symmetry,
            input_units = self.__unit_dict.lookup("COUNTS"),
            output_units = self.__unit_dict.lookup("COUNTS"),
            ncoeff = resp_fir.numberOfCoefficients,
            coeff = resp_fir.coefficients)

        b2 = _Blockette57(input_rate = input_rate,
            deci_fac = deci_fac,
            deci_offset = 0,
            delay = delay,
            correction = correction)

        b3 = _Blockette58(gain = gain,
            gain_freq = gain_freq)

        return (b1, b2, b3, input_rate / deci_fac, resp_fir.gain)

    def output(self, f):
        pass

class _Channel(object):
    def __init__(self, inventory, strmcfg, format_dict, unit_dict,
        comment_dict, gen_dict, resp_container):

        loccfg = strmcfg.mySensorLocation
        statcfg = loccfg.myStation
        netcfg = statcfg.myNetwork

        self.__id = (loccfg.code, strmcfg.code, strmcfg.start)
        self.__resp_container = resp_container
        self.__comment_dict = comment_dict
        self.__comment_blk = []

        sensor = inventory.object.get(strmcfg.sensor)
        if sensor is None:
            raise SEEDError("unknown sensor: " + strmcfg.sensor)

        resp = inventory.object.get(sensor.response)
        if resp is None:
            raise SEEDError("cannot find response for sensor " + sensor.name)

        digi = inventory.object.get(strmcfg.datalogger)
        #if digi is None:
        #    raise SEEDError("unknown datalogger referenced in channel %s: %s" % (strmcfg.code, strmcfg.datalogger))

        stream_deci = None
        if digi:
            try:
                stream_deci = digi.decimation[strmcfg.sampleRateNumerator][strmcfg.sampleRateDenominator]

            except KeyError:
                raise SEEDError("cannot find filter chain for stream " + \
                    str(strmcfg.sampleRateNumerator) + "/" + \
                    str(strmcfg.sampleRateDenominator) + " of datalogger " + \
                    digi.name)

        unit = None
        try:
            unit = sensor.unit

        except AttributeError:
            pass

        if unit:
            signal_units = unit_dict.lookup(unit, sensor.remark)

        elif _is_paz_response(resp) and resp.numberOfZeros == 0:
            signal_units = unit_dict.lookup("M/S**2")

        else:
            signal_units = unit_dict.lookup("M/S")

        if strmcfg.sampleRateNumerator == 0 or \
            strmcfg.sampleRateDenominator == 0:
            raise SEEDError("invalid sample rate %d/%d" % \
                (strmcfg.sampleRateNumerator, strmcfg.sampleRateDenominator))

        sample_rate = float(strmcfg.sampleRateNumerator) / \
            float(strmcfg.sampleRateDenominator)

        clock_drift = 0
        if digi:
            if digi.maxClockDrift is not None:
                clock_drift = digi.maxClockDrift / sample_rate

        self.__chan_blk = _Blockette52(loc_id = loccfg.code,
            chan_id = strmcfg.code,
            instr_id = gen_dict.lookup_sensor(strmcfg.sensor),
            comment = "",
            signal_units = signal_units,
            calibration_units = 0,
            latitude = loccfg.latitude,
            longitude = loccfg.longitude,
            elevation = loccfg.elevation,
            local_depth = strmcfg.depth,
            azimuth = strmcfg.azimuth,
            dip = strmcfg.dip,
            data_format = format_dict.lookup(strmcfg.format),
            record_length = 12,
            sample_rate = sample_rate,
            clock_drift = clock_drift,
            flags = strmcfg.flags,
            start_date = strmcfg.start,
            end_date = strmcfg.end)

        (sens, sens_freq) = resp_container.add_sensor(strmcfg.sensor,
            strmcfg.sensorSerialNumber, strmcfg.sensorChannel)

        if stream_deci:
            if stream_deci.analogueFilterChain:
                if len(stream_deci.analogueFilterChain) > 0:
                    for f in stream_deci.analogueFilterChain.split():
                        obj = inventory.object[f]
                        if _is_paz_response(obj):
                            gain = resp_container.add_analogue_paz(f)
                            sens *= gain
                        elif _is_iir_response(obj):
                            gain = resp_container.add_analogue_iir(f)
                            sens *= gain
                        elif _is_fap_response(obj):
                            gain = resp_container.add_analogue_fap(f)
                            sens *= gain
                        else:
                            raise SEEDError("invalid filter type: %s (%s)" % (f, obj.name))

            (rate, gain) = resp_container.add_digitizer(strmcfg.datalogger,
                strmcfg.dataloggerSerialNumber, strmcfg.dataloggerChannel,
                strmcfg.sampleRateNumerator, strmcfg.sampleRateDenominator)

            sens *= gain

            if stream_deci.digitalFilterChain:
                if len(stream_deci.digitalFilterChain) > 0:
                    for f in stream_deci.digitalFilterChain.split():
                        obj = inventory.object[f]
                        if _is_paz_response(obj):
                            (rate, gain) = resp_container.add_digital_paz(f, rate)
                        elif _is_iir_response(obj):
                            (rate, gain) = resp_container.add_digital_iir(f, rate)
                        elif _is_fir_response(obj):
                            (rate, gain) = resp_container.add_fir(f, rate)
                        else:
                            raise SEEDError("invalid filter type: %s (%s)" % (f, obj.name))

                        sens *= gain

            if sens_freq > rate / 5:
                sens_freq = rate / 5

        #if sample_rate != rate:
        #    print digi.name, netcfg.code, statcfg.code, strmcfg.code, "expected sample rate", sample_rate, "actual", rate

        if strmcfg.gain is None:
            strmcfg.gain = sens

        if strmcfg.gainFrequency is None:
            strmcfg.gainFrequency = sens_freq

        if _is_poly_response(resp):
            self.__stage0_blk = _Blockette62(input_units = signal_units,
                output_units = unit_dict.lookup("COUNTS"),
                freq_unit = resp.frequencyUnit,
                low_freq = sensor.lowFrequency,
                high_freq = sensor.highFrequency,
                approx_type = resp.approximationType,
                approx_lower_bound = resp.approximationLowerBound,
                approx_upper_bound = resp.approximationUpperBound,
                approx_error = resp.approximationError,
                ncoeff = resp.numberOfCoefficients,
                coeff = resp.coefficients,
                gain = sens)

        else:
            self.__stage0_blk = _Blockette58(gain = strmcfg.gain,
                gain_freq = strmcfg.gainFrequency)

    def add_comment(self, com):
        self.__comment_blk.append(_Blockette59(start_time = com.start,
            end_time = com.end,
            comment_key = self.__comment_dict.lookup('C', com.text)))

    def output(self, f, vol_start, vol_end):
        self.__chan_blk.set_vol_span(vol_start, vol_end)
        self.__chan_blk.output(f)
        self.__resp_container.output(f)
        self.__stage0_blk.output(f)

        for b in self.__comment_blk:
            b.output(f)

class _Station(object):
    def __init__(self, inventory, statcfg, format_dict, unit_dict,
        comment_dict, gen_dict, resp_fac):
        self.__inventory = inventory
        self.__statcfg = statcfg
        self.__format_dict = format_dict
        self.__unit_dict = unit_dict
        self.__comment_dict = comment_dict
        self.__gen_dict = gen_dict
        self.__resp_fac = resp_fac
        self.__recno = 0
        self.__id = (statcfg.myNetwork.code, statcfg.myNetwork.start,
          statcfg.code, statcfg.start)
        self.__channel = {}
        self.__comment_blk = []

        site_name = statcfg.description
        if not site_name:
            if statcfg.place and statcfg.country:
                site_name = statcfg.place + ", " + statcfg.country
            else:
                site_name = statcfg.code

        self.__stat_blk = _Blockette50(stat_code = statcfg.code,
            latitude = statcfg.latitude,
            longitude = statcfg.longitude,
            elevation = statcfg.elevation,
            site_name = site_name,
            net_id = gen_dict.lookup_network(statcfg.myNetwork.code,
              statcfg.myNetwork.start),
            net_code = statcfg.myNetwork.code,
            start_date = statcfg.start,
            end_date = statcfg.end)

        for com in statcfg.comment.values():
            self.__comment_blk.append(_Blockette51(start_time = com.start,
                end_time = com.end,
                comment_key = self.__comment_dict.lookup('S', com.text)))

    def add_chan(self, strmcfg):
        loccfg = strmcfg.mySensorLocation

        if (loccfg.code, strmcfg.code, strmcfg.start) in self.__channel:
            return

        cha = _Channel(self.__inventory, strmcfg, self.__format_dict,
            self.__unit_dict, self.__comment_dict, self.__gen_dict,
            self.__resp_fac.new_response())

        self.__channel[(loccfg.code, strmcfg.code, strmcfg.start)] = cha

        for com in strmcfg.comment.values():
            cha.add_comment(com)

    def get_id(self):
        return self.__id

    def get_recno(self):
        return self.__recno

    def output(self, f, vol_start, vol_end):
        self.__recno = f.get_recno()
        self.__stat_blk.output(f)
        for b in self.__comment_blk:
            b.output(f)

        chan_list = [self.__channel[k] for k in sorted(self.__channel.keys())]

        for c in chan_list:
            c.output(f, vol_start, vol_end)

        f.flush()

class _TimeSeries(object):
    def __init__(self, span, net_code, stat_code, loc_id, chan_id,
        start_time, end_time, recno):
        self.__span = span
        self.__net_code = net_code
        self.__stat_code = stat_code
        self.__loc_id = loc_id
        self.__chan_id = chan_id
        self.__start_time = start_time
        self.__start_recno = recno
        self.__end_time = end_time
        self.__end_recno = recno

    def extend(self, start_time, end_time, recno):
        if start_time < self.__start_time:
            self.__start_time = start_time

        if end_time > self.__end_time:
            self.__end_time = end_time

        self.__end_recno = recno
        self.__span.extend(start_time, end_time)

    def get_series_data(self):
        return (self.__net_code, self.__stat_code, self.__loc_id,
          self.__chan_id, self.__start_time, self.__end_time)

    def output(self, f, data_start):
        b = _Blockette74(self.__net_code, self.__stat_code, self.__loc_id,
            self.__chan_id, self.__start_time, self.__start_recno + data_start,
            self.__end_time, self.__end_recno + data_start)

        b.output(f)

class _Timespan(object):
    def __init__(self):
        self.__start_time = None
        self.__end_time = None
        self.__recno = 0
        self.__series = []

    def new_time_series(self, net, sta, loc, cha, start_time, end_time, recno):
        if len(self.__series) == 0:
            self.__start_time = start_time
            self.__end_time = end_time
        else:
            self.extend(start_time, end_time)

        ts = _TimeSeries(self, net, sta, loc, cha, start_time, end_time, recno)
        self.__series.append(ts)
        return ts

    def overlap(self, start_time, end_time):
        return self.__start_time - _min_ts_gap <= start_time <= self.__end_time + _min_ts_gap or \
            self.__start_time - _min_ts_gap <= end_time <= self.__end_time + _min_ts_gap

    def extend(self, start_time, end_time):
        if start_time < self.__start_time:
            self.__start_time = start_time

        if end_time > self.__end_time:
            self.__end_time = end_time

    def get_span_data(self):
        return (self.__start_time, self.__end_time, self.__recno)

    def get_series_data(self):
        return [ s.get_series_data() for s in self.__series ]

    def output_index(self, f, data_start):
        self.__recno = f.get_recno()

        b = _Blockette70("P", self.__start_time, self.__end_time)
        b.output(f)

        for s in self.__series:
            s.output(f, data_start)

        f.flush()

class _WaveformData(object):
    def __init__(self):
        self.__fd = TemporaryFile()
        self.__recno = 0
        self.__cur_rec = None
        self.__cur_series = None
        self.__span = []

    def __get_time_series(self, rec):
        for s in self.__span:
            if s.overlap(rec.begin_time, rec.end_time):
                break
        else:
            s = _Timespan()
            self.__span.append(s)

        return s.new_time_series(rec.net, rec.sta, rec.loc, rec.cha,
            rec.begin_time, rec.end_time, self.__recno)

    def add_data(self, rec):
        if self.__cur_rec is None:
            self.__cur_rec = rec
            self.__cur_series = self.__get_time_series(rec)

            #if rec.encoding != 10 and rec.encoding != 11:
            #    logs.warning("%s %s %s %s cannot merge records with encoding %d" % \
            #        (rec.net, rec.sta, rec.loc, rec.cha, rec.encoding))

            return

        if self.__cur_rec.net == rec.net and self.__cur_rec.sta == rec.sta and \
            self.__cur_rec.loc == rec.loc and self.__cur_rec.cha == rec.cha:

            contiguous = True
            if rec.encoding == 10 or rec.encoding == 11:
                if abs(rec.begin_time - self.__cur_rec.end_time) > _min_data_gap(rec.fsamp):
                    contiguous = False

                if rec.X_minus1 is None:
                    logs.debug("%s %s %s %s X[-1] not defined" %
                        (rec.net, rec.sta, rec.loc, rec.cha))
                    contiguous = False
            else:
                contiguous = False

            if self.__cur_rec.fsamp != rec.fsamp:
                logs.debug("%s %s %s %s sample rate changed from %f to %f" %
                    (rec.net, rec.sta, rec.loc, rec.cha, self.__cur_rec.fsamp,
                    rec.fsamp))
                contiguous = False

            if self.__cur_rec.encoding != rec.encoding:
                logs.debug("%s %s %s %s encoding changed from %d to %d" %
                    (rec.net, rec.sta, rec.loc, rec.cha, self.__cur_rec.encoding,
                    rec.encoding))
                contiguous = False

            if contiguous and self.__cur_rec.Xn != rec.X_minus1:
                logs.debug("%s %s %s %s non-contiguous data: %d != %d" %
                    (rec.net, rec.sta, rec.loc, rec.cha, self.__cur_rec.Xn,
                    rec.X_minus1))
                contiguous = False

            if contiguous and self.__cur_rec.size + rec.nframes * 64 <= (1 << _RECLEN_EXP):
                self.__cur_rec.merge(rec)

            else:
                self.__recno += 1

                if abs(rec.begin_time - self.__cur_rec.end_time) <= _min_ts_gap:
                    self.__cur_series.extend(rec.begin_time, rec.end_time,
                        self.__recno)
                else:
                    self.__cur_series = self.__get_time_series(rec)

                self.__cur_rec.write(self.__fd, _RECLEN_EXP)
                self.__cur_rec = rec

        else:
            self.__recno += 1
            self.__cur_series = self.__get_time_series(rec)
            self.__cur_rec.write(self.__fd, _RECLEN_EXP)
            self.__cur_rec = rec

    def get_series_data(self):
        return sum([ s.get_series_data() for s in self.__span ], [])

    def output_vol(self, f):
        b = _Blockette12()
        for s in self.__span:
            b.add_span(*s.get_span_data())

        b.output(f)

    def output_index(self, f, data_start):
        for s in self.__span:
            s.output_index(f, data_start)

    def output_data(self, fd, data_start):
        if self.__cur_rec is not None:
            self.__cur_rec.write(self.__fd, _RECLEN_EXP)
            self.__cur_rec = None
            self.__cur_series = None

        self.__fd.seek(0)
        #copyfileobj(self.__fd, fd)

        i = 0
        for rec in mseed.Input(self.__fd):
            rec.recno = data_start + i
            rec.write(fd, _RECLEN_EXP)
            i += 1

        self.__fd.close()

class _RecordBuilder(object):
    def __init__(self, type, fd):
        self.__recno = 1
        self.__type = type
        self.__fd = fd
        self.__buf = None

    def flush(self):
        if self.__buf is not None:
            self.__buf += ((1 << _RECLEN_EXP) - len(self.__buf)) * " "
            self.__fd.write(self.__buf.encode("ascii", "replace"))
            self.__buf = None

    def reset(self, type, fd, recno = None):
        self.flush()
        self.__type = type
        self.__fd = fd
        if recno is not None:
            self.__recno = recno

    def get_recno(self):
        return self.__recno

    def write_blk(self, s):
        if self.__buf is None:
            self.__buf = "%06d%c " % (self.__recno, self.__type)
            self.__recno += 1

        b = 0
        while len(s) - b > (1 << _RECLEN_EXP) - len(self.__buf):
            e = b + (1 << _RECLEN_EXP) - len(self.__buf)
            self.__buf += s[b:e]
            self.__fd.write(self.__buf.encode("ascii", "replace"))

            self.__buf = "%06d%c*" % (self.__recno, self.__type)
            self.__recno += 1
            b = e

        self.__buf += s[b:]

        if len(self.__buf) > (1 << _RECLEN_EXP) - 8:
            self.flush()

class SEEDVolume(object):
    def __init__(self, inventory, organization, label, resp_dict=True):
        self.__inventory = inventory
        self.__organization = organization
        self.__label = label or ""
        self.__vol_start_time = datetime.datetime(2100,1,1,0,0,0)
        self.__vol_end_time = datetime.datetime(1971,1,1,0,0,0)
        self.__format_dict = _FormatDict()
        self.__unit_dict = _UnitDict()
        self.__comment_dict = _CommentDict()
        self.__gen_dict = _GenericAbbreviationDict(inventory)
        self.__station = {}
        self.__waveform_data = None

        if resp_dict:
            self.__resp_fac = _Response4xFactory(inventory, self.__unit_dict)

        else:
            self.__resp_fac = _Response5xFactory(inventory, self.__unit_dict)

    def add_chan(self, net_code, stat_code, loc_id, chan_id, start_time, end_time, strict=False):
        found = False

        net_tp = self.__inventory.network.get(net_code)
        if net_tp is not None:
            for netcfg in net_tp.values():
#               if _cmptime(start_time, netcfg.end) <= 0 and \
#                   _cmptime(end_time, netcfg.start) >= 0:

                    sta_tp = netcfg.station.get(stat_code)
                    if sta_tp is not None:
                        for statcfg in sta_tp.values():
#                           if _cmptime(start_time, statcfg.end) <= 0 and \
#                               _cmptime(end_time, statcfg.start) >= 0:

                                sta = self.__station.get((net_code, netcfg.start, stat_code, statcfg.start))
                                if sta is None:
                                    sta = _Station(self.__inventory, statcfg, self.__format_dict,
                                        self.__unit_dict, self.__comment_dict, self.__gen_dict,
                                        self.__resp_fac)
                                    self.__station[(net_code, netcfg.start, stat_code, statcfg.start)] = sta

                                loc_tp = statcfg.sensorLocation.get(loc_id)
                                if loc_tp is not None:
                                    for loccfg in loc_tp.values():
#                                       if _cmptime(start_time, strmcfg.end) <= 0 and \
#                                           _cmptime(end_time, strmcfg.start) >= 0:

                                            strm_tp = loccfg.stream.get(chan_id)
                                            if strm_tp is not None:
                                                for strmcfg in strm_tp.values():
                                                    if _cmptime(start_time, strmcfg.end) <= 0 and \
                                                        _cmptime(end_time, strmcfg.start) >= 0:

                                                        if _cmptime(start_time, self.__vol_start_time) < 0:
                                                            self.__vol_start_time = start_time

                                                        if _cmptime(end_time, self.__vol_end_time) > 0:
                                                            self.__vol_end_time = end_time

                                                        sta.add_chan(strmcfg)
                                                        found = True

        if not found:
            if strict:
                raise SEEDError("cannot find %s %s %s %s %s %s" % \
                    (net_code, stat_code, loc_id, chan_id, start_time, end_time))
            else:
                logs.warning("cannot find %s %s %s %s %s %s" %
                    (net_code, stat_code, loc_id, chan_id, start_time, end_time))

    def add_data(self, rec):
        if self.__waveform_data is None:
            self.__waveform_data = _WaveformData()

        self.__waveform_data.add_data(rec)

    def __output_vol(self, vol_creat_time, sta_list, rb):
        b1 = _Blockette10(record_length = _RECLEN_EXP,
            start_time = self.__vol_start_time,
            end_time = self.__vol_end_time,
            vol_time = vol_creat_time,
            organization = self.__organization,
            label = self.__label)

        b2 = _Blockette11()

        for sta in sta_list:
            (net_code, net_start, stat_code, stat_start) = sta.get_id()
            b2.add_station(stat_code, sta.get_recno())

        b1.output(rb)
        b2.output(rb)

        if self.__waveform_data is not None:
            self.__waveform_data.output_vol(rb)

        rb.flush()

    def output(self, dest, strict=False):
        vol_creat_time = datetime.datetime.utcnow()

        if self.__waveform_data is not None:
            for (net_code, stat_code, loc_id, chan_id, start_time, end_time) in \
                self.__waveform_data.get_series_data():
                try:
                    self.add_chan(net_code, stat_code, loc_id, chan_id, start_time, end_time, strict)

                except SEEDError as e:
                    if strict:
                        raise SEEDError("%s.%s.%s.%s.%s: %s" % \
                            (net_code, stat_code, loc_id, chan_id, start_time.isoformat(), e))

                    logs.warning("%s.%s.%s.%s.%s: %s" %
                        (net_code, stat_code, loc_id, chan_id, start_time.isoformat(), e))

        sta_list = [self.__station[k] for k in sorted(self.__station.keys())]

        if isinstance(dest, str):
            fd = file(dest, "wb")
        elif hasattr(dest, "write"):
            fd = dest
        else:
            raise TypeError("invalid file object")

        try:
            filename = fd.name
        except AttributeError:
            filename = '<???>'

        rb = _RecordBuilder("V", fd)
        self.__output_vol(vol_creat_time, sta_list, rb)

        rb.reset("A", fd)
        self.__format_dict.output(rb)
        self.__comment_dict.output(rb)
        self.__gen_dict.output(rb)
        self.__unit_dict.output(rb)
        self.__resp_fac.output(rb)
        rb.flush()

        rb.reset("S", fd)
        for sta in sta_list:
            sta.output(rb, self.__vol_start_time, self.__vol_end_time)

        if self.__waveform_data is not None:
            index_start = rb.get_recno()
            rb.reset("T", fd)
            self.__waveform_data.output_index(rb, 0)

            data_start = rb.get_recno()
            rb.reset("T", fd, index_start)
            fd.seek((1 << _RECLEN_EXP) * (index_start - 1), 0)
            self.__waveform_data.output_index(rb, data_start)
            self.__waveform_data.output_data(fd, data_start)

        fd.seek(0, 0)
        rb.reset("V", fd, 1)
        self.__output_vol(vol_creat_time, sta_list, rb)

        if isinstance(dest, str):
            fd.close()

