concatenates several files. The files
        must have the same internal structure (same number of groups, and same
        channels in each group)

        Parameters
        ----------
        files : list | tuple
            list of *MDF* file names or *MDF* instances
        version : str
            merged file version
        sync : bool
            sync the files based on the start of measurement, default *True*
        add_samples_origin : bool
            option to create a new "__samples_origin" channel that will hold
            the index of the measurement from where each timestamp originated

        Returns
        -------
        concatenate : MDF
            new *MDF* object with concatenated channels

        Raises
        ------
        MdfException : if there are inconsistencies between the files

def concatenate(files, version="4.10", sync=True, add_samples_origin=False, **kwargs):
        """ concatenates several files. The files
        must have the same internal structure (same number of groups, and same
        channels in each group)

        Parameters
        ----------
        files : list | tuple
            list of *MDF* file names or *MDF* instances
        version : str
            merged file version
        sync : bool
            sync the files based on the start of measurement, default *True*
        add_samples_origin : bool
            option to create a new "__samples_origin" channel that will hold
            the index of the measurement from where each timestamp originated

        Returns
        -------
        concatenate : MDF
            new *MDF* object with concatenated channels

        Raises
        ------
        MdfException : if there are inconsistencies between the files

        """
        if not files:
            raise MdfException("No files given for merge")

        callback = kwargs.get("callback", None)
        if callback:
            callback(0, 100)

        mdf_nr = len(files)

        versions = []
        if sync:
            timestamps = []
            for file in files:
                if isinstance(file, MDF):
                    timestamps.append(file.header.start_time)
                    versions.append(file.version)
                else:
                    with open(file, "rb") as mdf:
                        mdf.seek(64)
                        blk_id = mdf.read(2)
                        if blk_id == b"HD":
                            header = HeaderV3
                            versions.append("3.00")
                        else:
                            versions.append("4.00")
                            blk_id += mdf.read(2)
                            if blk_id == b"##HD":
                                header = HeaderV4
                            else:
                                raise MdfException(f'"{file}" is not a valid MDF file')

                        header = header(address=64, stream=mdf)

                        timestamps.append(header.start_time)

            try:
                oldest = min(timestamps)
            except TypeError:
                timestamps = [
                    timestamp.astimezone(timezone.utc)
                    for timestamp in timestamps
                ]
                oldest = min(timestamps)

            offsets = [(timestamp - oldest).total_seconds() for timestamp in timestamps]
            offsets = [offset if offset > 0 else 0 for offset in offsets]

        else:
            file = files[0]
            if isinstance(file, MDF):
                oldest = file.header.start_time
                versions.append(file.version)
            else:
                with open(file, "rb") as mdf:
                    mdf.seek(64)
                    blk_id = mdf.read(2)
                    if blk_id == b"HD":
                        versions.append("3.00")
                        header = HeaderV3
                    else:
                        versions.append("4.00")
                        blk_id += mdf.read(2)
                        if blk_id == b"##HD":
                            header = HeaderV4
                        else:
                            raise MdfException(f'"{file}" is not a valid MDF file')

                    header = header(address=64, stream=mdf)

                    oldest = header.start_time

            offsets = [0 for _ in files]

        version = validate_version_argument(version)

        merged = MDF(version=version, callback=callback)

        merged.header.start_time = oldest

        encodings = []
        included_channel_names = []

        if add_samples_origin:
            origin_conversion = {}
            for i, mdf in enumerate(files):
                origin_conversion[f'val_{i}'] = i
                if isinstance(mdf, MDF):
                    origin_conversion[f'text_{i}'] = str(mdf.name)
                else:
                    origin_conversion[f'text_{i}'] = str(mdf)
            origin_conversion = from_dict(origin_conversion)

        for mdf_index, (offset, mdf) in enumerate(zip(offsets, files)):
            if not isinstance(mdf, MDF):
                mdf = MDF(mdf)

            try:
                for can_id, info in mdf.can_logging_db.items():
                    if can_id not in merged.can_logging_db:
                        merged.can_logging_db[can_id] = {}
                    merged.can_logging_db[can_id].update(info)
            except AttributeError:
                pass

            if mdf_index == 0:
                last_timestamps = [None for gp in mdf.groups]
                groups_nr = len(mdf.groups)

            cg_nr = -1

            for i, group in enumerate(mdf.groups):
                included_channels = mdf._included_channels(i)

                if mdf_index == 0:
                    included_channel_names.append(
                        [group.channels[k].name for k in included_channels]
                    )
                else:
                    names = [group.channels[k].name for k in included_channels]
                    if names != included_channel_names[i]:
                        if sorted(names) != sorted(included_channel_names[i]):
                            raise MdfException(f"internal structure of file {mdf_index} is different")
                        else:
                            logger.warning(
                                f'Different channel order in channel group {i} of file {mdf_index}.'
                                ' Data can be corrupted if the there are channels with the same '
                                'name in this channel group'
                            )
                            included_channels = [
                                mdf._validate_channel_selection(
                                    name=name_,
                                    group=i,
                                )[1]
                                for name_ in included_channel_names[i]
                            ]

                if included_channels:
                    cg_nr += 1
                else:
                    continue
                channels_nr = len(group.channels)

                y_axis = MERGE

                idx = np.searchsorted(CHANNEL_COUNT, channels_nr, side="right") - 1
                if idx < 0:
                    idx = 0
                read_size = y_axis[idx]

                idx = 0
                last_timestamp = last_timestamps[i]
                first_timestamp = None
                original_first_timestamp = None

                if read_size:
                    mdf.configure(read_fragment_size=int(read_size))

                parents, dtypes = mdf._prepare_record(group)

                data = mdf._load_data(group)

                for fragment in data:

                    if dtypes.itemsize:
                        group.record = np.core.records.fromstring(
                            fragment[0], dtype=dtypes
                        )
                    else:
                        group.record = None

                    if mdf_index == 0 and idx == 0:
                        encodings_ = []
                        encodings.append(encodings_)
                        signals = []
                        for j in included_channels:
                            sig = mdf.get(
                                group=i,
                                index=j,
                                data=fragment,
                                raw=True,
                                ignore_invalidation_bits=True,
                                copy_master=False,
                            )

                            if version < "4.00":
                                if sig.samples.dtype.kind == "S":
                                    encodings_.append(sig.encoding)
                                    strsig = mdf.get(
                                        group=i,
                                        index=j,
                                        samples_only=True,
                                        ignore_invalidation_bits=True,
                                    )[0]
                                    sig.samples = sig.samples.astype(strsig.dtype)
                                    del strsig
                                    if sig.encoding != "latin-1":

                                        if sig.encoding == "utf-16-le":
                                            sig.samples = (
                                                sig.samples.view(np.uint16)
                                                .byteswap()
                                                .view(sig.samples.dtype)
                                            )
                                            sig.samples = encode(
                                                decode(sig.samples, "utf-16-be"),
                                                "latin-1",
                                            )
                                        else:
                                            sig.samples = encode(
                                                decode(sig.samples, sig.encoding),
                                                "latin-1",
                                            )
                                else:
                                    encodings_.append(None)

                            if not sig.samples.flags.writeable:
                                sig.samples = sig.samples.copy()
                            signals.append(sig)

                        if signals and len(signals[0]):
                            if offset > 0:
                                timestamps = sig[0].timestamps + offset
                                for sig in signals:
                                    sig.timestamps = timestamps
                            last_timestamp = signals[0].timestamps[-1]
                            first_timestamp = signals[0].timestamps[0]
                            original_first_timestamp = first_timestamp

                        if add_samples_origin:
                            if signals:
                                _s = signals[-1]
                                signals.append(
                                    Signal(
                                        samples=np.ones(len(_s), dtype='<u2')*mdf_index,
                                        timestamps=_s.timestamps,
                                        conversion=origin_conversion,
                                        name='__samples_origin',
                                    )
                                )
                                _s = None

                        if signals:
                            merged.append(signals, common_timebase=True)
                            try:
                                if group.channel_group.flags & v4c.FLAG_CG_BUS_EVENT:
                                    merged.groups[-1].channel_group.flags = group.channel_group.flags
                                    merged.groups[-1].channel_group.acq_name = group.channel_group.acq_name
                                    merged.groups[-1].channel_group.acq_source = group.channel_group.acq_source
                                    merged.groups[-1].channel_group.comment = group.channel_group.comment
                            except AttributeError:
                                pass
                        else:
                            break
                        idx += 1
                    else:
                        master = mdf.get_master(i, fragment, copy_master=False)
                        _copied = False

                        if len(master):
                            if original_first_timestamp is None:
                                original_first_timestamp = master[0]
                            if offset > 0:
                                master = master + offset
                                _copied = True
                            if last_timestamp is None:
                                last_timestamp = master[-1]
                            else:
                                if last_timestamp >= master[0]:
                                    if len(master) >= 2:
                                        delta = master[1] - master[0]
                                    else:
                                        delta = 0.001
                                    if _copied:
                                        master -= master[0]
                                    else:
                                        master = master - master[0]
                                        _copied = True
                                    master += last_timestamp + delta
                                last_timestamp = master[-1]

                            signals = [(master, None)]

                            for k, j in enumerate(included_channels):
                                sig = mdf.get(
                                    group=i,
                                    index=j,
                                    data=fragment,
                                    raw=True,
                                    samples_only=True,
                                    ignore_invalidation_bits=True,
                                )

                                signals.append(sig)

                                if version < "4.00":
                                    encoding = encodings[i][k]
                                    samples = sig[0]
                                    if encoding:
                                        if encoding != "latin-1":

                                            if encoding == "utf-16-le":
                                                samples = (
                                                    samples.view(np.uint16)
                                                    .byteswap()
                                                    .view(samples.dtype)
                                                )
                                                samples = encode(
                                                    decode(samples, "utf-16-be"),
                                                    "latin-1",
                                                )
                                            else:
                                                samples = encode(
                                                    decode(samples, encoding), "latin-1"
                                                )
                                            sig.samples = samples


                            if signals:
                                if add_samples_origin:
                                    _s = signals[-1][0]
                                    signals.append(
                                        (np.ones(len(_s), dtype='<u2')*mdf_index, None)
                                    )
                                    _s = None
                                merged.extend(cg_nr, signals)

                            if first_timestamp is None:
                                first_timestamp = master[0]
                        idx += 1

                    group.record = None

                last_timestamps[i] = last_timestamp

            if callback:
                callback(i + 1 + mdf_index * groups_nr, groups_nr * mdf_nr)

            if MDF._terminate:
                return

            merged._transfer_events(mdf)

        return merged